{-# 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 = ()

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