{-# LANGUAGE GeneralizedNewtypeDeriving, BangPatterns #-}

module Graphics.XHB.Shared where

-- MAY NOT import any gnerated files

import Data.Typeable

import Data.Binary.Put
import Data.Binary.Get

import Data.Word
import Data.Int
import Data.Bits
import Data.Maybe

import Control.Monad
import Control.Exception
import Data.Function

import Data.List as L

import Foreign.C.Types (CChar, CFloat, CDouble)
import Foreign.C.String

import qualified Data.ByteString.Lazy as BS
import Data.ByteString.Lazy (ByteString)

import Control.Concurrent.STM

import System.ByteOrder

-- crazy imports for put/get storable
import qualified Data.ByteString.Internal as Strict
import Foreign.Storable
import Foreign.Ptr
import Foreign.ForeignPtr

import System.IO.Unsafe ( unsafePerformIO )

byteOrderToNum :: ByteOrder -> Int
byteOrderToNum BigEndian = fromEnum '\o102' -- B
byteOrderToNum LittleEndian = fromEnum '\o154' -- l
byteOrderToNum Mixed{} = error "Mixed endian platforms not supported."

newtype Xid = MkXid Word32
 deriving (Eq, Ord, Serialize, Deserialize)

instance Show Xid where
    show (MkXid x) = show x

class XidLike a where
    fromXid :: Xid -> a
    toXid   :: a -> Xid

instance XidLike Xid where
    fromXid = id
    toXid   = id

instance XidLike Word32 where
    fromXid (MkXid w) = w
    toXid = MkXid

xidNone :: Xid
xidNone = MkXid 0

-- Enums and ValueParams

class SimpleEnum a where
    toValue :: (Eq n, Num n) => a -> n
    fromValue :: (Eq n, Num n) => n -> a

class BitEnum a where
    toBit :: a -> Int
    fromBit :: Int -> a

instance BitEnum Integer where
    toBit = fromIntegral
    fromBit = fromIntegral

fromMask :: (Bits b, BitEnum e) => b -> [e]
fromMask x = mapMaybe go [0..(bitSize x) - 1]
    where go i | x `testBit` i = return $ fromBit i
               | otherwise = Nothing

toMask :: (Bits b, Num b, BitEnum e) => [e] -> b
toMask = foldl' (.|.) 0 . map (bit . toBit)


data ValueParam a = VP a [Word32]

toValueParam :: (Bits a, Num a, BitEnum e) => [(e,Word32)] -> ValueParam a
toValueParam xs = 
    let (es,ws) = unzip $ L.sortBy (compare `on` toBit . fst) xs
    in VP (toMask es) ws

fromValueParam :: (Bits a, BitEnum e) => ValueParam a -> [(e,Word32)]
fromValueParam (VP x ws) =
    let es = fromMask x
    in assert (length es == length ws) $ zip es ws

emptyValueParam :: Num a => ValueParam a
emptyValueParam = VP 0 []

instance (Bits a, Show a) => Show (ValueParam a) where
    show v = show (fromValueParam v :: [(Integer,Word32)])

stringToCList :: String -> [CChar]
stringToCList = map castCharToCChar


class Serialize a where
    serialize :: a -> Put
    size :: a -> Int -- Size in bytes

class Deserialize a where
    deserialize :: Get a

class ExtensionRequest a where
    serializeRequest :: a -> RequestOpCode -> Put
    extensionId :: a -> ExtensionId

type RequestOpCode = Word8
type ExtensionId = String -- limited to ASCII
  

-- In units of four bytes
type ReplyLength = Word32

-- The Receipt type allows the sender of the request
-- to arbitrarily munge the result before handing
-- it back to the caller
newtype Receipt a = MkReceipt (TVar (InnerReceipt a))

type RawReceipt = TMVar (Either SomeError ByteString)

data InnerReceipt a
    = Item (Either SomeError a)
    | Result RawReceipt (ByteString -> a)

newEmptyReceipt :: (ByteString -> a) -> IO (Receipt a, RawReceipt)
newEmptyReceipt f = do
  rawReceipt <- newEmptyTMVarIO
  ref <- newTVarIO $ Result rawReceipt f
  return $ (MkReceipt ref, rawReceipt)

newDeserReceipt :: Deserialize a => IO (Receipt a, RawReceipt)
newDeserReceipt = newEmptyReceipt $ runGet deserialize

putReceipt :: RawReceipt -> Either SomeError ByteString -> STM ()
putReceipt = putTMVar

-- | Extracts a reply from the receipt from the request.
-- Blocks until the reply is available.
getReply :: Receipt a -> IO (Either SomeError a)
getReply (MkReceipt ref) = atomically $
    readTVar ref >>= \ircpt -> case ircpt of
       Item a -> return a
       Result rrcpt f -> do
         res <- takeTMVar rrcpt
         let ret = either Left (Right . f) res
         writeTVar ref $ Item ret
         return ret

-- Because new errors and events are introduced with each extension,
-- I don't want to give the users of this library pattern-match
-- error every time a new extension is added.

class (Typeable a, Show a) => Error a where
    fromError :: SomeError -> Maybe a
    toError :: a -> SomeError

    fromError (SomeError e) = cast e
    toError = SomeError

data SomeError = forall a . Error a => SomeError a

instance Show SomeError where
    show se = case se of
          SomeError err -> show err

data UnknownError = UnknownError BS.ByteString deriving (Typeable, Show)
instance Error UnknownError



class Typeable a => Event a where
    fromEvent :: SomeEvent -> Maybe a
    toEvent :: a -> SomeEvent

    fromEvent (SomeEvent e) = cast e
    toEvent = SomeEvent

data SomeEvent = forall a . Event a => SomeEvent a

data UnknownEvent = UnknownEvent BS.ByteString deriving (Typeable)
instance Event UnknownEvent





deserializeList :: Deserialize a => Int -> Get [a]
deserializeList n = go n
    where go 0 = return []
          go n = do
            x <- deserialize
            xs <- go (n-1)
            return $ x : xs

serializeList :: Serialize a => [a] -> Put
serializeList = mapM_ serialize

convertBytesToRequestSize n =
    fromIntegral $ case quotRem n 4 of
      (d,0) -> d
      (d,r) -> d + 1

requiredPadding n = 
    fromIntegral $ case quotRem n 4 of
      (_,0) -> 0
      (_,r) -> 4 - r

--Instances


instance Serialize Bool where
    serialize = serialize `fmap` boolToWord
    size = size . boolToWord

instance Deserialize Bool where
    deserialize = wordToBool `fmap` deserialize

boolToWord :: Bool -> Word8
wordToBool :: Word8 -> Bool

boolToWord True = 1
boolToWord False = 0

wordToBool 0 = False
wordToBool _ = True


-- Words
instance Serialize Word8 where
    serialize = putWord8
    size _ = 1

instance Deserialize Word8 where
    deserialize = getWord8

instance Serialize Word16 where
    serialize = putWord16host
    size _ = 2

instance Deserialize Word16 where
    deserialize = getWord16host


instance Serialize Word32 where
    serialize = putWord32host
    size _ = 4

instance Deserialize Word32 where
    deserialize = getWord32host

-- Ints
instance Serialize Int8 where
    serialize = putInt8
    size _ = 1

instance Deserialize Int8 where
    deserialize = getInt8


instance Serialize Int16 where
    serialize = putInt16host
    size _ = 2

instance Deserialize Int16 where
    deserialize = getInt16host


instance Serialize Int32 where
    serialize = putInt32host
    size _ = 4

instance Deserialize Int32 where
    deserialize = getInt32host


instance Serialize CChar where
    serialize = putWord8 . fromIntegral -- assumes a CChar is one word
    size _ = 1

instance Deserialize CChar where
    deserialize = liftM fromIntegral getWord8



-- Binary.Missing

-- All of this relies on being able to roundtrip:
-- (IntN -> WordN) and (WordN -> IntN) using 'fromIntegral'

putInt8 :: Int8 -> Put
putInt8 = putWord8 . fromIntegral

getInt8 :: Get Int8
getInt8 = liftM fromIntegral getWord8

putInt16host :: Int16 -> Put
putInt16host = putWord16host . fromIntegral

getInt16host :: Get Int16
getInt16host = liftM fromIntegral getWord16host

putInt32host :: Int32 -> Put
putInt32host = putWord32host . fromIntegral

getInt32host :: Get Int32
getInt32host = liftM fromIntegral getWord32host

-- Fun stuff

-- I've no idea if this is what the other end expects
instance Deserialize CFloat where
    deserialize = getStorable

instance Serialize CFloat where
    size x = sizeOf x
    serialize = putStorable

instance Deserialize CDouble where
    deserialize = getStorable


getStorable :: Storable a => Get a
getStorable = (\dummy -> do
       let n = sizeOf dummy
       bytes <- getBytes n
       return $ storableFromBS bytes `asTypeOf` dummy
              ) undefined  

putStorable :: Storable a => a -> Put
putStorable = putByteString . bsFromStorable

storableFromBS (Strict.PS fptr len off) = 
    unsafePerformIO $ withForeignPtr fptr $ flip peekElemOff off . castPtr

bsFromStorable x = Strict.unsafeCreate (sizeOf x) $ \p -> do
                     poke (castPtr p) x

-- Other

instance (Serialize a, Bits a) => Serialize (ValueParam a) where
    serialize = serializeValueParam 0
    size (VP mask xs) = size mask + sum (map size xs)

-- there's one value param which needs funny padding, so it
-- uses the special function
serializeValueParam :: (Serialize a, Bits a) =>
                       Int -> ValueParam a -> Put
serializeValueParam pad (VP mask xs) = do
  serialize mask
  putSkip pad
  assert (length xs == setBits mask) $ return ()
  serializeList xs
  

instance (Deserialize a, Bits a) => Deserialize (ValueParam a) where
    deserialize = deserializeValueParam 0

deserializeValueParam :: (Deserialize a, Bits a) =>
                         Int -> Get (ValueParam a)
deserializeValueParam pad = do
  mask <- deserialize
  skip pad
  let n = setBits mask
  xs <- deserializeList n
  return $ VP mask xs

-- |Returns the number of bits set in the passed-in
-- bitmask.
setBits :: Bits a => a -> Int
setBits a = foldl' go 0 [0 .. (bitSize a) - 1]
    where go !n bit | a `testBit` bit = n + 1
                    | otherwise = n


putSkip :: Int -> Put
putSkip 0 = return ()
putSkip n = replicateM_ n $ putWord8 0

isCard32 :: Word32 -> a
isCard32 = undefined