{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}

module Lackey
  ( rubyForAPI
  ) where

import qualified Data.Char as Char
import Data.Function ((&))
import qualified Data.Maybe as Maybe
import Data.Monoid ((<>))
import qualified Data.Proxy as Proxy
import qualified Data.Text as Text
import qualified Data.Text.Encoding as Text
import qualified Servant.Foreign as Servant

type Language = Servant.NoTypes

languageProxy :: Proxy.Proxy Language
languageProxy = Proxy.Proxy

type Request = Servant.NoContent

requestProxy :: Proxy.Proxy Request
requestProxy = Proxy.Proxy

renderRequests :: [Servant.Req Request] -> Text.Text
renderRequests requests = requests & map renderRequest & Text.intercalate ";"

functionName :: Servant.Req Request -> Text.Text
functionName request = request & Servant._reqFuncName & Servant.snakeCase

hasBody :: Servant.Req Request -> Bool
hasBody request =
  case Servant._reqBody request of
    Nothing -> False
    Just _ -> True

bodyArgument :: Text.Text
bodyArgument = "body"

underscore :: Text.Text -> Text.Text
underscore text =
  text & Text.toLower &
  Text.map
    (\c ->
       if Char.isAlphaNum c
         then c
         else '_')

getHeaders :: Servant.Req Request -> [Text.Text]
getHeaders request =
  request & Servant._reqHeaders &
  Maybe.mapMaybe
    (\h ->
       case h of
         Servant.HeaderArg x -> Just x
         Servant.ReplaceHeaderArg _ _ -> Nothing) &
  map Servant._argName &
  map Servant.unPathSegment

getURLPieces :: Servant.Req Request -> [Either Text.Text Text.Text]
getURLPieces request =
  let url = request & Servant._reqUrl
      path =
        url & Servant._path & map Servant.unSegment &
        Maybe.mapMaybe
          (\segment ->
             case segment of
               Servant.Static _ -> Nothing
               Servant.Cap arg -> Just arg) &
        map Servant._argName &
        map Servant.unPathSegment
      query =
        url & Servant._queryStr & map Servant._queryArgName &
        map Servant._argName &
        map Servant.unPathSegment
  in map Left path ++ map Right query

functionArguments :: Servant.Req Request -> Text.Text
functionArguments request =
  Text.concat
    [ "("
    , [ [Just "excon"]
      , request & getURLPieces &
        map
          (\piece ->
             case piece of
               Left capture -> underscore capture
               Right param -> underscore param <> ": nil") &
        map Just
      , request & getHeaders & map underscore & map (<> ": nil") & map Just
      , [ if hasBody request
            then Just bodyArgument
            else Nothing
        ]
      ] &
      concat &
      Maybe.catMaybes &
      Text.intercalate ","
    , ")"
    ]

requestMethod :: Servant.Req Request -> Text.Text
requestMethod request =
  request & Servant._reqMethod & Text.decodeUtf8 & Text.toLower & Text.cons ':'

requestPath :: Servant.Req Request -> Text.Text
requestPath request =
  let path =
        request & Servant._reqUrl & Servant._path & map Servant.unSegment &
        map
          (\x ->
             case x of
               Servant.Static y -> Servant.unPathSegment y
               Servant.Cap y ->
                 let z =
                       y & Servant._argName & Servant.unPathSegment & underscore
                 in "#{" <> z <> "}") &
        Text.intercalate "/"
      query =
        request & Servant._reqUrl & Servant._queryStr &
        map Servant._queryArgName &
        map Servant._argName &
        map Servant.unPathSegment &
        map (\x -> x <> "=#{" <> underscore x <> "}") &
        Text.intercalate "&"
      url =
        "/" <> path <>
        (if Text.null query
           then ""
           else "?" <> query)
  in "\"" <> url <> "\""

requestHeaders :: Servant.Req Request -> Text.Text
requestHeaders request =
  [ ["{"]
  , request & getHeaders & map (\x -> "\"" <> x <> "\"=>" <> underscore x)
  , ["}"]
  ] &
  concat &
  Text.concat

requestBody :: Servant.Req Request -> Text.Text
requestBody request =
  if hasBody request
    then bodyArgument
    else "nil"

functionBody :: Servant.Req Request -> Text.Text
functionBody request =
  Text.concat
    [ "excon.request("
    , ":method=>"
    , requestMethod request
    , ","
    , ":path=>"
    , requestPath request
    , ","
    , ":headers=>"
    , requestHeaders request
    , ","
    , ":body=>"
    , requestBody request
    , ")"
    ]

renderRequest :: Servant.Req Request -> Text.Text
renderRequest request =
  Text.concat
    [ "def "
    , functionName request
    , functionArguments request
    , functionBody request
    , "end"
    ]

requestsForAPI ::
     ( Servant.HasForeign Language Request api
     , Servant.GenerateList Request (Servant.Foreign Request api)
     )
  => Proxy.Proxy api
  -> [Servant.Req Request]
requestsForAPI api = api & Servant.listFromAPI languageProxy requestProxy

rubyForAPI ::
     ( Servant.HasForeign Language Request api
     , Servant.GenerateList Request (Servant.Foreign Request api)
     )
  => Proxy.Proxy api
  -> Text.Text
rubyForAPI api = api & requestsForAPI & renderRequests