{-# LANGUAGE NamedFieldPuns    #-}
{-# LANGUAGE OverloadedStrings #-}


module Network.Miku.Engine where

import           Control.Lens                      hiding (use)
import           Control.Monad.Reader              hiding (join)
import           Control.Monad.State               hiding (join)
import           Data.ByteString.Char8             (ByteString)
import qualified Data.ByteString.Char8             as B
import qualified Data.Default                      as Default
import           Data.List
import           Data.Maybe
import           Hack2
import           Hack2.Contrib.Middleware.NotFound
import           Hack2.Contrib.Middleware.UserMime
import           Hack2.Contrib.Utils               hiding (get, put)
import           Network.Miku.Config
import           Network.Miku.Type
import           Network.Miku.Utils
import           Prelude                           hiding ((-))
import           System.FilePath                   ((</>))


miku :: MikuMonad -> Application
miku miku_monad = miku_middleware miku_monad (not_found dummy_app)

miku_middleware :: MikuMonad -> Middleware
miku_middleware miku_monad =

  let miku_state                      = execState miku_monad mempty
      mime_filter                     = user_mime - miku_state ^. mimes
      miku_middleware_stack           = use - miku_state ^. middlewares
      miku_router_middleware          = use - miku_state ^. router
      pre_installed_middleware_stack  = use - pre_installed_middlewares
  in

  use [pre_installed_middleware_stack, mime_filter, miku_middleware_stack, miku_router_middleware]


miku_router :: RequestMethod -> ByteString -> AppMonad -> Middleware
miku_router route_method route_string app_monad app = \env ->
  if request_method env == route_method
    then
      case env & path_info & parse_params route_string of
        Nothing -> app env
        Just (_, params) ->
          let miku_app = run_app_monad - local (put_namespace miku_captures params) app_monad
          in
          miku_app env

    else
      app env


  where

    run_app_monad :: AppMonad -> Application
    run_app_monad app_monad = \env -> runReaderT app_monad env & flip execStateT Default.def


parse_params :: ByteString -> ByteString -> Maybe (ByteString, [(ByteString, ByteString)])
parse_params "*" x = Just (x, [])
parse_params "" ""  = Just ("", [])
parse_params "" _   = Nothing
parse_params "/" "" = Nothing
parse_params "/" "/"  = Just ("/", [])

parse_params t s =

  let template_tokens = B.split '/' t
      url_tokens      = B.split '/' s

      _template_last_token_matches_everything         = (template_tokens & length) > 0 && (["*"] `isSuffixOf` template_tokens)
      _template_tokens_length_equals_url_token_length = (template_tokens & length) == (url_tokens & length)
  in

  if not - _template_last_token_matches_everything || _template_tokens_length_equals_url_token_length
    then Nothing
    else
      let rs = zipWith capture template_tokens url_tokens
      in
      if all isJust rs
        then
          let token_length = length template_tokens
              location     = B.pack - "/" </> (B.unpack - B.intercalate "/" - take token_length url_tokens)
          in
          Just - (location, rs & catMaybes & catMaybes)
        else Nothing

  where
    capture x y
      | ":" `isPrefixOf` B.unpack x = Just - Just (B.tail x, y)
      | x == "*" = Just Nothing
      | x == y = Just Nothing
      | otherwise = Nothing