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

module Network.Docker.Types where

import           Prelude                hiding(id)
import           Control.Applicative
import           Control.Lens.TH
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)


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

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

data DockerClientOpts = DockerClientOpts {
      apiVersion :: ApiVersion
    , baseUrl    :: URL
    } 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)