{-# LANGUAGE TypeOperators, TypeFamilies #-}

{-|
Module      : Peer
Copyright   : (c) Kai Lindholm, 2014
License     : MIT
Maintainer  : megantti@gmail.com
Stability   : experimental

For more info on actions, see "Network.RTorrent.Action".
-}

module Network.RTorrent.Peer (
    PeerId (..)
  , PeerInfo (..)
  , PeerAction 

  , getPeerPartial
  , allPeers
  , getTorrentPeers

  -- * Control peers
  , banPeer
  , disconnectPeer

  -- * Functions for single variables
  , getPeerHash
  , getPeerIp

  , getPeerClientVersion
  , getPeerUpRate
  , getPeerDownRate
  , getPeerUpTotal
  , getPeerDownTotal
  , getPeerEncrypted
  , getPeerCompletedPercent
  , getPeerPort
) where

import Control.Applicative
import Control.DeepSeq

import Network.RTorrent.Action.Internals
import Network.RTorrent.Torrent
import Network.RTorrent.Command
import Network.XmlRpc.Internals

import Data.List.Split (splitOn)

data PeerId = PeerId !TorrentId !String 
    deriving Show

instance XmlRpcType PeerId where
    toValue (PeerId (TorrentId tid) i) = ValueString $ tid ++ ":p" ++ i
    fromValue v = return . uncurry PeerId =<< parse =<< fromValue v
      where
        parse :: Monad m => String -> m (TorrentId, String)
        parse str = do
            [hash, s] <- return $ splitOn ":p" str
            return (TorrentId hash, s)
    getType _ = TString

instance NFData PeerId where
    rnf (PeerId tid i) = rnf tid `seq` rnf i

data PeerInfo = PeerInfo {
    peerClientVersion :: String
  , peerIp :: String
  , peerUpRate :: !Int
  , peerDownRate :: !Int
  , peerUpTotal :: !Int
  , peerDownTotal :: !Int
  , peerEncrypted :: !Bool
  , peerCompletedPercent :: !Int
  , peerPort :: !Int
  , peerId :: PeerId
} deriving Show

instance NFData PeerInfo where
    rnf (PeerInfo a0 a1 a2 a3 a4 a5 a6 a7 a8 a9) =
              rnf a0
        `seq` rnf a1 
        `seq` rnf a2
        `seq` rnf a3
        `seq` rnf a4
        `seq` rnf a5
        `seq` rnf a6
        `seq` rnf a7
        `seq` rnf a8
        `seq` rnf a9

getPeerHash :: PeerId -> PeerAction String
getPeerHash = simpleAction "p.get_id" []

getPeerIp :: PeerId -> PeerAction String
getPeerIp = simpleAction "p.get_address" []

getPeerClientVersion :: PeerId -> PeerAction String
getPeerClientVersion = simpleAction "p.get_client_version" []

getPeerUpRate :: PeerId -> PeerAction Int
getPeerUpRate = simpleAction "p.get_up_rate" []

getPeerDownRate :: PeerId -> PeerAction Int
getPeerDownRate = simpleAction "p.get_down_rate" []

getPeerUpTotal :: PeerId -> PeerAction Int
getPeerUpTotal = simpleAction "p.get_up_total" []

getPeerDownTotal :: PeerId -> PeerAction Int
getPeerDownTotal = simpleAction "p.get_down_total" []

getPeerEncrypted :: PeerId -> PeerAction Bool
getPeerEncrypted = fmap toEnum . simpleAction "p.is_encrypted" []

getPeerCompletedPercent :: PeerId -> PeerAction Int
getPeerCompletedPercent = simpleAction "p.get_completed_percent" []

getPeerPort :: PeerId -> PeerAction Int
getPeerPort = simpleAction "p.get_port" []

-- | Get a partial peer. @PeerId@ can be gotten by running @allPeers@.
getPeerPartial :: PeerId -> PeerAction (PeerId -> PeerInfo)
getPeerPartial = runActionB $ PeerInfo
         <$> b getPeerClientVersion
         <*> b getPeerIp
         <*> b getPeerUpRate
         <*> b getPeerDownRate
         <*> b getPeerUpTotal
         <*> b getPeerDownTotal
         <*> b getPeerEncrypted
         <*> b getPeerCompletedPercent
         <*> b getPeerPort
  where
    b = ActionB

disconnectPeer :: PeerId -> PeerAction Int
disconnectPeer = simpleAction "p.disconnect" []

banPeer :: PeerId -> PeerAction Int
banPeer = simpleAction "p.banned.set" [PInt 1]

type PeerAction = Action PeerId

getTorrentPeers :: TorrentId -> TorrentAction [PeerInfo]
getTorrentPeers = fmap (map contract) . allPeers getPeerPartial
  where
    contract (x :*: f) = f x

-- | Run the peer action on all peers that a torrent has.
allPeers :: (PeerId -> PeerAction a) -> TorrentId -> TorrentAction [PeerId :*: a]
allPeers p = fmap addId . (getTorrentId <+> allToMulti (allP (getPeerHash <+> p)))
  where
    addId (hash :*: peers) = 
        map (\(phash :*: f) -> PeerId hash phash :*: f) peers
    allP :: (PeerId -> PeerAction a) -> AllAction PeerId a
    allP = AllAction (PeerId (TorrentId "") "") "p.multicall"