module Main where

import           Control.Concurrent
import           Control.Lens
import qualified Data.Binary           as B
import qualified Data.Binary.Get       as B
import qualified Data.ByteString.Char8 as B
import qualified Data.ByteString.Lazy  as BL
import qualified Data.Text             as T
import           Foundation
import qualified Prelude               (show)

import           Network.ABCI

data State = State {
  stateHashCount ∷ Integer,
  stateTxCount   ∷ Integer,
  stateSerial    ∷ Bool
} deriving (Show, Eq)

main ∷ IO ()
main = serve defaultHost defaultPort =<< counter False

handleInfo ∷ MVar State → RequestInfo → IO ResponseInfo
handleInfo state RequestInfo = do
  (State hashCount txCount _) ← readMVar state
  return ResponseInfo {
    _ResponseInfo'data'             = T.concat ["{\"hashes\":", T.pack (Prelude.show hashCount), ",\"txs\":", T.pack (Prelude.show txCount), "}"],
    _ResponseInfo'version           = "0.1.0",
    _ResponseInfo'lastBlockHeight   = 0,
    _ResponseInfo'lastBlockAppHash  = B.empty
  }

handleEcho ∷ RequestEcho → IO ResponseEcho
handleEcho (RequestEcho e) = return $ ResponseEcho e

handleFlush ∷ RequestFlush → IO ResponseFlush
handleFlush RequestFlush = return ResponseFlush

handleOption ∷ MVar State → RequestSetOption → IO ResponseSetOption
handleOption state (RequestSetOption key val) = do
  if key == "serial" && val == "on" then
    modifyMVar_ state (\s → return $ s { stateSerial = True })
  else return ()
  return $ ResponseSetOption ""

handleInitChain ∷ RequestInitChain → IO ResponseInitChain
handleInitChain _ = return ResponseInitChain

handleBeginBlock ∷ RequestBeginBlock → IO ResponseBeginBlock
handleBeginBlock _ = return ResponseBeginBlock

handleCheckTx ∷ MVar State → RequestCheckTx → IO ResponseCheckTx
handleCheckTx state (RequestCheckTx tx) =
  case B.runGetOrFail B.getInt64be $ BL.fromStrict $ B.append (B.replicate (8 - B.length tx) '\NUL') tx of
    Right (_, _, num) → do
      (State _ txCount serial) ← readMVar state
      if not serial || fromIntegral num >= txCount then
        return $ ResponseCheckTx OK B.empty T.empty
      else
        return $ ResponseCheckTx BadNonce B.empty invalidNumber
    Left _ → return $ ResponseCheckTx EncodingError B.empty undecodable

handleDeliverTx ∷ MVar State → RequestDeliverTx → IO ResponseDeliverTx
handleDeliverTx state (RequestDeliverTx tx) =
  case B.runGetOrFail B.getInt64be $ BL.fromStrict $ B.append (B.replicate (8 - B.length tx) '\NUL') tx of
    Right (_, _, num) →
      modifyMVar state $ \state@(State _ txCount serial) →
        if not serial || fromIntegral num == txCount then
          return (state { stateTxCount = txCount + 1 }, ResponseDeliverTx OK B.empty T.empty)
        else
          return (state, ResponseDeliverTx BadNonce B.empty invalidNumber)
    Left _ → return $ ResponseDeliverTx EncodingError B.empty undecodable

handleEndBlock ∷ RequestEndBlock → IO ResponseEndBlock
handleEndBlock _ = return $ ResponseEndBlock []

handleCommit ∷ MVar State → RequestCommit → IO ResponseCommit
handleCommit state _ = do
  count ← modifyMVar state $ \state@(State hashCount txCount _) → do
    let newCount = hashCount + 1
    return (state { stateHashCount = newCount }, fromIntegral txCount ∷ Int64)
  return $ ResponseCommit OK (if count == 0 then "" else BL.toStrict $ B.encode count) T.empty

handleQuery ∷ MVar State → RequestQuery → IO ResponseQuery
handleQuery state (RequestQuery _ path _ _) =
  case path of
    "hash"  → do
      (State hashCount _ _) ← readMVar state
      return $ ResponseQuery OK 0 "" (BL.toStrict $ B.encode hashCount) "" 0 ""
    "tx"    → do
      (State _ txCount _) ← readMVar state
      return $ ResponseQuery OK 0 "" (BL.toStrict $ B.encode txCount) "" 0 ""
    _       →  return $ ResponseQuery EncodingError 0 "" "" "" 0 $ T.append "Invalid query path. Expected hash or tx, got " path

counter ∷ Bool → IO (Request → IO Response)
counter serial = do
  state ← newMVar $ State 0 0 serial
  let respondWith func val = func .~ val $ emptyResponse
  return $ \req →
    handleCase _Request'info (fmap (respondWith info) . handleInfo state) req $
      handleCase _Request'echo (fmap (respondWith echo) . handleEcho) req $
        handleCase _Request'flush (fmap (respondWith flush) . handleFlush) req $
          handleCase _Request'setOption (fmap (respondWith setOption) . handleOption state) req $
            handleCase _Request'initChain (fmap (respondWith initChain) . handleInitChain) req $
              handleCase _Request'checkTx (fmap (respondWith checkTx) . handleCheckTx state) req $
                handleCase _Request'beginBlock (fmap (respondWith beginBlock) . handleBeginBlock) req $
                  handleCase _Request'deliverTx (fmap (respondWith deliverTx) . handleDeliverTx state) req $
                    handleCase _Request'endBlock (fmap (respondWith endBlock) . handleEndBlock) req $
                      handleCase _Request'commit (fmap (respondWith commit) . handleCommit state) req $
                        handleCase _Request'query (fmap (respondWith query) . handleQuery state) req $
                         return $ exception .~ ResponseException unknown $ emptyResponse

flushResponse ∷ Response
flushResponse = flush .~ ResponseFlush $ emptyResponse

handleCase ∷ ∀ a b c m . (a → Maybe b) → (b → m c) → a → m c → m c
handleCase getter func val cont =
  case getter val of
    Just x  → func x
    Nothing → cont

emptyResponse ∷ Response
emptyResponse = Response Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing

unknown ∷ T.Text
unknown = "unknown_request"

invalidNumber ∷ T.Text
invalidNumber = "invalid_number"

undecodable ∷ T.Text
undecodable = "undecodable"