{-# LANGUAGE RecordWildCards,
             ScopedTypeVariables,
             TemplateHaskell #-}

module TH.YQL(generateYQLs, generateYQL) where

import Control.Applicative ((<$>))
import Control.Monad.Catch (MonadThrow)
import Control.Monad.IO.Class (MonadIO(liftIO))

import Data.Aeson
import Data.Char
import Data.Monoid ((<>))

import Language.Haskell.TH (Body(..), Dec(..),  Pat(..),
                            Pred(..), Q, Type(..), TyVarBndr(..),
                            mkName)

import System.Log.Logger

import Control.Monad.Trans.API

import Data.JSON.Void ()
import Data.Settings.YQL
import Data.State.YQL
import Data.TH.API
import Data.TH.YQL
import Data.TH.Object

import Helper.Name

import YQL

generateYQLs :: [API] -> Q [YQL]
generateYQLs apis = do
  sequence $ generateYQL <$> apis

generateYQL :: API -> Q YQL
generateYQL api = do

  let cc = camelCase . apiName $ api
      name    = (toLower . head $ cc):(tail cc)
      inType  = apiInputType  . apiInput  $ api
      outType = apiOutputType . apiOutput $ api

  pipe <- generateYQLPipe name api

  return $ YQL inType outType pipe

generateYQLPipe :: String -> API -> Q YQLPipe
generateYQLPipe base API {..} = do
  let name = mkName base
      opentable = snd $ apiInputOpenDataTable apiInput
      inType  = apiInputType  $ apiInput
      outType = apiOutputType $ apiOutput

  let r = mkName "r"
      s = mkName "s"
      m = mkName "m"
      a = mkName "a"
      t = AppT (AppT ArrowT (ConT ''YQLSettings)) (AppT (AppT ArrowT inType) (AppT (AppT (AppT (ConT ''APIT) (VarT s)) (VarT m)) (AppT (ConT ''Maybe) outType)))
      t' = ForallT
           [ PlainTV s, PlainTV m, PlainTV a ]
           [ ClassP ''MonadIO [VarT m]
           , ClassP ''MonadThrow [VarT m]
           , ClassP ''YQLState [VarT s] ]
           t
      sig = SigD name t'

  body <-
    [| \YQLSettings {..} input -> do
        value <- liftIO $ runYQL opentable (toObject input)
        case fromJSON value of
         Success output -> do
           return . Just $ output
         Error _ -> do
           liftIO $
             errorM
             ("YQL." ++ apiName)
             "Could not decode response body"
           return Nothing |]
  let dec = ValD (VarP name) (NormalB body) []

  return $ YQLPipe name sig dec
