{-# LANGUAGE DeriveFunctor     #-}
{-# LANGUAGE FlexibleContexts  #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards   #-}
{-# LANGUAGE TemplateHaskell   #-}

module Network.Docker.Types where

import           Control.Applicative
import           Control.Lens.TH
import           Data.Aeson
import           Data.Aeson.TH
import           Data.Bool
import qualified Data.ByteString.Lazy   as BS
import           Data.Default
import qualified Data.Map               as Map
import qualified Data.Text              as T
import           GHC.Generics
import           Network.Docker.Options
import           Network.Wreq.Types     (Postable)
import           OpenSSL.Session        (SSLContext)
import           Prelude                hiding (id)

type URL = String
type ApiVersion = String
type Endpoint = String

type Tag = String
type IP = String
type Port = Int
type PortType = String

data SSL = NoSSL | SSL SSLOptions deriving Show

data DockerClientOpts = DockerClientOpts {
      apiVersion :: ApiVersion
    , baseUrl    :: URL
    , ssl        :: SSL
    } deriving (Show)


data SSLOptions = SSLOptions {
    optionsKey  :: FilePath
  , optionsCert :: FilePath
  } deriving Show


data ResourceId = ResourceId { _id :: String } deriving (Show, Eq)


data DockerImage = DockerImage
                { _imageId        :: ResourceId
                , _imageCreatedAt :: Int
                , _parentId       :: Maybe String
                , _repoTags       :: [Tag]
                , _size           :: Int
                , _virtualSize    :: Int
                } deriving (Show, Eq)


data DockerVersion = DockerVersion
                  { _Version       :: String
                  , _GitCommit     :: String
                  , _GoVersion     :: String
                  , _Arch          :: String
                  , _KernelVersion :: String
                  } deriving (Show, Eq)


-- The JSON looks likes this:
-- "Ports":[{"IP":"0.0.0.0","PrivatePort":55555,"PublicPort":55555,"Type":"tcp"}]

data PortMap = PortMap
            { _ip          :: IP
            , _privatePort :: Port
            , _publicPort  :: Port
            , _type        :: PortType
            } deriving (Show, Eq)

data DeleteOpts = DeleteOpts
            { removeVolumes :: Bool
            , force         :: Bool
            }

defaultDeleteOpts = DeleteOpts False False


data DockerContainer = DockerContainer
                    { _containerId        :: ResourceId
                    , _containerImageId   :: ResourceId
                    , _command            :: String
                    , _containerCreatedAt :: Int
                    , _names              :: [String]
                    , _status             :: String
                    , _ports              :: Maybe [PortMap]
                    } deriving (Show, Eq)


data CreateContainerOpts = CreateContainerOpts
                  { _hostname       :: String
                  , _user           :: String
                  , _memory         :: Int
                  , _memorySwap     :: Int
                  , _attachStdin    :: Bool
                  , _attachStdout   :: Bool
                  , _attachStderr   :: Bool
                  , _portSpecs      :: Maybe Object
                  , _tty            :: Bool
                  , _openStdin      :: Bool
                  , _stdinOnce      :: Bool
                  , _env            :: Maybe Object
                  , _cmd            :: [String]
                  , _image          :: String
                  , _volumes        :: Maybe Object
                  , _volumesFrom    :: Maybe Object
                  , _workingDir     :: String
                  , _disableNetwork :: Bool
                  , _exposedPorts   :: Maybe Object
                  } deriving (Show)

defaultCreateOpts = CreateContainerOpts {
                             _hostname = ""
                            , _user = ""
                            , _memory = 0
                            , _memorySwap =  0
                            , _attachStdin = False
                            , _attachStdout = False
                            , _attachStderr = False
                            , _portSpecs = Nothing
                            , _tty = False
                            , _openStdin =  False
                            , _stdinOnce = False
                            , _env = Nothing
                            , _cmd = []
                            , _image = "debian"
                            , _volumes = Nothing
                            , _volumesFrom =  Nothing
                            , _workingDir = ""
                            , _disableNetwork = False
                            , _exposedPorts = Nothing
                            }

instance ToJSON CreateContainerOpts where
        toJSON (CreateContainerOpts {..}) = object
            [ "Hostname" .= _hostname
            , "User" .= _user
            , "Memory" .= _memory
            , "MemorySwap" .= _memorySwap
            , "AttachStdin" .= _attachStdin
            , "AttachStdout" .= _attachStdout
            , "AttachStderr" .= _attachStderr
            , "PortSpecs" .= _portSpecs
            , "Tty" .= _tty
            , "OpenStdin" .= _openStdin
            , "StdinOnce" .= _stdinOnce
            , "Env" .= _env
            , "Cmd" .= _cmd
            , "Image" .= _image
            , "Volumes" .= _volumes
            , "VolumesFrom" .= _volumesFrom
            , "WrokingDir" .= _workingDir
            , "DisableNetwork" .= _disableNetwork
            , "ExposedPorts" .= _exposedPorts
            ]

-- data CreateContainerResponse = CreateContainerResponse
--                               { _createdContainerId :: String
--                               , _warnings           :: Maybe [T.Text]
--                               } deriving (Show)

data StartContainerOpts = StartContainerOpts
                        { _Binds           :: [T.Text]
                        , _Links           :: [T.Text]
                        , _LxcConf         :: [(T.Text, T.Text)]
                        , _PortBindings    :: [((Int,T.Text),Int)]
                        , _PublishAllPorts :: Bool
                        , _Privileged      :: Bool
                        , _Dns             :: [T.Text]
                        , _VolumesFrom     :: [T.Text]
			, _RestartPolicy   :: RestartPolicy
                        } deriving (Show)

defaultStartOpts = StartContainerOpts
                { _Binds = []
                , _Links = []
                , _LxcConf = []
                , _PortBindings = []
                , _PublishAllPorts = False
                , _Privileged = False
                , _Dns = []
                , _VolumesFrom = []
                , _RestartPolicy = RestartNever
                }

instance ToJSON StartContainerOpts where
        toJSON (StartContainerOpts {..}) = object
            [ "Binds" .= _Binds
            , "Links" .= _Links
            , "LxcConf" .= _LxcConf
            , "PortBindings" .= object (map (\((h,p),c)->T.concat [T.pack (show h),"/",p] .= [object ["HostPort" .= show c]]) _PortBindings)
            , "PublishAllPorts" .= _PublishAllPorts
            , "Privileged" .= _Privileged
            , "Dns" .= _Dns
            , "VolumesFrom" .= _VolumesFrom
	    , "RestartPolicy" .= _RestartPolicy
            ]

data RestartPolicy = RestartNever
                   | RestartAlways
                   | RestartOnFailure Int
                   deriving (Show)

instance ToJSON RestartPolicy where
  toJSON RestartNever = object ["Name" .= (""::String), "MaximumRetryCount" .= (0::Int) ]
  toJSON RestartAlways = object ["Name" .= ("always"::String), "MaximumRetryCount" .= (0::Int) ]
  toJSON (RestartOnFailure n) = object ["Name" .= ("on-failure"::String), "MaximumRetryCount" .= n ]

makeClassy ''ResourceId

-- makeLenses ''CreateContainerResponse
makeLenses ''DockerImage
makeLenses ''DockerContainer
makeLenses ''CreateContainerOpts

instance HasResourceId DockerImage where
        resourceId = imageId

instance FromJSON DockerImage where
        parseJSON (Object v) =
            DockerImage <$> ResourceId <$> (v .: "Id")
                <*> (v .: "Created")
                <*> (v .:? "ParentId")
                <*> (v .: "RepoTags")
                <*> (v .: "Size")
                <*> (v .: "VirtualSize")

instance FromJSON PortMap where
        parseJSON (Object v) =
            PortMap <$> (v .: "IP")
                <*> (v .: "PrivatePort")
                <*> (v .: "PublicPort")
                <*> (v .: "Type")

instance HasResourceId DockerContainer where
        resourceId = containerId

instance FromJSON DockerContainer where
        parseJSON (Object v) =
            DockerContainer <$> (ResourceId <$> (v .: "Id"))
                <*> (ResourceId <$> (v .: "Id"))
                <*> (v .: "Command")
                <*> (v .: "Created")
                <*> (v .: "Names")
                <*> (v .: "Status")
                <*> (v .:? "Ports")

-- instance FromJSON CreateContainerResponse where
--         parseJSON (Object v) =
--             CreateContainerResponse <$> (v .: "Id")
--                 <*> (v .:? "warnings")

$(deriveJSON dopts ''DockerVersion)