-- |
-- Module: Codec.Libevent.Generate
-- Copyright: Adam Langley 2007
-- License: BSD
--
-- This module generates Haskell code for serialising and deserialising
-- libevent tagged data structures, as implemented in libevent-1.4.0-beta.
--
-- A single .rpc file (containing one or more structures) is mapped to a single
-- Haskell file. Take this example:
--
-- > struct test {
-- >   required int a = 1;
-- >   optional string b = 2;
-- >   repeated struct[test2] c = 3;
-- > }
--
-- This will result in a data decl for 'Test', having named members:
-- test_a, test_b and test_c. Required elements are simple, optional
-- elements are wrapped in a Maybe and repeated elements in a list.
--
-- Types are mapped thus:
--
--    * int -> Word32
--
--    * string -> String
--
--    * bytes -> ByteString (strict)
--
--    * bytes[n] -> ByteString (with runtime checks on the size)
--
--    * struct[name] -> Name (the struct must be defined in the same file)
--
-- In the example above, @test2@ is required to be in the same .rpc file.
--
-- For a structure named @test@, the following would also be generated:
--
--   * @testEmpty@ - a Test filled with default values
--
--   * @testDeserialise@ - a strict Get instance to deserialise a test. Note
--     that these structures are self-deliminating, so additional garbage at
--     the end will be consumed and will probably result in an error
--
--   * @testDeserialiseBS@ - a function with type
--     ByteString -> Either String Test where the String is an error message
--
--   * @testSerialise@ - a Put Test function. Again, recall that these
--     structures aren't self deliminating
--
--   * @testSerialiseBS@ - a function with type Test -> ByteString
--
-- Each structure will also be an instance of the @TaggedStructure@ class
-- that you can find in "Codec.Libevent.Class"
--
module Codec.Libevent.Generate (generate) where

import System.Environment (getArgs)
import Data.Char (toUpper)
import Data.List (intersperse)
import Data.Maybe (mapMaybe)

import Text.Printf (printf)

import Codec.Libevent.Parse

-- | Generate the Haskell code for the given RPC file and write to standard
--   out. The generated module will export everything and takes the given
--   name
generate :: String  -- ^ the name of the module in the output
         -> RPCFile  -- ^ the RPC file to generate code for
         -> IO ()
generate modulename rpcfile = do
  printf "module %s where\n\n" modulename
  putStr "import Codec.Libevent\nimport Data.Word\n\n"
  putStr "import qualified Data.IntSet as IS\n\n"
  putStr "import Data.Binary.Put\nimport Data.Binary.Strict.Get\n\n"
  putStr "import qualified Data.ByteString as BS\n"
  putStr "import qualified Data.ByteString.Lazy as BSL\n\n"
  putStr "import Codec.Libevent.Class\n"

  sequence $ map generateStruct $ rpcstructs rpcfile
  putStr "\n"
  return ()

-- | Convert a struct name to a Haskell type name by uppercasing the first
--   letter
toDataName :: String -> String
toDataName (first:rest) = (toUpper first) : rest

rpcTypeToTypeString Int = "Word32"
rpcTypeToTypeString VarBytes = "BS.ByteString"
rpcTypeToTypeString String = "String"
rpcTypeToTypeString (Bytes _) = "BS.ByteString"
rpcTypeToTypeString (Struct name) = toDataName name

typeString :: Presence -> Type -> String
typeString Required x = rpcTypeToTypeString x
typeString Optional x = printf "Maybe %s" $ rpcTypeToTypeString x
typeString Repeated x = printf "[%s]" $ rpcTypeToTypeString x

generateElem :: String -> RPCElem -> String
generateElem structname elem =
  printf "%s_%s :: %s" structname (elemname elem) typestring where
  typestring = typeString (elempresence elem) (elemtype elem)

generateSerialiseElem :: String -> RPCElem -> String
generateSerialiseElem structname elem@(RPCElem { elempresence = Required }) =
  generateSerialiseElem' structname elem var where
  var :: String
  var = printf "%s_%s x" structname (elemname elem)
generateSerialiseElem structname elem@(RPCElem { elempresence = Optional }) =
  printf "case (%s x) of\n       Nothing -> return ()\n       (Just x) -> %s" a b where
  a :: String
  a = printf "%s_%s" structname (elemname elem)
  b = generateSerialiseElem' structname elem "x"
generateSerialiseElem structname elem@(RPCElem { elempresence = Repeated }) =
  printf "sequence $ map (\\x -> %s) $ %s x" a b where
  a = generateSerialiseElem' structname elem "x"
  b :: String
  b = printf "%s_%s" structname (elemname elem)

generateSerialiseElem' :: String -> RPCElem -> String -> String
generateSerialiseElem' structname elem@(RPCElem { elemtype = Bytes n }) var =
  printf "if (BS.length (%s)) /= %d then error \"Bad length for %s while serialising a %s\" else putTaggedVarBytes %d (%s)" var n (elemname elem) dataname tag var where
  dataname = toDataName structname
  tag = elemtag elem
generateSerialiseElem' structname elem@(RPCElem { elemtype = VarBytes }) var =
  printf "putTaggedVarBytes %d (%s)" (elemtag elem) var
generateSerialiseElem' structname elem@(RPCElem { elemtype = Int }) var =
  printf "putTaggedWord32 %d (%s)" (elemtag elem) var
generateSerialiseElem' structname elem@(RPCElem { elemtype = String }) var =
  printf "putTaggedString %d (%s)" (elemtag elem) var
generateSerialiseElem' structname elem@(RPCElem { elemtype = Struct struct }) var =
  printf "putTaggedVarBytes %d $ %sSerialiseBS (%s)" (elemtag elem) struct var

-- | Return the default value for a given type as a string
defaultValue :: RPCElem -> String
defaultValue (RPCElem { elempresence = Optional }) = "Nothing"
defaultValue (RPCElem { elempresence = Repeated }) = "[]"
defaultValue (RPCElem { elemtype = String }) = "\"\""
defaultValue (RPCElem { elemtype = Int }) = "0"
defaultValue (RPCElem { elemtype = Bytes _ }) = "BS.empty"
defaultValue (RPCElem { elemtype = VarBytes }) = "BS.empty"
defaultValue (RPCElem { elemtype = Struct n }) = printf "%sEmpty" n

-- | Return a list of tag numbers which are required elements
requiredTags :: [RPCElem] -> [Integer]
requiredTags = mapMaybe f where
  f (RPCElem { elempresence = Required, elemtag = tag }) = Just tag
  f _ = Nothing

-- | Generate the code to add the given variable (called @valuename)
--   to an object (called @objectname).
wrapValue structname elem@(RPCElem { elempresence = Required }) objectname valuename =
  valuename
wrapValue structname elem@(RPCElem { elempresence = Optional }) objectname valuename =
  printf "(Just %s)" valuename
wrapValue structname elem@(RPCElem { elempresence = Repeated }) objectname valuename =
  printf "(%s : %s_%s %s)" valuename structname (elemname elem) objectname

generateDeserialise :: String -> RPCElem -> String
generateDeserialise name elem@(RPCElem { elemtype = Int }) =
  printf "                 %d -> getWord8 >> getLengthPrefixed >>= (\\v -> f (o { %s_%s = %s }) (IS.insert %d set))"
         tag name (elemname elem) (wrapValue name elem "o" "v") tag where
  tag = elemtag elem
generateDeserialise name elem@(RPCElem { elemtype = String }) =
  printf "                 %d -> getLengthPrefixed >>= getByteString . fromIntegral >>= (\\v -> f (o { %s_%s = %s }) (IS.insert %d set))"
         tag name (elemname elem) (wrapValue name elem "o" "(decodeString v)") tag where
  tag = elemtag elem
generateDeserialise name elem@(RPCElem { elemtype = VarBytes }) =
  printf "                 %d -> getLengthPrefixed >>= getByteString . fromIntegral >>= (\\v -> f (o { %s_%s = %s }) (IS.insert %d set))"
         tag name (elemname elem) (wrapValue name elem "o" "v") tag where
  tag = elemtag elem
generateDeserialise name elem@(RPCElem { elemtype = Bytes n }) =
  printf "                 %d -> getLengthPrefixed >>= getByteString . fromIntegral >>= (\\v -> if (BS.length v) /= %d then fail \"bytes element had incorrect length decoding %s\" else f (o { %s_%s = %s }) (IS.insert %d set))"
         tag n (toDataName name) name (elemname elem) (wrapValue name elem "o" "v") tag where
  tag = elemtag elem
generateDeserialise name elem@(RPCElem { elemtype = Struct struct }) =
  printf "                 %d -> getLengthPrefixed >>= getByteString . fromIntegral >>= (\\v -> case (%sDeserialiseBS v) of { Left err -> fail (\"Failed to deserialse %s: \" ++ err) ; Right result -> f (o {%s_%s = %s }) (IS.insert %d set) })" (elemtag elem) struct (toDataName name) name (elemname elem) (wrapValue name elem "o" "result") (elemtag elem)

generateStruct (RPCStruct { structname = name, structelems = elems }) = do
  let dataname = toDataName name
  printf "data %s = %s { " dataname dataname
  putStr $ concat $ intersperse ", " $ map (generateElem name) elems
  putStr " } deriving (Show, Eq)\n\n"

  printf "%sSerialise :: %s -> Put\n" name dataname
  printf "%sSerialise x = do\n" name
  putStr "  "
  putStr $ concat $ intersperse "\n  " $ map (generateSerialiseElem name) elems
  putStr "\n\n"
  printf "%sSerialiseBS = BS.concat . BSL.toChunks . runPut . %sSerialise\n\n" name name
  printf "%sEmpty = %s %s" name dataname $ concat $ intersperse " " $ map defaultValue elems
  putStr "\n\n"
  printf "%sRequiredElementsSet = IS.fromList %s\n\n" name $ show $ requiredTags elems
  printf "%sDeserialise :: Get %s\n" name dataname
  printf "%sDeserialise = f %sEmpty IS.empty where\n" name name
  putStr "  f o set = do\n"
  putStr "    emptyp <- isEmpty\n"
  putStr "    if emptyp\n"
  printf "       then if not (IS.isSubsetOf %sRequiredElementsSet set)\n" name
  printf "               then fail \"%s did not contain all required elements\"\n" dataname
  putStr "               else return o\n"
  putStr "       else do tag <- getBase128\n"
  putStr "               case tag of\n"
  putStr $ concat $ intersperse "\n" $ map (generateDeserialise name) elems
  putStr "\n"
  putStr "                 otherwise -> getLengthPrefixed >>= getByteString . fromIntegral >> f o set\n"
  putStr "\n\n"
  printf "%sDeserialiseBS = fst . runGet %sDeserialise\n" name name
  putStr "\n"
  printf "instance TaggedStructure %s where\n" dataname
  printf "  empty = %sEmpty\n" name
  printf "  serialise = %sSerialiseBS\n" name
  printf "  deserialise = %sDeserialiseBS\n" name
  putStr "\n"