{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards   #-}

module Network.Syncthing.Types.Config
    ( Config(..)
    , FolderConfig(..)
    , DeviceConfig(..)
    , VersioningConfig(..)
    , OptionsConfig(..)
    , GuiConfig(..)
    , AddressType(..)
    ) where

import           Control.Applicative              ((<$>), (<*>))
import           Control.Monad                    (MonadPlus (mzero))
import           Data.Aeson
import qualified Data.Map                         as M
import           Data.Maybe                       (fromMaybe)
import           Data.Text                        (Text, cons, uncons)

import           Network.Syncthing.Types.Common
import           Network.Syncthing.Internal.Utils



-------------------------------------------------------------------------------
-- CONFIG RECORD -----
-------------------------------------------------------------------------------

-- | The current configuration data structure.
data Config = Config {
      getConfigVersion :: Int
    , getFolderConfigs :: [FolderConfig]
    , getDeviceConfigs :: [DeviceConfig]
    , getGuiConfig     :: GuiConfig
    , getOptionsConfig :: OptionsConfig
    } deriving (Eq, Show)

instance FromJSON Config where
    parseJSON (Object v) =
        Config <$> (v .: "Version")
               <*> (v .: "Folders")
               <*> (v .: "Devices")
               <*> (v .: "GUI")
               <*> (v .: "Options")
    parseJSON _          = mzero

instance ToJSON Config where
    toJSON Config{..} =
        object [ "Version"  .= getConfigVersion
               , "Folders"  .= getFolderConfigs
               , "Devices"  .= getDeviceConfigs
               , "GUI"      .= getGuiConfig
               , "Options"  .= getOptionsConfig
               ]


-------------------------------------------------------------------------------
-- ADDRESS TYPE -----
-------------------------------------------------------------------------------

-- | An address can be dynamic or static.
data AddressType =
      Dynamic
    | Address Addr
    deriving (Eq, Show)

decodeAddressType :: Text -> AddressType
decodeAddressType "dynamic" = Dynamic
decodeAddressType addr      = Address $ parseAddr addr

encodeAddressType :: AddressType -> Text
encodeAddressType Dynamic        = "dynamic"
encodeAddressType (Address addr) = encodeAddr addr


-------------------------------------------------------------------------------
-- FOLDER CONFIG -----
-------------------------------------------------------------------------------

-- | The folder specific configuration.
data FolderConfig = FolderConfig {
      getId              :: FolderName
    , getPath            :: Path
    , getFolderDevices   :: [Device]
    , getReadOnly        :: Bool
    , getRescanIntervalS :: Int
    , getIgnorePerms     :: Bool
    , getVersioning      :: VersioningConfig
    , getLenientMtimes   :: Bool
    , getCopiers         :: Int
    , getPullers         :: Int
    , getFolderInvalid   :: Text
    } deriving (Eq, Show)

instance FromJSON FolderConfig where
    parseJSON (Object v) =
        FolderConfig <$> (v .: "ID")
                     <*> (v .: "Path")
                     <*> (map getFolderDevice <$> (v .: "Devices"))
                     <*> (v .: "ReadOnly")
                     <*> (v .: "RescanIntervalS")
                     <*> (v .: "IgnorePerms")
                     <*> (v .: "Versioning")
                     <*> (v .: "LenientMtimes")
                     <*> (v .: "Copiers")
                     <*> (v .: "Pullers")
                     <*> (v .: "Invalid")
    parseJSON _          = mzero

instance ToJSON FolderConfig where
    toJSON FolderConfig{..} =
        object [ "ID"              .= getId
               , "Path"            .= getPath
               , "Devices"         .= map FolderDeviceConfig getFolderDevices
               , "ReadOnly"        .= getReadOnly
               , "RescanIntervalS" .= getRescanIntervalS
               , "IgnorePerms"     .= getIgnorePerms
               , "Versioning"      .= getVersioning
               , "LenientMtimes"   .= getLenientMtimes
               , "Copiers"         .= getCopiers
               , "Pullers"         .= getPullers
               , "Invalid"         .= getFolderInvalid
               ]


-------------------------------------------------------------------------------
-- VERSIONING CONFIG -----
-------------------------------------------------------------------------------

-- | Information about versioning.
data VersioningConfig = VersioningConfig {
      getType   :: Text
    , getParams :: M.Map Text Text
    } deriving (Eq, Show)

instance FromJSON VersioningConfig where
    parseJSON (Object v) =
        VersioningConfig <$> (v .: "Type")
                         <*> (v .: "Params")
    parseJSON _          = mzero

instance ToJSON VersioningConfig where
    toJSON VersioningConfig{..} =
        object [ "Type"   .= getType
               , "Params" .= getParams
               ]


-------------------------------------------------------------------------------
-- DEVICE CONFIG -----
-------------------------------------------------------------------------------

-- | Device specific configuration information.
data DeviceConfig = DeviceConfig {
      getDevice      :: Device
    , getDeviceName  :: Text
    , getAddresses   :: [AddressType]
    , getCompression :: Bool
    , getCertName    :: Text
    , getIntroducer  :: Bool
    } deriving (Eq, Show)

instance FromJSON DeviceConfig where
    parseJSON (Object v) =
        DeviceConfig <$> (v .: "DeviceID")
                     <*> (v .: "Name")
                     <*> (map decodeAddressType <$> (v .: "Addresses"))
                     <*> (v .: "Compression")
                     <*> (v .: "CertName")
                     <*> (v .: "Introducer")
    parseJSON _          = mzero

instance ToJSON DeviceConfig where
    toJSON DeviceConfig{..} =
        object [ "DeviceID"     .= getDevice
               , "Name"         .= getDeviceName
               , "Addresses"    .= map encodeAddressType getAddresses
               , "Compression"  .= getCompression
               , "CertName"     .= getCertName
               , "Introducer"   .= getIntroducer
               ]


-------------------------------------------------------------------------------
-- FOLDER-DEVICE CONFIG -----
-------------------------------------------------------------------------------

data FolderDeviceConfig = FolderDeviceConfig {
      getFolderDevice :: Device
    } deriving (Eq, Show)

instance FromJSON FolderDeviceConfig where
    parseJSON (Object v) = FolderDeviceConfig <$> (v .: "DeviceID")
    parseJSON _          = mzero

instance ToJSON FolderDeviceConfig where
    toJSON (FolderDeviceConfig device) =
        object [ "DeviceID" .= device ]


-------------------------------------------------------------------------------
-- GUI CONFIG -----
-------------------------------------------------------------------------------

-- | Gui settings.
data GuiConfig = GuiConfig {
      getEnabled    :: Bool
    , getApiKey     :: Maybe Text
    , getGuiAddress :: Addr
    , getUser       :: Text
    , getPassword   :: Text
    , getUseTLS     :: Bool
    } deriving (Eq, Show)

instance FromJSON GuiConfig where
    parseJSON (Object v) =
        GuiConfig <$> (v .: "Enabled")
                  <*> (decodeApiKey <$> (v .: "APIKey"))
                  <*> (parseAddr <$> (v .: "Address"))
                  <*> (v .: "User")
                  <*> (v .: "Password")
                  <*> (v .: "UseTLS")
    parseJSON _          = mzero

instance ToJSON GuiConfig where
    toJSON GuiConfig{..} =
        object [ "Enabled"  .= getEnabled
               , "APIKey"   .= encodeApiKey getApiKey
               , "Address"  .= encodeAddr getGuiAddress
               , "User"     .= getUser
               , "Password" .= getPassword
               , "UseTLS"   .= getUseTLS
               ]

decodeApiKey :: Text -> Maybe Text
decodeApiKey = (uncurry cons `fmap`) . uncons

encodeApiKey :: Maybe Text -> Text
encodeApiKey = fromMaybe ""


-------------------------------------------------------------------------------
-- OPTIONS CONFIG -----
-------------------------------------------------------------------------------

-- | Various config settings.
data OptionsConfig = OptionsConfig {
      getListenAddress           :: [Addr]
    , getGlobalAnnServers        :: [Text]
    , getGlobalAnnEnabled        :: Bool
    , getLocalAnnEnabled         :: Bool
    , getLocalAnnPort            :: Int
    , getLocalAnnMCAddr          :: Text
    , getMaxSendKbps             :: Int
    , getMaxRecvKbps             :: Int
    , getReconnectIntervalS      :: Int
    , getStartBrowser            :: Bool
    , getUPnPEnabled             :: Bool
    , getUPnPLease               :: Int
    , getUPnPRenewal             :: Int
    , getURAccepted              :: Int
    , getURUniqueID              :: Text
    , getRestartOnWakeup         :: Bool
    , getAutoUpgradeIntervalH    :: Int
    , getKeepTemporariesH        :: Int
    , getCacheIgnoredFiles       :: Bool
    , getProgressUpdateIntervalS :: Int
    , getSymlinksEnabled         :: Bool
} deriving (Eq, Show)

instance FromJSON OptionsConfig where
    parseJSON (Object v) =
        OptionsConfig <$> (map parseAddr <$> (v .: "ListenAddress"))
                      <*> (v .: "GlobalAnnServers")
                      <*> (v .: "GlobalAnnEnabled")
                      <*> (v .: "LocalAnnEnabled")
                      <*> (v .: "LocalAnnPort")
                      <*> (v .: "LocalAnnMCAddr")
                      <*> (v .: "MaxSendKbps")
                      <*> (v .: "MaxRecvKbps")
                      <*> (v .: "ReconnectIntervalS")
                      <*> (v .: "StartBrowser")
                      <*> (v .: "UPnPEnabled")
                      <*> (v .: "UPnPLease")
                      <*> (v .: "UPnPRenewal")
                      <*> (v .: "URAccepted")
                      <*> (v .: "URUniqueID")
                      <*> (v .: "RestartOnWakeup")
                      <*> (v .: "AutoUpgradeIntervalH")
                      <*> (v .: "KeepTemporariesH")
                      <*> (v .: "CacheIgnoredFiles")
                      <*> (v .: "ProgressUpdateIntervalS")
                      <*> (v .: "SymlinksEnabled")
    parseJSON _          = mzero

instance ToJSON OptionsConfig where
    toJSON OptionsConfig{..} =
        object [ "ListenAddress"           .= map encodeAddr getListenAddress
               , "GlobalAnnServers"        .= getGlobalAnnServers
               , "GlobalAnnEnabled"        .= getGlobalAnnEnabled
               , "LocalAnnEnabled"         .= getLocalAnnEnabled
               , "LocalAnnPort"            .= getLocalAnnPort
               , "LocalAnnMCAddr"          .= getLocalAnnMCAddr
               , "MaxSendKbps"             .= getMaxSendKbps
               , "MaxRecvKbps"             .= getMaxRecvKbps
               , "ReconnectIntervalS"      .= getReconnectIntervalS
               , "StartBrowser"            .= getStartBrowser
               , "UPnPEnabled"             .= getUPnPEnabled
               , "UPnPLease"               .= getUPnPLease
               , "UPnPRenewal"             .= getUPnPRenewal
               , "URAccepted"              .= getURAccepted
               , "URUniqueID"              .= getURUniqueID
               , "RestartOnWakeup"         .= getRestartOnWakeup
               , "AutoUpgradeIntervalH"    .= getAutoUpgradeIntervalH
               , "KeepTemporariesH"        .= getKeepTemporariesH
               , "CacheIgnoredFiles"       .= getCacheIgnoredFiles
               , "ProgressUpdateIntervalS" .= getProgressUpdateIntervalS
               , "SymlinksEnabled"         .= getSymlinksEnabled
               ]