{-# LANGUAGE DeriveDataTypeable#-}

module Codec.Archive.LibZip.Types
    ( Zip
    , ZipFile
    , ZipSource
    , ZipStat(..)
    , toZipStat
    , OpenFlag(..)
    , FileFlag(..)
    , ArchiveFlag(..)
    , CodecFlag(..)
    , ZipError(..)
    , ZipCompMethod(..)
    , ZipEncryptionMethod(..)
    , combine
    ) where
    
import Data.Bits ((.|.))
import Data.Time (UTCTime)
import Data.Time.Clock.POSIX (posixSecondsToUTCTime)
import Data.Typeable (Typeable)
import Data.Word (Word, Word32, Word64)
import Foreign.C.String (peekCString)
import Foreign.C.Types ()
import Foreign.Ptr (Ptr, nullPtr)
import qualified Control.Exception as E

import Bindings.LibZip

-- | Handler of the open zip file.
type Zip = Ptr C'zip

-- | Handler of an open file in the zip archive.
type ZipFile = Ptr C'zip_file

-- | Handler of data source for new files in the zip archive.
type ZipSource = Ptr C'zip_source

-- |  File statistics expressed in native Haskell types.
data ZipStat = ZipStat {
      zs'valid :: Word64
    , zs'name :: String
    , zs'index :: Integer
    , zs'size :: Integer
    , zs'comp_size :: Integer
    , zs'mtime :: UTCTime
    , zs'crc :: Word
    , zs'comp_method :: ZipCompMethod
    , zs'encryption_method :: ZipEncryptionMethod
    , zs'flags :: Word32
    } deriving (Show, Eq)

-- | Convert marshalled stat record.
toZipStat :: C'zip_stat -> IO ZipStat
toZipStat s = do
    let valid = fromIntegral $ c'zip_stat'valid s
    let np = c'zip_stat'name s
    name <- if (np /= nullPtr) then peekCString np else return ""
    let idx = fromIntegral $ c'zip_stat'index s
    let crc = fromIntegral $ c'zip_stat'crc s
    let mtime = posixSecondsToUTCTime . realToFrac $ c'zip_stat'mtime s
    let size = fromIntegral $ c'zip_stat'size s
    let comp_size = fromIntegral $ c'zip_stat'comp_size s
    let comp_meth = toEnum . fromIntegral $ c'zip_stat'comp_method s
    let enc_meth = toEnum . fromIntegral $ c'zip_stat'encryption_method s
    let flags =  toEnum . fromIntegral $ c'zip_stat'flags s
    return $ ZipStat valid name idx size comp_size mtime crc
             comp_meth enc_meth flags
   

-- | Flags for opening an archive.
data OpenFlag
  = CreateFlag      -- ^ Create an archive if it does not exist.
  | ExclFlag        -- ^ Error if the archive already exists.
  | CheckConsFlag   -- ^ Check archive's consistency and error on failure.
  deriving (Show,Eq)

instance Enum OpenFlag where
  fromEnum CheckConsFlag = c'ZIP_CHECKCONS
  fromEnum CreateFlag = c'ZIP_CREATE
  fromEnum ExclFlag = c'ZIP_EXCL
  toEnum x | x == c'ZIP_CHECKCONS = CheckConsFlag
  toEnum x | x == c'ZIP_CREATE = CreateFlag
  toEnum x | x == c'ZIP_EXCL = ExclFlag
  toEnum _ = undefined

-- | Flags for accessing files in the archive.
-- Please consult @libzip@ documentation about their use.
data FileFlag
  = FileNOCASE      -- ^ Ignore case on name lookup.
  | FileNODIR       -- ^ Ignore directory component.
  | FileCOMPRESSED  -- ^ Read the compressed data.
  | FileUNCHANGED   -- ^ Read the original data, ignore changes.
  | FileRECOMPRESS  -- ^ Force recompression of data.
  | FileENCRYPTED   -- ^ Read encrypted data (implies FileCOMPRESSED).
  deriving (Show,Eq)

instance Enum FileFlag where
  fromEnum FileCOMPRESSED = c'ZIP_FL_COMPRESSED
  fromEnum FileNOCASE = c'ZIP_FL_NOCASE
  fromEnum FileNODIR = c'ZIP_FL_NODIR
  fromEnum FileRECOMPRESS = c'ZIP_FL_RECOMPRESS
  fromEnum FileUNCHANGED = c'ZIP_FL_UNCHANGED
  fromEnum FileENCRYPTED = c'ZIP_FL_ENCRYPTED
  toEnum x | x == c'ZIP_FL_COMPRESSED = FileCOMPRESSED
  toEnum x | x == c'ZIP_FL_NOCASE = FileNOCASE
  toEnum x | x == c'ZIP_FL_NODIR = FileNODIR
  toEnum x | x == c'ZIP_FL_RECOMPRESS = FileRECOMPRESS
  toEnum x | x == c'ZIP_FL_UNCHANGED = FileUNCHANGED
  toEnum x | x == c'ZIP_FL_ENCRYPTED = FileENCRYPTED
  toEnum _ = undefined


-- | @libzip@ archive global flags
data ArchiveFlag
    = ArchiveTORRENT
    | ArchiveRDONLY
    deriving (Show, Eq)

instance Enum ArchiveFlag where
    fromEnum ArchiveTORRENT = c'ZIP_AFL_TORRENT
    fromEnum ArchiveRDONLY = c'ZIP_AFL_RDONLY
    toEnum x | x == c'ZIP_AFL_TORRENT = ArchiveTORRENT
    toEnum x | x == c'ZIP_AFL_RDONLY = ArchiveRDONLY
    toEnum _ = undefined


-- | @libzip@ flags for compression and encryption sources
data CodecFlag = CodecENCODE deriving (Show, Eq)

instance Enum CodecFlag where
    fromEnum CodecENCODE = c'ZIP_CODEC_ENCODE
    toEnum x | x == c'ZIP_CODEC_ENCODE = CodecENCODE
    toEnum _ = undefined


-- | @libzip@ error codes.
data ZipError
  = ErrOK             -- ^ No error.
  | ErrMULTIDISK      -- ^ Multi-disk zip archives not supported.
  | ErrRENAME         -- ^ Renaming temporary file failed.
  | ErrCLOSE          -- ^ Closing zip archive failed.
  | ErrSEEK           -- ^ Seek error.
  | ErrREAD           -- ^ Read error.
  | ErrWRITE          -- ^ Write error.
  | ErrCRC            -- ^ CRC error.
  | ErrZIPCLOSED      -- ^ Containing zip archive was closed.
  | ErrNOENT          -- ^ No such file.
  | ErrEXISTS         -- ^ File already exists.
  | ErrOPEN           -- ^ Can't open file.
  | ErrTMPOPEN        -- ^ Failure to create temporary file.
  | ErrZLIB           -- ^ Zlib error.
  | ErrMEMORY         -- ^ Malloc error.
  | ErrCHANGED        -- ^ Entry has been changed.
  | ErrCOMPNOTSUPP    -- ^ Compression method not supported.
  | ErrEOF            -- ^ Premature EOF.
  | ErrINVAL          -- ^ Invalid argument.
  | ErrNOZIP          -- ^ Not a zip archive.
  | ErrINTERNAL       -- ^ Internal error.
  | ErrINCONS         -- ^ Zip archive inconsistent.
  | ErrREMOVE         -- ^ Can't remove file.
  | ErrDELETED        -- ^ Entry has been deleted.
  | ErrENCRNOTSUPP    -- ^ Encryption method not supported.
  | ErrRDONLY         -- ^ Read-only archive.
  | ErrNOPASSWD       -- ^ No password provided.
  | ErrWRONGPASSWD    -- ^ Wrong password provided.
  deriving (Eq, Typeable)

instance Enum ZipError where
  fromEnum ErrCHANGED = c'ZIP_ER_CHANGED
  fromEnum ErrCLOSE = c'ZIP_ER_CLOSE
  fromEnum ErrCOMPNOTSUPP = c'ZIP_ER_COMPNOTSUPP
  fromEnum ErrCRC = c'ZIP_ER_CRC
  fromEnum ErrDELETED = c'ZIP_ER_DELETED
  fromEnum ErrEOF = c'ZIP_ER_EOF
  fromEnum ErrEXISTS = c'ZIP_ER_EXISTS
  fromEnum ErrINCONS = c'ZIP_ER_INCONS
  fromEnum ErrINTERNAL = c'ZIP_ER_INTERNAL
  fromEnum ErrINVAL = c'ZIP_ER_INVAL
  fromEnum ErrMEMORY = c'ZIP_ER_MEMORY
  fromEnum ErrMULTIDISK = c'ZIP_ER_MULTIDISK
  fromEnum ErrNOENT = c'ZIP_ER_NOENT
  fromEnum ErrNOZIP = c'ZIP_ER_NOZIP
  fromEnum ErrOK = c'ZIP_ER_OK
  fromEnum ErrOPEN = c'ZIP_ER_OPEN
  fromEnum ErrREAD = c'ZIP_ER_READ
  fromEnum ErrREMOVE = c'ZIP_ER_REMOVE
  fromEnum ErrRENAME = c'ZIP_ER_RENAME
  fromEnum ErrSEEK = c'ZIP_ER_SEEK
  fromEnum ErrTMPOPEN = c'ZIP_ER_TMPOPEN
  fromEnum ErrWRITE = c'ZIP_ER_WRITE
  fromEnum ErrZIPCLOSED = c'ZIP_ER_ZIPCLOSED
  fromEnum ErrZLIB = c'ZIP_ER_ZLIB
  fromEnum ErrENCRNOTSUPP = c'ZIP_ER_ENCRNOTSUPP
  fromEnum ErrRDONLY = c'ZIP_ER_RDONLY
  fromEnum ErrNOPASSWD = c'ZIP_ER_NOPASSWD
  fromEnum ErrWRONGPASSWD = c'ZIP_ER_WRONGPASSWD
  toEnum x | x == c'ZIP_ER_CHANGED = ErrCHANGED
  toEnum x | x == c'ZIP_ER_CLOSE = ErrCLOSE
  toEnum x | x == c'ZIP_ER_COMPNOTSUPP = ErrCOMPNOTSUPP
  toEnum x | x == c'ZIP_ER_CRC = ErrCRC
  toEnum x | x == c'ZIP_ER_DELETED = ErrDELETED
  toEnum x | x == c'ZIP_ER_EOF = ErrEOF
  toEnum x | x == c'ZIP_ER_EXISTS = ErrEXISTS
  toEnum x | x == c'ZIP_ER_INCONS = ErrINCONS
  toEnum x | x == c'ZIP_ER_INTERNAL = ErrINTERNAL
  toEnum x | x == c'ZIP_ER_INVAL = ErrINVAL
  toEnum x | x == c'ZIP_ER_MEMORY = ErrMEMORY
  toEnum x | x == c'ZIP_ER_MULTIDISK = ErrMULTIDISK
  toEnum x | x == c'ZIP_ER_NOENT = ErrNOENT
  toEnum x | x == c'ZIP_ER_NOZIP = ErrNOZIP
  toEnum x | x == c'ZIP_ER_OK = ErrOK
  toEnum x | x == c'ZIP_ER_OPEN = ErrOPEN
  toEnum x | x == c'ZIP_ER_READ = ErrREAD
  toEnum x | x == c'ZIP_ER_REMOVE = ErrREMOVE
  toEnum x | x == c'ZIP_ER_RENAME = ErrRENAME
  toEnum x | x == c'ZIP_ER_SEEK = ErrSEEK
  toEnum x | x == c'ZIP_ER_TMPOPEN = ErrTMPOPEN
  toEnum x | x == c'ZIP_ER_WRITE = ErrWRITE
  toEnum x | x == c'ZIP_ER_ZIPCLOSED = ErrZIPCLOSED
  toEnum x | x == c'ZIP_ER_ZLIB = ErrZLIB
  toEnum x | x == c'ZIP_ER_ENCRNOTSUPP = ErrENCRNOTSUPP
  toEnum x | x == c'ZIP_ER_RDONLY = ErrRDONLY
  toEnum x | x == c'ZIP_ER_NOPASSWD = ErrNOPASSWD
  toEnum x | x == c'ZIP_ER_WRONGPASSWD = ErrWRONGPASSWD
  toEnum _ = undefined

instance E.Exception ZipError

instance Show ZipError where
  show ErrOK             =  "No error"
  show ErrMULTIDISK      =  "Multi-disk zip archives not supported"
  show ErrRENAME         =  "Renaming temporary file failed"
  show ErrCLOSE          =  "Closing zip archive failed"
  show ErrSEEK           =  "Seek error"
  show ErrREAD           =  "Read error"
  show ErrWRITE          =  "Write error"
  show ErrCRC            =  "CRC error"
  show ErrZIPCLOSED      =  "Containing zip archive was closed"
  show ErrNOENT          =  "No such file"
  show ErrEXISTS         =  "File already exists"
  show ErrOPEN           =  "Can't open file"
  show ErrTMPOPEN        =  "Failure to create temporary file"
  show ErrZLIB           =  "Zlib error"
  show ErrMEMORY         =  "Malloc failure"
  show ErrCHANGED        =  "Entry has been changed"
  show ErrCOMPNOTSUPP    =  "Compression method not supported"
  show ErrEOF            =  "Premature EOF"
  show ErrINVAL          =  "Invalid argument"
  show ErrNOZIP          =  "Not a zip archive"
  show ErrINTERNAL       =  "Internal error"
  show ErrINCONS         =  "Zip archive inconsistent"
  show ErrREMOVE         =  "Can't remove file"
  show ErrDELETED        =  "Entry has been deleted"
  show ErrENCRNOTSUPP    =  "Encryption method not supported"
  show ErrRDONLY         =  "Read-only archive" 
  show ErrNOPASSWD       =  "No password provided"
  show ErrWRONGPASSWD    =  "Wrong password provided"

-- | Compression methods.
data ZipCompMethod
  = CompDEFAULT         -- ^ Better of deflate or store.
  | CompSTORE           -- ^ Stored (uncompressed).
  | CompSHRINK          -- ^ Shrunk.
  | CompREDUCE_1        -- ^ Reduced with factor 1
  | CompREDUCE_2        -- ^ Reduced with factor 2
  | CompREDUCE_3        -- ^ Reduced with factor 3
  | CompREDUCE_4        -- ^ Reduced with factor 4
  | CompIMPLODE         -- ^ Imploded.
  | CompDEFLATE         -- ^ Deflated.
  | CompDEFLATE64       -- ^ Deflate64.
  | CompPKWARE_IMPLODE  -- ^ PKWARE imploding.
  | CompBZIP2           -- ^ Compressed using BZIP2 algorithm.
  | CompLZMA            -- ^ LZMA (EFS)
  | CompTERSE           -- ^ Compressed using IBM TERSE (new).
  | CompLZ77            -- ^ IBM LZ77 z Architecture (PFS).
  | CompWAVPACK         -- ^ WavPack compressed data.
  | CompPPMD            -- ^ PPMd version I, Rev 1.
  deriving (Show, Eq)

instance Enum ZipCompMethod where
  fromEnum CompDEFAULT = c'ZIP_CM_DEFAULT
  fromEnum CompSTORE = c'ZIP_CM_STORE
  fromEnum CompSHRINK = c'ZIP_CM_SHRINK
  fromEnum CompREDUCE_1 = c'ZIP_CM_REDUCE_1
  fromEnum CompREDUCE_2 = c'ZIP_CM_REDUCE_2
  fromEnum CompREDUCE_3 = c'ZIP_CM_REDUCE_3
  fromEnum CompREDUCE_4 = c'ZIP_CM_REDUCE_4
  fromEnum CompIMPLODE = c'ZIP_CM_IMPLODE
  fromEnum CompDEFLATE = c'ZIP_CM_DEFLATE
  fromEnum CompDEFLATE64 = c'ZIP_CM_DEFLATE64
  fromEnum CompPKWARE_IMPLODE = c'ZIP_CM_PKWARE_IMPLODE
  fromEnum CompBZIP2 = c'ZIP_CM_BZIP2
  fromEnum CompLZMA = c'ZIP_CM_LZMA
  fromEnum CompTERSE = c'ZIP_CM_TERSE
  fromEnum CompLZ77 = c'ZIP_CM_LZ77
  fromEnum CompWAVPACK = c'ZIP_CM_WAVPACK
  fromEnum CompPPMD = c'ZIP_CM_PPMD
  toEnum x | x == c'ZIP_CM_DEFAULT = CompDEFAULT
  toEnum x | x == c'ZIP_CM_STORE = CompSTORE
  toEnum x | x == c'ZIP_CM_SHRINK = CompSHRINK
  toEnum x | x == c'ZIP_CM_REDUCE_1 = CompREDUCE_1
  toEnum x | x == c'ZIP_CM_REDUCE_2 = CompREDUCE_2
  toEnum x | x == c'ZIP_CM_REDUCE_3 = CompREDUCE_3
  toEnum x | x == c'ZIP_CM_REDUCE_4 = CompREDUCE_4
  toEnum x | x == c'ZIP_CM_IMPLODE = CompIMPLODE
  toEnum x | x == c'ZIP_CM_DEFLATE = CompDEFLATE
  toEnum x | x == c'ZIP_CM_DEFLATE64 = CompDEFLATE64
  toEnum x | x == c'ZIP_CM_PKWARE_IMPLODE = CompPKWARE_IMPLODE
  toEnum x | x == c'ZIP_CM_BZIP2 = CompBZIP2
  toEnum x | x == c'ZIP_CM_LZMA = CompLZMA
  toEnum x | x == c'ZIP_CM_TERSE = CompTERSE
  toEnum x | x == c'ZIP_CM_LZ77 = CompLZ77
  toEnum x | x == c'ZIP_CM_WAVPACK = CompWAVPACK
  toEnum x | x == c'ZIP_CM_PPMD = CompPPMD
  toEnum _ = undefined

-- | Encryption methods.
data ZipEncryptionMethod
  = EncryptNONE          -- ^ Not encrypted.
  | EncryptTRAD_PKWARE   -- ^ Traditional PKWARE encryption.
  | EncryptUNKNOWN       -- ^ Unknown algorithm.
  deriving (Show,Eq)

instance Enum ZipEncryptionMethod where
  fromEnum EncryptNONE = c'ZIP_EM_NONE
  fromEnum EncryptTRAD_PKWARE = c'ZIP_EM_TRAD_PKWARE
  fromEnum EncryptUNKNOWN = c'ZIP_EM_UNKNOWN
  toEnum x | x == c'ZIP_EM_NONE = EncryptNONE
  toEnum x | x == c'ZIP_EM_TRAD_PKWARE = EncryptTRAD_PKWARE
  toEnum x | x == c'ZIP_EM_UNKNOWN = EncryptUNKNOWN
  toEnum _ = undefined

combine :: (Enum a, Num b) => [a] -> b
combine fs = fromIntegral . foldr (.|.) 0 $ map fromEnum fs

