{-# LANGUAGE TypeOperators #-}

module Lackey.Internal.HasRuby where

import Lackey.Internal.Endpoint
import Lackey.Internal.Header
import Lackey.Internal.MatrixItem
import Lackey.Internal.PathSegment
import Lackey.Internal.QueryItem
import Servant.API ((:<|>)(..))

import qualified Data.Char as Char
import qualified Data.List as List
import qualified Data.Maybe as Maybe

infixl 1 &
(&) :: a -> (a -> b) -> b
x & f = f x

class HasRuby a where
    rubyFor :: a -> String

instance HasRuby () where
    rubyFor _ = ""

instance HasRuby Endpoint where
    rubyFor endpoint = "\
        \def " ++ renderName endpoint ++ "(" ++ renderParams endpoint ++ ")\n\
        \  excon.request(\n\
        \    method: :" ++ renderMethod endpoint ++ ",\n\
        \    path: \"" ++ renderPath endpoint ++ "\",\n\
        \    headers: {" ++ renderHeaders endpoint ++ "},\n\
        \    body: " ++ (if endpointHasBody endpoint then "body" else "nil") ++ "\n\
        \  )\n\
        \end\
    \"

instance (HasRuby a, HasRuby b) => HasRuby (a :<|> b) where
    rubyFor (x :<|> y) =
        let rx = rubyFor x
            ry = rubyFor y
            s = if null rx || null ry then "" else "\n\n"
        in  concat [rx, s, ry]

renderName :: Endpoint -> String
renderName endpoint =
    let method = renderMethod endpoint

        renderPathSegment (PathLiteral literal) = literal
        renderPathSegment (PathCapture capture) = capture
        renderPathSegment (PathMatrix (MatrixFlag flag)) = flag
        renderPathSegment (PathMatrix (MatrixParam param)) = param
        renderPathSegment (PathMatrix (MatrixParams params)) = params
        pathSegments =
            let segments = endpointPathSegments endpoint
                renderedSegments = map renderPathSegment segments
            in  if all isPathMatrix segments
                then "index" : renderedSegments
                else renderedSegments
        path = List.intercalate "_" pathSegments

        renderQueryItem (QueryFlag flag) = flag
        renderQueryItem (QueryParam param) = param
        renderQueryItem (QueryParams params) = params
        queryItems = map renderQueryItem (endpointQueryItems endpoint)
        query = if null queryItems
            then ""
            else "_" ++ List.intercalate "_" queryItems

        renderHeader (Header x) = x
        headers = map renderHeader (endpointHeaders endpoint)
        header = if null headers
            then ""
            else "_" ++ List.intercalate "_" headers

    in  sanitize (method ++ "_" ++ path ++ query ++ header)

renderParams :: Endpoint -> String
renderParams endpoint =
    let renderPathSegment (PathCapture capture) = Just (sanitize capture)
        renderPathSegment _ = Nothing
        pathSegments
            = endpoint
            & endpointPathSegments
            & map renderPathSegment
            & Maybe.catMaybes

        renderMatrixItem (PathMatrix (MatrixFlag flag)) = Just (sanitize flag ++ ": false")
        renderMatrixItem (PathMatrix (MatrixParam param)) = Just (sanitize param ++ ": nil")
        renderMatrixItem (PathMatrix (MatrixParams params)) = Just (sanitize params ++ ": []")
        renderMatrixItem _ = Nothing
        matrixItems
            = endpoint
            & endpointPathSegments
            & Maybe.mapMaybe renderMatrixItem

        renderQueryItem (QueryFlag flag) = Just (sanitize flag ++ ": false")
        renderQueryItem (QueryParam param) = Just (sanitize param ++ ": nil")
        renderQueryItem (QueryParams params) = Just (sanitize params ++ ": []")
        queryItems
            = endpoint
            & endpointQueryItems
            & Maybe.mapMaybe renderQueryItem

        renderHeader (Header header) = sanitize header ++ ": nil"
        headers = endpoint & endpointHeaders & map renderHeader

        body = ["body" | endpointHasBody endpoint]

    in  List.intercalate ", "
        (concat [["excon"], pathSegments, body, matrixItems, queryItems, headers])

renderMethod :: Endpoint -> String
renderMethod endpoint = endpoint & endpointMethod & show & map Char.toLower

renderPath :: Endpoint -> String
renderPath endpoint =
    let renderPathSegment (PathLiteral literal) = '/' : literal
        renderPathSegment (PathCapture capture) = concat ["/#{", sanitize capture, "}"]
        renderPathSegment (PathMatrix (MatrixFlag flag)) = concat ["#{\";", flag, "\" if ", sanitize flag, "}"]
        renderPathSegment (PathMatrix (MatrixParam param)) = concat [";", param, "=#{", sanitize param, "}"]
        renderPathSegment (PathMatrix (MatrixParams params)) = concat ["#{", sanitize params, ".map { |x| \";", params, "[]=#{x}\" }.join}"]
        pathSegments =
            let segments = endpointPathSegments endpoint
                renderedSegments = concatMap renderPathSegment segments
            in  case segments of
                [] -> '/' : renderedSegments
                (PathMatrix _ : _) -> '/' : renderedSegments
                _ -> renderedSegments

        renderQueryItem (QueryFlag flag) = concat ["#{\"&", flag, "\" if ", sanitize flag, "}"]
        renderQueryItem (QueryParam param) = concat ["&", param, "=#{", sanitize param, "}"]
        renderQueryItem (QueryParams params) = concat ["#{", sanitize params, ".map { |x| \"&", params, "[]=#{x}\" }.join}"]
        queryItems = case endpointQueryItems endpoint of
            [] -> ""
            items -> '?' : concatMap renderQueryItem items

    in pathSegments ++ queryItems

renderHeaders :: Endpoint -> String
renderHeaders endpoint =
    let headers = endpointHeaders endpoint
    in  if null headers
        then ""
        else
            let y = headers
                    & map (\ (Header x) -> concat ["\"", x, "\" => ", sanitize x])
                    & List.intercalate ", "
            in  " " ++ y ++ " "

sanitize :: String -> String
sanitize xs
    = xs
    & map (\ x -> if Char.isAlphaNum x then Char.toLower x else '_')