{-# LANGUAGE CPP #-}
{-# OPTIONS_GHC -funbox-strict-fields #-}

{-|
Module      : Database.MySQL.Protocol.ColumnDef
Description : MySQL field type
Copyright   : (c) Winterland, 2016
License     : BSD
Maintainer  : drkoster@qq.com
Stability   : experimental
Portability : PORTABLE

Column definition(aka. field type).

-}

module Database.MySQL.Protocol.ColumnDef where

import           Control.Applicative
import           Data.Binary
import           Data.Binary.Get
import           Data.Binary.Parser
import           Data.Binary.Put
import           Data.Bits                      ((.&.))
import           Data.ByteString                (ByteString)
import           Database.MySQL.Protocol.Packet

--------------------------------------------------------------------------------
--  Resultset

-- | A description of a field (column) of a table.
data ColumnDef = ColumnDef
    { -- fieldCatalog :: !ByteString              -- ^ const 'def'
      columnDB        ::  !ByteString             -- ^ Database for table.
    , columnTable     ::  !ByteString             -- ^ Table of column, if column was a field.
    , columnOrigTable ::  !ByteString             -- ^ Original table name, if table was an alias.
    , columnName      ::  !ByteString             -- ^ Name of column.
    , columnOrigName  ::  !ByteString             -- ^ Original column name, if an alias.
    , columnCharSet   ::  !Word16                 -- ^ Character set number.
    , columnLength    ::  !Word32                 -- ^ Width of column (create length).
    , columnType      ::  !FieldType
    , columnFlags     ::  !Word16                 -- ^ Div flags.
    , columnDecimals  ::  !Word8                  -- ^ Number of decimals in field.
    } deriving (Show, Eq)

getField :: Get ColumnDef
getField = ColumnDef
        <$> (skipN 4                 -- const "def"
         *> getLenEncBytes)         -- db
        <*> getLenEncBytes          -- table
        <*> getLenEncBytes          -- origTable
        <*> getLenEncBytes          -- name
        <*> getLenEncBytes          -- origName
        <*  skipN 1                  -- const 0x0c
        <*> getWord16le             -- charset
        <*> getWord32le             -- length
        <*> getFieldType            -- type
        <*> getWord16le             -- flags
        <*> getWord8                -- decimals
        <* skipN 2                   -- const 0x00 0x00
{-# INLINE getField #-}

putField :: ColumnDef -> Put
putField (ColumnDef db tbl otbl name oname charset len typ flags dec) = do
    putLenEncBytes "def"
    putLenEncBytes db
    putLenEncBytes tbl
    putLenEncBytes otbl
    putLenEncBytes name
    putLenEncBytes oname
    putWord16le charset
    putWord32le len
    putFieldType typ
    putWord16le  flags
    putWord8 dec
    putWord16le 0X0000
{-# INLINE putField #-}

instance Binary ColumnDef where
    get = getField
    {-# INLINE get #-}
    put = putField
    {-# INLINE put #-}

-- | @newtype@ around 'Word8' for represent @MySQL_TYPE@, We don't use sum type here for speed reason.
--
newtype FieldType = FieldType Word8 deriving (Show, Eq)

mySQLTypeDecimal, mySQLTypeTiny, mySQLTypeShort, mySQLTypeLong, mySQLTypeFloat :: FieldType
mySQLTypeDouble, mySQLTypeNull, mySQLTypeTimestamp, mySQLTypeLongLong, mySQLTypeInt24 :: FieldType
mySQLTypeDate, mySQLTypeTime, mySQLTypeDateTime, mySQLTypeYear, mySQLTypeNewDate, mySQLTypeVarChar :: FieldType
mySQLTypeBit, mySQLTypeTimestamp2, mySQLTypeDateTime2, mySQLTypeTime2, mySQLTypeNewDecimal :: FieldType
mySQLTypeEnum, mySQLTypeSet, mySQLTypeTinyBlob, mySQLTypeMediumBlob, mySQLTypeLongBlob :: FieldType
mySQLTypeBlob, mySQLTypeVarString, mySQLTypeString, mySQLTypeGeometry :: FieldType

mySQLTypeDecimal        = FieldType 0x00
mySQLTypeTiny           = FieldType 0x01
mySQLTypeShort          = FieldType 0x02
mySQLTypeLong           = FieldType 0x03
mySQLTypeFloat          = FieldType 0x04
mySQLTypeDouble         = FieldType 0x05
mySQLTypeNull           = FieldType 0x06
mySQLTypeTimestamp      = FieldType 0x07
mySQLTypeLongLong       = FieldType 0x08
mySQLTypeInt24          = FieldType 0x09
mySQLTypeDate           = FieldType 0x0a
mySQLTypeTime           = FieldType 0x0b
mySQLTypeDateTime       = FieldType 0x0c
mySQLTypeYear           = FieldType 0x0d
mySQLTypeNewDate        = FieldType 0x0e
mySQLTypeVarChar        = FieldType 0x0f
mySQLTypeBit            = FieldType 0x10
mySQLTypeTimestamp2     = FieldType 0x11
mySQLTypeDateTime2      = FieldType 0x12
mySQLTypeTime2          = FieldType 0x13
mySQLTypeNewDecimal     = FieldType 0xf6
mySQLTypeEnum           = FieldType 0xf7
mySQLTypeSet            = FieldType 0xf8
mySQLTypeTinyBlob       = FieldType 0xf9
mySQLTypeMediumBlob     = FieldType 0xfa
mySQLTypeLongBlob       = FieldType 0xfb
mySQLTypeBlob           = FieldType 0xfc
mySQLTypeVarString      = FieldType 0xfd
mySQLTypeString         = FieldType 0xfe
mySQLTypeGeometry       = FieldType 0xff

getFieldType :: Get FieldType
getFieldType = FieldType <$> getWord8
{-# INLINE getFieldType #-}

putFieldType :: FieldType -> Put
putFieldType (FieldType t) = putWord8 t
{-# INLINE putFieldType #-}

instance Binary FieldType where
    get = getFieldType
    {-# INLINE get #-}
    put = putFieldType
    {-# INLINE put #-}

--------------------------------------------------------------------------------
--  Field flags

#define NOT_NULL_FLAG         1
#define PRI_KEY_FLAG          2
#define UNIQUE_KEY_FLAG       4
#define MULT_KEY_FLAG         8
#define BLOB_FLAG             16
#define UNSIGNED_FLAG         32
#define ZEROFILL_FLAG         64
#define BINARY_FLAG           128
#define ENUM_FLAG             256
#define AUTO_INCREMENT_FLAG   512
#define TIMESTAMP_FLAG        1024
#define SET_FLAG              2048
#define NO_DEFAULT_VALUE_FLAG 4096
#define PART_KEY_FLAG         16384
#define NUM_FLAG              32768

flagNotNull, flagPrimaryKey, flagUniqueKey, flagMultipleKey, flagBlob, flagUnsigned, flagZeroFill :: Word16 -> Bool
flagBinary, flagEnum, flagAutoIncrement, flagTimeStamp, flagSet, flagNoDefaultValue, flagPartKey, flagNumeric :: Word16 -> Bool
flagNotNull        flags = flags .&. NOT_NULL_FLAG         == NOT_NULL_FLAG
flagPrimaryKey     flags = flags .&. PRI_KEY_FLAG          == PRI_KEY_FLAG
flagUniqueKey      flags = flags .&. UNIQUE_KEY_FLAG       == UNIQUE_KEY_FLAG
flagMultipleKey    flags = flags .&. MULT_KEY_FLAG         == MULT_KEY_FLAG
flagBlob           flags = flags .&. BLOB_FLAG             == BLOB_FLAG
flagUnsigned       flags = flags .&. UNSIGNED_FLAG         == UNSIGNED_FLAG
flagZeroFill       flags = flags .&. ZEROFILL_FLAG         == ZEROFILL_FLAG
flagBinary         flags = flags .&. BINARY_FLAG           == BINARY_FLAG
flagEnum           flags = flags .&. ENUM_FLAG             == ENUM_FLAG
flagAutoIncrement  flags = flags .&. AUTO_INCREMENT_FLAG   == AUTO_INCREMENT_FLAG
flagTimeStamp      flags = flags .&. TIMESTAMP_FLAG        == TIMESTAMP_FLAG
flagSet            flags = flags .&. SET_FLAG              == SET_FLAG
flagNoDefaultValue flags = flags .&. NO_DEFAULT_VALUE_FLAG == NO_DEFAULT_VALUE_FLAG
flagPartKey        flags = flags .&. PART_KEY_FLAG         == PART_KEY_FLAG
flagNumeric        flags = flags .&. NUM_FLAG              == NUM_FLAG