{-# LANGUAGE TemplateHaskell, QuasiQuotes #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}

module Language.MessagePack.IDL.CodeGen.Haskell (
  Config(..),
  generate,
  ) where

import Data.Char
import Data.Monoid
import qualified Data.Text as T
import qualified Data.Text.Lazy as LT
import qualified Data.Text.Lazy.IO as LT
import Text.Shakespeare.Text

import Language.MessagePack.IDL.Syntax as MP

data Config
  = Config
    { configFilePath :: FilePath
    }

generate :: Config -> Spec -> IO ()
generate Config {..} spec = do
  LT.writeFile "Types.hs" [lt|
{-# LANGUAGE TemplateHaskell #-}

module Types where

import Data.Int
import Data.MessagePack
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Words
#{LT.concat $ map genTypeDecl spec}
|]

  LT.writeFile "Server.hs" [lt|
|]

  LT.writeFile "Client.hs" [lt|
module Server where

import Data.ByteString (ByteString)
import qualified Data.ByteString as B
import Data.Map (Map)
import qualified Data.Map as M
import Data.Text (Text)
import qualified Data.Text as T

import qualified Network.MessagePackRpc.Client as MP

import Types
#{LT.concat $ map genClient spec}
|]

genClient :: Decl -> LT.Text
genClient MPService {..} =
  [lt|
newtype #{monadName} m a
  = #{monadName} { un#{monadName} :: StateT () m a }
  deriving (Monad, MonadIO, MonadTrans, MonadState ())
#{LT.concat $ map genMethod serviceMethods}
|]
  where
    monadName = classize (serviceName) `mappend` "T"
    genMethod Function {..} =
      let ts = map (genType . fldType) methodArgs in
      let typs = ts ++ [ [lt|#{monadName} (#{genRetType methodRetType})|] ] in
      [lt|
#{methodize methodName} :: #{LT.intercalate " -> " typs}
#{methodize methodName} = MP.method "#{methodName}"
|]
    genMethod f = error $ "unsupported: " ++ show f

genClient _ = ""

genTypeDecl :: Decl -> LT.Text
genTypeDecl MPMessage {..} =
  let mems = LT.intercalate "\n  , " $ map f msgFields in
  [lt|
data #{dataName}
  = #{dataName}
  { #{mems}
  }
  deriving (Eq, Show)
deriveObject False ''#{dataName}
|]
  where
    dataName = classize msgName
    f Field {..} =
      let fname = uncapital dataName `mappend` (capital $ camelize fldName) in
      [lt|#{fname} :: #{genType fldType}|]

genTypeDecl _ = ""

genType :: Type -> LT.Text
genType (TInt sign bits) =
  let base = if sign then "Int" else "Word" :: T.Text in
  [lt|#{base}#{show bits}|]
genType (TFloat False) =
  [lt|Float|]
genType (TFloat True) =
  [lt|Double|]
genType TBool =
  [lt|Bool|]
genType TRaw =
  [lt|ByteString|]
genType TString =
  [lt|Text|]
genType (TList typ) =
  [lt|[#{genType typ}]|]
genType (TMap typ1 typ2) =
  [lt|Map (#{genType typ1}) (#{genType typ2})|]
genType (TTuple typs) =
  [lt|(#{LT.intercalate ", " $ map genType typs})|]
genType (TUserDef name params) =
  [lt|#{classize name}|]
genType (TObject) =
  undefined

genRetType :: Maybe Type -> LT.Text
genRetType Nothing = "()"
genRetType (Just t) = genType t

classize :: T.Text -> T.Text
classize = capital . camelize

methodize :: T.Text -> T.Text
methodize = uncapital . camelize

camelize :: T.Text -> T.Text
camelize = T.concat . map capital . T.words . T.map ubToSpc where
  ubToSpc '_' = ' '
  ubToSpc c = c

capital :: T.Text -> T.Text
capital word =
  (T.map toUpper $ T.take 1 word) `mappend` T.drop 1 word

uncapital :: T.Text -> T.Text
uncapital word =
  (T.map toLower $ T.take 1 word) `mappend` T.drop 1 word

{-
genServer :: Spec -> IO Builder
genServer = undefined

genClient :: Spec -> IO Builder
genClient spec = do
  decs <- runQ $ genClient' spec
  putStrLn $ pprint decs
  undefined

genClient' :: Spec -> Q [Dec]
genClient' spec = return . concat =<< mapM genDecl spec

genDecl :: Decl -> Q [Dec]
genDecl (Message name super fields) = do
  let clsName = mkName $ T.unpack name
      con = recC clsName $ map genFld fields
  d <- dataD (cxt []) clsName [] [con] [''Eq, ''Ord, ''Show]
  return [d]
  where
    genFld (Field fid req typ fname _) =
      varStrictType (mkName $ uncapital $ T.unpack name ++ capital (T.unpack fname)) (strictType notStrict $ genType typ)

genDecl (Service name version meths) = do
  return []

genDecl _ = do
  d <- dataD (cxt []) (mkName "Ign") [] [] []
  return [d]

genType :: MP.Type -> Q TH.Type
genType (TInt False 8 ) = conT ''Word8
genType (TInt False 16) = conT ''Word16
genType (TInt False 32) = conT ''Word32
genType (TInt False 64) = conT ''Word64
genType (TInt True  8 ) = conT ''Int8
genType (TInt True  16) = conT ''Int16
genType (TInt True  32) = conT ''Int32
genType (TInt True  64) = conT ''Int64

genType (TFloat False) = conT ''Float
genType (TFloat True ) = conT ''Double

genType TBool = conT ''Bool
genType TRaw = conT ''B.ByteString
genType TString = conT ''T.Text

genType (TList typ) =
  listT `appT` genType typ
genType (TMap kt vt) =
  [t| M.Map $(genType kt) $(genType vt) |]

genType (TClass name) =
  conT $ mkName $ capital $ T.unpack name

genType (TTuple typs) =
  foldl appT (tupleT (length typs)) (map genType typs)

capital (c:cs) = toUpper c : cs
capital cs = cs

uncapital (c:cs) = toLower c : cs
uncapital cs = cs
-}