module Crypto.Gpgme.Types where

import Bindings.Gpgme
import qualified Data.ByteString as BS
import Data.Maybe(catMaybes)
import Foreign
import qualified Foreign.Concurrent as FC
import Foreign.C.String (peekCString)
import System.IO.Unsafe (unsafePerformIO)
import Control.Exception (SomeException, Exception)

-- | the protocol to be used in the crypto engine
data Protocol =
      CMS
    | GPGCONF
    | OpenPGP
    | UNKNOWN
    deriving (Show, Eq, Ord)

-- | Context to be passed around with operations. Use 'newCtx' or
--   'withCtx' in order to obtain an instance.
data Ctx = Ctx {
      _ctx             :: Ptr C'gpgme_ctx_t -- ^ context
    , _version         :: String            -- ^ GPGME version
    , _protocol        :: Protocol          -- ^ context protocol
    , _engineVersion   :: String            -- ^ engine version
}

-- | Modes for key listings
data KeyListingMode
    = KeyListingLocal
    | KeyListingExtern
    | KeyListingSigs
    | KeyListingSigNotations
    | KeyListingValidate

-- | Modes for signing with GPG
data SignMode = Normal | Detach | Clear deriving Show

-- | a fingerprint
type Fpr = BS.ByteString

-- | a plaintext
type Plain = BS.ByteString

-- | an ciphertext
type Encrypted = BS.ByteString

-- | a signature
type Signature = BS.ByteString

-- | the summary of a signature status
data SignatureSummary =
      BadPolicy                        -- ^ A policy requirement was not met
    | CrlMissing                       -- ^ The CRL is not available
    | CrlTooOld                        -- ^ Available CRL is too old
    | Green                            -- ^ The signature is good but one might want to display some extra information
    | KeyExpired                       -- ^ The key or one of the certificates has expired
    | KeyMissing                       -- ^ Can’t verify due to a missing key or certificate
    | KeyRevoked                       -- ^ The key or at least one certificate has been revoked
    | Red                              -- ^ The signature is bad
    | SigExpired                       -- ^ The signature has expired
    | SysError                         -- ^ A system error occured
    | UnknownSummary C'gpgme_sigsum_t  -- ^ The summary is something else
    | Valid                            -- ^ The signature is fully valid
    deriving (Show, Eq, Ord)

-- | Translate the gpgme_sigsum_t bit vector to a list of SignatureSummary
toSignatureSummaries :: C'gpgme_sigsum_t -> [SignatureSummary]
toSignatureSummaries x = catMaybes $ map (\(mask, val) -> if mask .&. x == 0 then Nothing else Just val)
    [ (c'GPGME_SIGSUM_BAD_POLICY , BadPolicy)
    , (c'GPGME_SIGSUM_CRL_MISSING, CrlMissing)
    , (c'GPGME_SIGSUM_CRL_TOO_OLD, CrlTooOld)
    , (c'GPGME_SIGSUM_GREEN      , Green)
    , (c'GPGME_SIGSUM_KEY_EXPIRED, KeyExpired)
    , (c'GPGME_SIGSUM_KEY_MISSING, KeyMissing)
    , (c'GPGME_SIGSUM_KEY_REVOKED, KeyRevoked)
    , (c'GPGME_SIGSUM_RED        , Red)
    , (c'GPGME_SIGSUM_SIG_EXPIRED, SigExpired)
    , (c'GPGME_SIGSUM_SYS_ERROR  , SysError)
    , (c'GPGME_SIGSUM_VALID      , Valid)
    ]

type VerificationResult = [(GpgmeError, [SignatureSummary], Fpr)]

-- | The fingerprint and an error code
type InvalidKey = (String, Int)
-- TODO map intot better error code

-- | A key from the context
newtype Key = Key { unKey :: ForeignPtr C'gpgme_key_t }

-- | Allocate a key
allocKey :: IO Key
allocKey = do
    keyPtr <- malloc
    let finalize = do
            peek keyPtr >>= c'gpgme_key_unref
            free keyPtr
    Key `fmap` FC.newForeignPtr keyPtr finalize

-- | Perform an action with the pointer to a 'Key'
withKeyPtr :: Key -> (Ptr C'gpgme_key_t -> IO a) -> IO a
withKeyPtr (Key fPtr) f = withForeignPtr fPtr f

-- | Whether to include secret keys when searching
data IncludeSecret =
      WithSecret -- ^ do not include secret keys
    | NoSecret   -- ^ include secret keys
    deriving (Show, Eq, Ord)

data Flag =
      AlwaysTrust
    | NoFlag
    deriving (Show, Eq, Ord)

-- | A GPGME error.
--
-- Errors in GPGME consist of two parts: a code indicating the nature of the fault,
-- and a source indicating from which subsystem the error originated.
newtype GpgmeError = GpgmeError C'gpgme_error_t
                   deriving (Show, Ord, Eq)

-- | An explanatory string for a GPGME error.
errorString :: GpgmeError -> String
errorString (GpgmeError n) =
    unsafePerformIO $ c'gpgme_strerror n >>= peekCString

-- | An explanatory string describing the source of a GPGME error
sourceString :: GpgmeError -> String
sourceString (GpgmeError n) =
    unsafePerformIO $ c'gpgme_strsource n >>= peekCString

-- | error indicating what went wrong in decryption
data DecryptError =
      NoData              -- ^ no data to decrypt
    | Failed              -- ^ not a valid cipher
    | BadPass             -- ^ passphrase for secret was wrong
    | Unknown GpgmeError  -- ^ something else went wrong
    deriving (Show, Eq, Ord)

toDecryptError :: C'gpgme_error_t -> DecryptError
toDecryptError n =
    case unsafePerformIO $ c'gpgme_err_code n of
        58   -> NoData
        152  -> Failed
        11   -> BadPass
        x    -> Unknown (GpgmeError x)

-- | The validity of a user identity
data Validity =
      ValidityUnknown
    | ValidityUndefined
    | ValidityNever
    | ValidityMarginal
    | ValidityFull
    | ValidityUltimate
    deriving (Show, Ord, Eq)

-- | A public-key encryption algorithm
data PubKeyAlgo =
      Rsa
    | RsaE
    | RsaS
    | ElgE
    | Dsa
    | Elg
    deriving (Show, Ord, Eq)

-- | h-gpgme exception for wrapping exception which occur outside of the control of h-gpgme
newtype HgpgmeException = HgpgmeException SomeException deriving (Show)
instance Exception HgpgmeException