{-# LANGUAGE OverloadedStrings #-}

module Main where

import           Control.Monad (join)
import qualified Data.ByteString.Lazy as BSL
import qualified Data.Text as TS
import qualified Data.Text.Encoding as EncodingS
import qualified Data.Text.Lazy as T
import qualified Network.HTTP.Types as HTTP
import qualified Network.Wai as W
import           System.FilePath ((</>))

import qualified Inf.DB.Monad as DB
import qualified Inf.DB as DB
import qualified Inf.Feed as Feed
import qualified Inf.Templates as Template
import qualified Inf.Log as Log
import           Inf.Types
import qualified Inf.Web as Web

main :: IO ()
main = Web.run route

route :: Web.Routes
route c req = do
  user <- Web.getUser c (lookup HTTP.hCookie (W.requestHeaders req))
  case (W.requestMethod req, W.pathInfo req) of
    ("GET", []) -> do
      post <- DB.runDB c DB.newestPostRef
      Web.redirect (postRefURL post)

    ("POST", []) ->
      case user of
        Nothing -> do
          Log.error [ "non-logged-in user attempted to post something" ]
          Web.redirect "/"
        Just u -> do
          formData <- Web.formData req
          let rpMb = do
                title <- join (lookup "title" formData)
                contents <- join (lookup "contents" formData)
                pure RawPost
                  { rpId       = Nothing
                  , rpTitle    = EncodingS.decodeUtf8 title
                  , rpAuthor   = User u
                  , rpContents = EncodingS.decodeUtf8 contents
                  }
          case rpMb of
            Nothing -> do
              Log.error [ "bad input data somehow" ]
              Web.redirect "/"
            Just rp -> do
              DB.runDB c (DB.submitPost (User u) rp)
              Web.redirect "/newest"

    ("GET", ["auth"]) -> do
      password <- Template.login
      Web.ok user "login" password "/"

    ("GET", ["archive"]) -> do
      posts <- DB.runDB c DB.allPostRefs
      contents <- Template.list posts
      Web.ok user "Past Entries" contents "/archive"

    ("GET", ["create"]) ->
      case user of
        Nothing -> Web.redirect "/"
        Just _  -> do
          contents <- Template.edit "/" emptyRawPost
          Web.ok user "New Post" contents "/"

    ("GET", [y, m, s]) -> do
      (post, cached) <- DB.runDB c $ do
        ps <- DB.postByDateAndSlug y m s
        ch <- DB.cachedMarkup (postId ps)
        pure (ps, ch)
      case cached of
        Just cache -> do
          Web.ok user (T.fromStrict (postTitle post)) cache (postURL post)
        Nothing -> do
          contents <- Template.post user post
          DB.runDB c (DB.storeCachedMarkup (postId post) contents)
          Web.ok user (T.fromStrict (postTitle post)) contents (postURL post)

    ("POST", [y, m, s]) ->
      case user of
        Nothing -> do
          Log.error [ "non-logged-in user attempted to edit post" ]
          Web.redirect "/"
        Just u -> do
          formData <- Web.formData req
          oldPost <- DB.runDB c (DB.postByDateAndSlug y m s)
          let rpMb = do
                title <- join (lookup "title" formData)
                contents <- join (lookup "contents" formData)
                pure RawPost
                  { rpId       = Just (postId oldPost)
                  , rpTitle    = EncodingS.decodeUtf8 title
                  , rpAuthor   = User u
                  , rpContents = EncodingS.decodeUtf8 contents
                  }
          case rpMb of
            Nothing -> do
              Log.error [ "bad input data somehow" ]
              Web.redirect "/"
            Just rp -> do
              DB.runDB c (DB.submitPost (User u) rp)
              Web.redirect (postURL oldPost)

    ("GET", [y, m, s, "edit"]) ->
      case user of
        Nothing -> do
          Log.error [ "non-logged-in user attempted to edit post" ]
          Web.redirect "/"
        Just {} -> do
          oldPost <- DB.runDB c (DB.postByDateAndSlug y m s)
          contents <- Template.edit (postURL oldPost) (postToRawPost oldPost)
          Web.ok user (T.fromStrict (postTitle oldPost)) contents "/"

    ("POST", [y, m, s, "delete"]) ->
      case user of
        Nothing -> Web.redirect "/"
        Just _ -> do
          Log.warn [ "deleting post:", show (y, m, s) ]
          DB.runDB c $ do
            post <- DB.postByDateAndSlug y m s
            DB.deletePost (postToRawPost post)
          Web.redirect "/"

    ("GET", [y, m, s, "delete"]) ->
      case user of
        Nothing -> Web.redirect "/"
        Just _ -> do
          post <- DB.runDB c (DB.postByDateAndSlug y m s)
          contents <- Template.delete post
          Web.ok user "Delete Post?" contents "/"

    ("POST", ["login"]) -> do
      formData <- Web.formData req
      Log.warn [ "attempting login"
               , case lookup "user" formData of
                   Just (Just u) -> "of user " ++ show u
                   _ -> "without a specified user"
               ]
      case (lookup "user" formData, lookup "passwd" formData) of
        (Just (Just u), Just (Just p)) -> do
          userMb <- Web.verifyUser c u p
          case userMb of
            Just _  ->
              let cookies = Web.authCookie u p
              in Web.redirectWithCookies "/" cookies
            Nothing -> Web.raise500 req (Unimplemented "Bad Password")
        _ -> Web.raise500 req (Unimplemented "Bad Login")

    ("GET", ["logout"]) ->
      Web.redirectWithCookies "/" [("USERDATA", "")]

    ("GET", ["newest"]) -> do
      post <- DB.runDB c DB.newestPostRef
      Web.redirect (postRefURL post)

    ("GET", ["oldest"]) -> do
      post <- DB.runDB c DB.oldestPostRef
      Web.redirect (postRefURL post)

    ("GET", ["static", fp]) ->
      let path = "static" </> TS.unpack fp
      in pure (W.responseFile HTTP.status200 [] path Nothing)

    ("GET", ["rss"]) -> do
      cached <- DB.runDB c DB.cachedRSS
      case cached of
        Just t -> Web.atomFeed (BSL.fromStrict t)
        Nothing -> do
          posts <- DB.runDB c DB.allPosts
          feed <- Feed.renderFeed posts
          DB.runDB c (DB.storeCachedRSS (BSL.toStrict feed))
          Web.atomFeed feed

    ("GET", [pagename]) -> do
      pg <- DB.runDB c (DB.staticPage pagename)
      contents <- Template.page (pageText pg)
      Web.ok user (T.fromStrict pagename) contents ("/" <> pagename)

    ("GET", [_page, "edit"]) ->
      case user of
        Nothing -> Web.redirect "/"
        Just _  -> do
          contents <- Template.edit "/" emptyRawPost
          Web.ok user "New Post" contents "/"

    (_, path) -> Web.raise404 req path
