module Network.IPFS.Peer
  ( all
  , rawList
  , connect
  , connectRetry
  , getExternalAddress
  ) where

import qualified RIO.Text            as Text
import qualified RIO.List            as List

import qualified Net.IPv4 as IPv4
import           Text.Regex

import           Network.IPFS.Prelude hiding (all)
import qualified Network.IPFS.Internal.UTF8       as UTF8

import qualified Network.IPFS.Types         as IPFS
import qualified Network.IPFS.Process.Error as Process
import           Network.IPFS.Local.Class   as IPFS
import           Network.IPFS.Peer.Error    as IPFS.Peer
import           Network.IPFS.Peer.Types
import           Network.IPFS.Info.Types

all ::
  MonadLocalIPFS m
  => m (Either IPFS.Peer.Error [IPFS.Peer])
all = rawList <&> \case
  Right raw -> case UTF8.encode raw of
    Left  _    -> Left  <| DecodeFailure <| show raw
    Right text -> Right <| IPFS.Peer <$> Text.lines text
  Left err -> Left . UnknownErr <| UTF8.textShow err

rawList ::
  MonadLocalIPFS m
  => m (Either Process.Error Process.RawMessage)
rawList = IPFS.runLocal ["bootstrap", "list"] ""

connect ::
  MonadLocalIPFS m
  => Peer
  -> m (Either IPFS.Peer.Error ())
connect peer@(Peer peerID) = IPFS.runLocal ["swarm", "connect"] (UTF8.textToLazyBS peerID) >>= pure . \case
  Left _ -> Left <| CannotConnect peer
  Right _ -> Right ()

connectRetry ::
  MonadLocalIPFS m
  => Peer
  -> Int
  -> m (Either IPFS.Peer.Error ())
connectRetry peer (-1) = return . Left <| CannotConnect peer
connectRetry peer tries = connect peer >>= \case
  Right _ -> return <| Right ()
  Left _err -> connectRetry peer (tries - 1)

peerAddressRe :: Regex
peerAddressRe = mkRegex "^/ip[46]/([a-zA-Z0-9.:]*)/"

-- | Retrieve just the ip address from a peer address
extractIPfromPeerAddress :: String -> Maybe String
extractIPfromPeerAddress peer = matchRegex peerAddressRe peer >>= List.headMaybe

-- | True if a given peer address is externally accessable
isExternalIPv4 :: Text -> Bool
isExternalIPv4 ip = maybe False not isReserved
  where
    isReserved :: Maybe Bool
    isReserved = do
      ipAddress  <- extractIPfromPeerAddress <| Text.unpack ip
      normalized <- IPv4.decode <| Text.pack ipAddress
      return <| IPv4.reserved normalized

-- | Filter a list of peers to include only the externally accessable addresses
filterExternalPeers :: [Peer] -> [Peer]
filterExternalPeers = filter (isExternalIPv4 . peer)

-- | Get all external ipfs peer addresses
getExternalAddress ::
  MonadLocalIPFS m
  => m (Either IPFS.Peer.Error [Peer])
getExternalAddress = IPFS.runLocal ["id"] "" >>= \case
  Left err -> return <| Left <| UnknownErr <| UTF8.textShow err
  Right raw ->
    raw
      |> decode
      |> maybe [] addresses
      |> Right . filterExternalPeers
      |> pure