{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE LambdaCase #-}

module Data.Apiary.Method
    ( Method(..)
    , renderMethod
    , dispatchMethod
    , parseMethod
    ) where

import Data.Hashable(Hashable(..))
import Data.String(IsString(..))
import qualified Data.ByteString.Char8 as S
import qualified Data.ByteString.Unsafe as U

data Method
    = GET
    | POST
    | HEAD
    | PUT
    | DELETE
    | TRACE
    | CONNECT
    | OPTIONS
    | PATCH
    | NonStandard S.ByteString
    deriving (Eq, Ord, Read, Show)

instance Hashable Method where
    hash GET             = 0
    hash POST            = 1
    hash HEAD            = 2
    hash PUT             = 3
    hash DELETE          = 4
    hash TRACE           = 5
    hash CONNECT         = 6
    hash OPTIONS         = 7
    hash PATCH           = 8
    hash (NonStandard s) = hash s
    hashWithSalt salt x = salt `hashWithSalt` hash x

renderMethod :: Method -> S.ByteString
renderMethod = \case
    GET           -> "GET"
    POST          -> "POST"
    HEAD          -> "HEAD"
    PUT           -> "PUT"
    DELETE        -> "DELETE"
    TRACE         -> "TRACE"
    CONNECT       -> "CONNECT"
    OPTIONS       -> "OPTIONS"
    PATCH         -> "PATCH"
    NonStandard a -> a
{-# INLINE renderMethod #-}

dispatchMethod :: a -> a -> a -> a -> a -> a -> a -> a -> a -> (S.ByteString -> a) -> S.ByteString -> a
dispatchMethod get post head_ put delete trace connect options patch ns s
    | S.length s < 2 = ns s
    | otherwise = case U.unsafeHead s of
        71 -> if S.length s == 3 && cc 1 69 && cc 2 84 then get else ns s
        80 -> case U.unsafeIndex s 1 of
            79 -> if S.length s == 4 && cc 2 83 && cc 3 84 then post  else ns s
            85 -> if S.length s == 3 && cc 2 84            then put   else ns s
            65 -> if S.length s == 5 && cc 2 84 && cc 3 67 && cc 4 72 then patch else ns s
            _  -> ns s
        72 -> if S.length s == 4 && cc 1 69 && cc 2 65 && cc 3 68 then head_ else ns s
        68 -> if S.length s == 6 && cc 1 69 && cc 2 76 && cc 3 69 && cc 4 84 && cc 5 69 then delete else ns s
        84 -> if S.length s == 5 && cc 1 82 && cc 2 65 && cc 3 67 && cc 4 69 then trace else ns s
        67 -> if S.length s == 7 && cc 1 79 && cc 2 78 && cc 3 78 && cc 4 69 && cc 5 67 && cc 6 84 then connect else ns s
        79 -> if S.length s == 7 && cc 1 80 && cc 2 84 && cc 3 73 && cc 4 79 && cc 5 78 && cc 6 83 then options else ns s
        _  -> ns s
  where
    cc i c = U.unsafeIndex s i == c

parseMethod :: S.ByteString -> Method
parseMethod = dispatchMethod GET POST HEAD PUT DELETE TRACE CONNECT OPTIONS PATCH NonStandard

instance IsString Method where
    fromString = parseMethod . S.pack
