    module Propellor.Types.PrivData where

    import Propellor.Types.OS
    import Utility.PartialPrelude
    import Utility.FileSystemEncoding

    import Data.Maybe
    import qualified Data.ByteString.Lazy as L

    -- | Note that removing or changing constructors or changing types will
    -- break the serialized privdata files, so don't do that!
    -- It's fine to add new constructors.
    data PrivDataField
            = DockerAuthentication
            | SshPubKey SshKeyType UserName -- ^ Not used anymore, but retained to avoid breaking serialization of old files
            | SshPrivKey SshKeyType UserName -- ^ For host key, use empty UserName
            | SshAuthorizedKeys UserName
            | Password UserName
            | CryptPassword UserName
            | PrivFile FilePath
            | GpgKey
            | DnsSec DnsSecKey
            deriving (Read, Show, Ord, Eq)

    -- | Combines a PrivDataField with a description of how to generate
    -- its value.
    data PrivDataSource
            = PrivDataSourceFile PrivDataField FilePath
            | PrivDataSourceFileFromCommand PrivDataField FilePath String
            | PrivDataSource PrivDataField String

    type PrivDataSourceDesc = String

    class IsPrivDataSource s where
            privDataField :: s -> PrivDataField
            describePrivDataSource :: s -> Maybe PrivDataSourceDesc

    instance IsPrivDataSource PrivDataField where
            privDataField = id
            describePrivDataSource _ = Nothing

    instance IsPrivDataSource PrivDataSource where
            privDataField s = case s of
                    PrivDataSourceFile f _ -> f
                    PrivDataSourceFileFromCommand f _ _ -> f
                    PrivDataSource f _ -> f
            describePrivDataSource s = Just $ case s of
                    PrivDataSourceFile _ f -> "< " ++ f
                    PrivDataSourceFileFromCommand _ f c ->
                            "< " ++ f ++ " (created by running, for example, `" ++ c ++ "` )"
                    PrivDataSource _ d -> "< (" ++ d ++ ")"

    -- | A context in which a PrivDataField is used.
    --
    -- Often this will be a domain name. For example,
    -- Context "www.example.com" could be used for the SSL cert
    -- for the web server serving that domain. Multiple hosts might
    -- use that privdata.
    --
    -- This appears in serialized privdata files.
    newtype Context = Context String
            deriving (Read, Show, Ord, Eq)

    -- | A context that may vary depending on the HostName where it's used.
    newtype HostContext = HostContext { mkHostContext :: HostName -> Context }

    instance Show HostContext where
            show hc = show $ mkHostContext hc "<hostname>"

    instance Ord HostContext where
            a <= b = show a <= show b

    instance Eq HostContext where
            a == b = show a == show b

    -- | Class of things that can be used as a Context.
    class IsContext c where
            asContext :: HostName -> c -> Context
            asHostContext :: c -> HostContext

    instance IsContext HostContext where
            asContext = flip mkHostContext
            asHostContext = id

    instance IsContext Context where
            asContext _ c = c
            asHostContext = HostContext . const

    -- | Use when a PrivDataField is not dependent on any paricular context.
    anyContext :: Context
    anyContext = Context "any"

    -- | Makes a HostContext that consists just of the hostname.
    hostContext :: HostContext
    hostContext = HostContext Context

    -- | Contains the actual private data.
    --
    -- Note that this may contain exta newlines at the end, or they may have
    -- been stripped off, depending on how the user entered the privdata,
    -- and which version of propellor stored it. Use the accessor functions
    -- below to avoid newline problems.
    newtype PrivData = PrivData String

    -- | When PrivData is the content of a file, this is the lines thereof.
    privDataLines :: PrivData -> [String]
    privDataLines (PrivData s) = lines s

    -- | When the PrivData is a single value, like a password, this extracts
    -- it. Note that if multiple lines are present in the PrivData, only
    -- the first is returned; there is never a newline in the String.
    privDataVal :: PrivData -> String
    privDataVal (PrivData s) = fromMaybe "" (headMaybe (lines s))

    -- | Use to get ByteString out of PrivData.
    privDataByteString :: PrivData -> L.ByteString
    privDataByteString (PrivData s) = encodeBS s

    data SshKeyType = SshRsa | SshDsa | SshEcdsa | SshEd25519
            deriving (Read, Show, Ord, Eq, Enum, Bounded)

    -- | Parameter that would be passed to ssh-keygen to generate key of this type
    sshKeyTypeParam :: SshKeyType -> String
    sshKeyTypeParam SshRsa = "RSA"
    sshKeyTypeParam SshDsa = "DSA"
    sshKeyTypeParam SshEcdsa = "ECDSA"
    sshKeyTypeParam SshEd25519 = "ED25519"

    data DnsSecKey
            = PubZSK -- ^ DNSSEC Zone Signing Key (public)
            | PrivZSK -- ^ DNSSEC Zone Signing Key (private)
            | PubKSK -- ^ DNSSEC Key Signing Key (public)
            | PrivKSK -- ^ DNSSEC Key Signing Key (private)
            deriving (Read, Show, Ord, Eq, Bounded, Enum)