{-# LANGUAGE DeriveGeneric     #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}

module Network.IPFS.Git.RemoteHelper.Options where

import           Control.Applicative (liftA2, (<|>))
import           Control.Exception.Safe (throwString)
import           Control.Monad.Trans.Maybe (MaybeT(..))
import           Data.Function (on)
import           Data.Git (withCurrentRepo)
import qualified Data.Git.Repository as Git
import           Data.IPLD.CID (CID, cidFromText)
import           Data.List (dropWhileEnd)
import           Data.Monoid (Last(..))
import           Data.Text (Text)
import qualified Data.Text as Text
import qualified Generics.SOP as SOP
import           GHC.Generics (Generic)
import           GHC.Stack (HasCallStack)
import           Network.URI
                 ( parseURI
                 , uriAuthority
                 , uriPath
                 , uriRegName
                 , uriScheme
                 )
import           Options.Applicative
                 ( Parser
                 , ReadM
                 , argument
                 , eitherReader
                 , metavar
                 , strArgument
                 )
import           Servant.Client (BaseUrl(..), Scheme(Http), parseBaseUrl)
import           System.Environment (lookupEnv)
import           Text.Read (readMaybe)

import           Network.IPFS.Git.RemoteHelper.Generic
                 ( HKD
                 , ginvalidate
                 , gvalidate
                 )
import           Network.IPFS.Git.RemoteHelper.Internal (note)


data Options = Options
    { optRemoteName :: String
    , optRemoteUrl  :: RemoteUrl
    }

data RemoteUrl = RemoteUrl
    { remoteUrlScheme   :: Text
    , remoteUrlIpfsPath :: IpfsPath
    }

data IpfsPath
    = IpfsPathIpfs CID
    | IpfsPathIpns Text

type IpfsOptions = IpfsOptions' SOP.I

data IpfsOptions' f = IpfsOptions
    { ipfsApiUrl       :: HKD f BaseUrl
    -- ^ URL of the IPFS daemon API.
    --
    -- Default: \"http://localhost:5001\"
    , ipfsMaxConns     :: HKD f Int
    -- ^ Maximum number of concurrent connections to the IPFS daemon. Note that
    -- this is approximate.
    --
    -- Default: 30
    , ipfsMaxBlockSize :: HKD f Int
    -- ^ The maximum size of an IPFS block. This is configurable as there is no
    -- unambiguous documentation on what the actual value is. It may also be
    -- subject to change in the future.
    --
    -- Default: 2048000 (2MB)
    } deriving Generic

instance SOP.Generic (IpfsOptions' f)

instance Semigroup (IpfsOptions' Last) where
    a <> b = IpfsOptions
        { ipfsApiUrl       = on (<>) ipfsApiUrl       a b
        , ipfsMaxConns     = on (<>) ipfsMaxConns     a b
        , ipfsMaxBlockSize = on (<>) ipfsMaxBlockSize a b
        }

instance Monoid (IpfsOptions' Last) where
    mempty  = IpfsOptions mempty mempty mempty
    mappend = (<>)

defaultIpfsOptions :: IpfsOptions
defaultIpfsOptions = IpfsOptions
    { ipfsApiUrl       = BaseUrl Http "localhost" 5001 mempty
    , ipfsMaxConns     = 30
    , ipfsMaxBlockSize = 2048000
    }

parseOptions :: Parser Options
parseOptions = liftA2 Options
    (strArgument (metavar "REMOTE_NAME"))
    (argument remoteUrl (metavar "URL"))

remoteUrl :: ReadM RemoteUrl
remoteUrl = eitherReader $ \s -> do
    uri  <- note "Invalid URI" $ parseURI s
    let path = dropWhile (== '/') $ uriPath uri
    ipfs <-
        case uriRegName <$> uriAuthority uri of
            Just "ipns" -> pure . IpfsPathIpns . Text.pack $ path
            _           -> IpfsPathIpfs <$> cidFromString path
    pure RemoteUrl
        { remoteUrlScheme   = Text.pack . dropWhileEnd (== ':') $ uriScheme uri
        , remoteUrlIpfsPath = ipfs
        }
  where
    cidFromString = cidFromText . \case
        [] -> emptyRepo
        xs -> Text.pack xs

    emptyRepo = "QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn"

-- | Determine the 'IpfsOptions'.
--
-- This must happen after 'parseOptions' in order to support per-remote
-- settings. The @GIT_DIR@ environment variable must be set and point to a valid
-- git repository (when the remote helper is invoked by git, this is the current
-- repository).
--
-- 'IpfsOptions' are configured using @git-config(2)@. The precedence rules
-- specified there apply. However, @$XDG_CONFIG_HOME/git/config@ and
-- @$(prefix)/etc/gitconfig@ (i.e. @--system@) are __not__ yet supported.
--
-- The available configuration keys are:
--
-- * @ipfs.apiurl@
-- * @ipfs.maxconnections@
-- * @ipfs.maxblocksize@
--
-- 'ipfsApiUrl' may be overridden per-remote using the key @remote.<remote
-- name>.ipfsapiurl@ (e.g. @remote.origin.ipfsapiurl@). If the environment
-- variable @IPFS_API_URL@, it will be used instead of any @git-config@
-- settings.
--
getIpfsOptions :: HasCallStack => Options -> IO IpfsOptions
getIpfsOptions Options { optRemoteName } = withCurrentRepo $ \r -> do
    ipfsApiUrl <-
        fmap Last . runMaybeT $ do
            url <-
                    MaybeT (lookupEnv "IPFS_API_URL")
                <|> MaybeT (Git.configGet r ("remote \"" <> optRemoteName <> "\"") "ipfsapiurl")
                <|> MaybeT (Git.configGet r "ipfs" "apiurl")
            parseBaseUrl url

    ipfsMaxConns <-
        Last . (>>= readMaybe) <$> Git.configGet r "ipfs" "maxconnections"
    ipfsMaxBlockSize <-
        Last . (>>= readMaybe) <$> Git.configGet r "ipfs" "maxblocksize"

    maybe (throwString "Das Unmögliche ist eingetreten: Invalid IpfsOptions")
          pure
        . getLast
        . gvalidate
        $ ginvalidate pure defaultIpfsOptions <> IpfsOptions {..}