-- Pkt.hs: OpenPGP (RFC4880) Pkt data types
-- Copyright © 2012-2022  Clint Adams
-- This software is released under the terms of the Expat license.
-- (See the LICENSE file).
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TemplateHaskell #-}

module Codec.Encryption.OpenPGP.Types.Internal.Pkt where

import GHC.Generics (Generic)

import Codec.Encryption.OpenPGP.Types.Internal.Base
import Codec.Encryption.OpenPGP.Types.Internal.PKITypes

import Codec.Encryption.OpenPGP.Types.Internal.PrettyUtils (prettyLBS)
import Control.Lens (makeLenses)
import Data.Aeson ((.=), object)
import qualified Data.Aeson as A
import Data.ByteString.Lazy (ByteString)
import qualified Data.ByteString.Lazy as BL
import Data.Data (Data)
import Data.Hashable (Hashable(..))
import Data.List.NonEmpty (NonEmpty)
import qualified Data.List.NonEmpty as NE
import Data.Ord (comparing)
import Data.Text (Text)
import qualified Data.Text as T
import Data.Time.Clock (UTCTime)
import Data.Typeable (Typeable)
import Data.Word (Word8)
import Prettyprinter (Pretty(..), (<+>))

-- data Pkt = forall a. (Packet a, Show a, Eq a) => Pkt a
data Pkt
  = PKESKPkt PacketVersion EightOctetKeyId PubKeyAlgorithm (NonEmpty MPI)
  | SignaturePkt SignaturePayload
  | SKESKPkt PacketVersion SymmetricAlgorithm S2K (Maybe BL.ByteString)
  | OnePassSignaturePkt
      PacketVersion
      SigType
      HashAlgorithm
      PubKeyAlgorithm
      EightOctetKeyId
      NestedFlag
  | SecretKeyPkt PKPayload SKAddendum
  | PublicKeyPkt PKPayload
  | SecretSubkeyPkt PKPayload SKAddendum
  | CompressedDataPkt CompressionAlgorithm CompressedDataPayload
  | SymEncDataPkt ByteString
  | MarkerPkt ByteString
  | LiteralDataPkt DataType FileName ThirtyTwoBitTimeStamp ByteString
  | TrustPkt ByteString
  | UserIdPkt Text
  | PublicSubkeyPkt PKPayload
  | UserAttributePkt [UserAttrSubPacket]
  | SymEncIntegrityProtectedDataPkt PacketVersion ByteString
  | ModificationDetectionCodePkt ByteString
  | OtherPacketPkt Word8 ByteString
  | BrokenPacketPkt String Word8 ByteString
  deriving (Data, Eq, Generic, Show, Typeable) -- FIXME

instance Hashable Pkt

instance Ord Pkt where
  compare = comparing pktTag <> comparing hash -- FIXME: is there something saner?

instance Pretty Pkt where
  pretty (PKESKPkt pv eoki pka mpis) =
    pretty "PKESK v" <> pretty pv <> pretty ':' <+>
    pretty eoki <+> pretty pka <+> (pretty . NE.toList) mpis
  pretty (SignaturePkt sp) = pretty sp
  pretty (SKESKPkt pv sa s2k mbs) =
    pretty "SKESK v" <> pretty pv <> pretty ':' <+>
    pretty sa <+> pretty s2k <+> pretty (fmap bsToHexUpper mbs)
  pretty (OnePassSignaturePkt pv st ha pka eoki nestedflag) =
    pretty "one-pass signature v" <> pretty pv <> pretty ':' <+>
    pretty st <+> pretty ha <+> pretty pka <+> pretty eoki <+> pretty nestedflag
  pretty (SecretKeyPkt pkp ska) =
    pretty "secret key:" <+> pretty pkp <+> pretty ska
  pretty (PublicKeyPkt pkp) = pretty "public key:" <+> pretty pkp
  pretty (SecretSubkeyPkt pkp ska) =
    pretty "secret subkey:" <+> pretty pkp <+> pretty ska
  pretty (CompressedDataPkt ca cdp) =
    pretty "compressed-data:" <+> pretty ca <+> prettyLBS cdp
  pretty (SymEncDataPkt bs) =
    pretty "symmetrically-encrypted-data:" <+> pretty (bsToHexUpper bs)
  pretty (MarkerPkt bs) = pretty "marker:" <+> pretty (bsToHexUpper bs)
  pretty (LiteralDataPkt dt fn ts bs) =
    pretty "literal-data" <+>
    pretty dt <+> prettyLBS fn <+> pretty ts <+> pretty (bsToHexUpper bs)
  pretty (TrustPkt bs) = pretty "trust:" <+> pretty (BL.unpack bs)
  pretty (UserIdPkt u) = pretty "user-ID:" <+> pretty u
  pretty (PublicSubkeyPkt pkp) = pretty "public subkey:" <+> pretty pkp
  pretty (UserAttributePkt us) = pretty "user-attribute:" <+> pretty us
  pretty (SymEncIntegrityProtectedDataPkt pv bs) =
    pretty "symmetrically-encrypted-integrity-protected-data v" <> pretty pv <>
    pretty ':' <+>
    pretty (bsToHexUpper bs)
  pretty (ModificationDetectionCodePkt bs) =
    pretty "MDC:" <+> pretty (bsToHexUpper bs)
  pretty (OtherPacketPkt t bs) =
    pretty "unknown packet type" <+>
    pretty t <> pretty ':' <+> pretty (bsToHexUpper bs)
  pretty (BrokenPacketPkt s t bs) =
    pretty "BROKEN packet (" <> pretty s <> pretty ')' <+>
    pretty t <> pretty ':' <+> pretty (bsToHexUpper bs)

instance A.ToJSON Pkt where
  toJSON (PKESKPkt pv eoki pka mpis) =
    object
      [ key "pkesk" .=
        object
          [ key "version" .= pv
          , key "keyid" .= eoki
          , key "pkalgo" .= pka
          , key "mpis" .= NE.toList mpis
          ]
      ]
  toJSON (SignaturePkt sp) = object [key "signature" .= sp]
  toJSON (SKESKPkt pv sa s2k mbs) =
    object
      [ key "skesk" .=
        object
          [ key "version" .= pv
          , key "symalgo" .= sa
          , key "s2k" .= s2k
          , key "data" .= maybe mempty BL.unpack mbs
          ]
      ]
  toJSON (OnePassSignaturePkt pv st ha pka eoki nestedflag) =
    object
      [ key "onepasssignature" .=
        object
          [ key "version" .= pv
          , key "sigtype" .= st
          , key "hashalgo" .= ha
          , key "pkalgo" .= pka
          , key "keyid" .= eoki
          , key "nested" .= nestedflag
          ]
      ]
  toJSON (SecretKeyPkt pkp ska) =
    object
      [ key "secretkey" .=
        object [key "public" .= pkp, key "secret" .= ska]
      ]
  toJSON (PublicKeyPkt pkp) = object [key "publickey" .= pkp]
  toJSON (SecretSubkeyPkt pkp ska) =
    object
      [ key "secretsubkey" .=
        object [key "public" .= pkp, key "secret" .= ska]
      ]
  toJSON (CompressedDataPkt ca cdp) =
    object
      [ key "compresseddata" .=
        object [key "compressionalgo" .= ca, key "data" .= BL.unpack cdp]
      ]
  toJSON (SymEncDataPkt bs) = object [key "symencdata" .= BL.unpack bs]
  toJSON (MarkerPkt bs) = object [key "marker" .= BL.unpack bs]
  toJSON (LiteralDataPkt dt fn ts bs) =
    object
      [ key "literaldata" .=
        object
          [ key "dt" .= dt
          , key "filename" .= BL.unpack fn
          , key "ts" .= ts
          , key "data" .= BL.unpack bs
          ]
      ]
  toJSON (TrustPkt bs) = object [key "trust" .= BL.unpack bs]
  toJSON (UserIdPkt u) = object [key "userid" .= u]
  toJSON (PublicSubkeyPkt pkp) = object [key "publicsubkkey" .= pkp]
  toJSON (UserAttributePkt us) = object [key "userattribute" .= us]
  toJSON (SymEncIntegrityProtectedDataPkt pv bs) =
    object
      [ key "symencipd" .=
        object [key "version" .= pv, key "data" .= BL.unpack bs]
      ]
  toJSON (ModificationDetectionCodePkt bs) =
    object [key "mdc" .= BL.unpack bs]
  toJSON (OtherPacketPkt t bs) =
    object
      [ key "otherpacket" .=
        object [key "tag" .= t, key "data" .= BL.unpack bs]
      ]
  toJSON (BrokenPacketPkt s t bs) =
    object
      [ key "brokenpacket" .=
        object
          [ key "error" .= s
          , key "tag" .= t
          , key "data" .= BL.unpack bs
          ]
      ]

pktTag :: Pkt -> Word8
pktTag PKESKPkt {} = 1
pktTag (SignaturePkt _) = 2
pktTag SKESKPkt {} = 3
pktTag OnePassSignaturePkt {} = 4
pktTag SecretKeyPkt {} = 5
pktTag (PublicKeyPkt _) = 6
pktTag SecretSubkeyPkt {} = 7
pktTag CompressedDataPkt {} = 8
pktTag (SymEncDataPkt _) = 9
pktTag (MarkerPkt _) = 10
pktTag LiteralDataPkt {} = 11
pktTag (TrustPkt _) = 12
pktTag (UserIdPkt _) = 13
pktTag (PublicSubkeyPkt _) = 14
pktTag (UserAttributePkt _) = 17
pktTag SymEncIntegrityProtectedDataPkt {} = 18
pktTag (ModificationDetectionCodePkt _) = 19
pktTag (OtherPacketPkt t _) = t
pktTag (BrokenPacketPkt _ t _) = t -- is this the right thing to do?

data Verification =
  Verification
    { _verificationSigner :: PKPayload
    , _verificationSignature :: SignaturePayload
    }

data SOPVVerification =
  SOPVVerification
    { _sopvvDateStamp :: UTCTime
    , _sopvvFingerprint :: TwentyOctetFingerprint
    , _sopvvPrimaryFingerprint :: TwentyOctetFingerprint
    , _sopvvMode :: String
    , _sopvvDescription :: String
    }

$(makeLenses ''Verification)
$(makeLenses ''SOPVVerification)
