{-# LANGUAGE ScopedTypeVariables #-}
module ControllerPostActions where

import Data.List (isInfixOf)
import qualified Data.ByteString.Char8 as B
import Control.Monad.Error


import Happstack.Server
import Happstack.State
import Happstack.Helpers

import StateVersions.AppState1
import View
import Misc
import ControllerMisc
import ControllerGetActions
import FromDataInstances


loginPage :: RenderGlobals -> ServerPartT IO Response
loginPage rglobs = do
  ref <- runErrorT tutAppReferrer 
  let landingpage = fromEither "/" $ ref -- tutAppReferrer rq
  loginPage' authUser (const startsess') rglobs landingpage
  where 
    authUser = authUser' getUserPassword
    getUserPassword name = return . maybe Nothing (Just . B.unpack . password) =<< query (GetUserInfos name)

-- move to common code/helpers
fromEither :: a -> Either e a -> a
fromEither def (Left _) = def
fromEither _ (Right x) = x

-- move this to HAppSHelpers
--tutAppReferrer :: Request -> Either String String
tutAppReferrer :: ServerMonad m => ErrorT String m String
tutAppReferrer = do
  -- check against logout, otherwise if you have just logged out then 
  -- try immediately to log in again it won't let you. (???)
  rf <- ErrorT $ getHeaderVal "referer" `liftM` askRq
  return $ if or $ map (flip isInfixOf rf) ["logout", "login", "newuser"]
                         then "/"
                         else rf

-- getReferrer = getHeaderVal "referer" =<< askRq

-- Use a helper function because the plan is to eventually have a similar function
-- that works for admin logins
loginPage' :: (Monad m) =>
              (UserName -> B.ByteString -> ServerPartT m Bool)
              -> (RenderGlobals -> UserName -> String -> ServerPartT m Response)
              -> RenderGlobals
              -> String
              -> ServerPartT m Response
loginPage' auth startsession rglobs landingpage = do
  UserAuthInfo user pass <- getData'
  loginOk <- auth user pass
  if loginOk
   then startsession rglobs user landingpage
   else return $ errW rglobs "Invalid user or password"

-- check if a username and password is valid. If it is, return the user as successful monadic value
-- otherwise fail monadically
authUser' :: (UserName -> ServerPartT IO (Maybe String) ) -> UserName -> B.ByteString -> ServerPartT IO Bool
authUser' getpwd name pass = do
  mbP <- getpwd name
  -- scramblepass works with lazy bytestrings, maybe that's by design. meh, leave it for now
  -- to do: we need to use a seed, there was a discussion about this on haskell cafe.
  return $ maybe False ( == scramblepass (B.unpack pass) ) mbP


changePasswordSP :: RenderGlobals -> ServerPartT IO Response
changePasswordSP rglobs = do 
    ChangePasswordInfo newpass1 newpass2 <- getData'
    if newpass1 /= newpass2 
      then return $ errw "new passwords don't match"
      else do 
        etRes <- runErrorT $ getLoggedInUserInfos rglobs
        case etRes of
          Left e -> return $ errw e
          Right (u,_) ->  do     
            update $ ChangePassword u newpass1
            return $ tutlayoutU rglobs [] "accountsettings-changed"
  where errw msg = tutlayoutU rglobs [("errormsgAccountSettings", msg)] "changepassword"

processformEditConsultantProfile :: RenderGlobals -> ServerPartT IO Response
processformEditConsultantProfile = requireLogin $ \unB rglobs -> do 
 EditUserProfileFormData fdContact fdBlurb fdlistAsC fdimagecontents <- getData'
 mbUP <- query $ GetUserProfile unB
 case mbUP of
   Nothing -> return $ errW rglobs "error retrieving user infos"
   Just (UserProfile _ _ _ pAvatar) -> do
             up <- if B.null (fdimagecontents)
                    then return $ UserProfile fdContact fdBlurb fdlistAsC pAvatar
                    else do
                      let avatarpath = writeavatarpath unB
         -- to do: verify this handles errors, eg try writing to someplace we don't have permission,
         -- or a filename with spaces, whatever
                      liftIO $ writeFileForce avatarpath fdimagecontents
                      return $ UserProfile fdContact fdBlurb fdlistAsC (B.pack avatarpath)             
             update $ SetUserProfile unB up
             viewEditConsultantProfile rglobs

processformEditJob :: RenderGlobals -> ServerPartT IO Response
processformEditJob = requireLogin $ \uname rglobs -> do
  EditJob jn jbud jblu <- getData'
  if null (B.unpack . unjobname $ jn)
   then return $ errW rglobs "error, blank job name"
   else do 
    update $ SetJob uname (Job (B.pack jbud) (B.pack jblu)) jn
    viewEditJob uname jn rglobs

processformNewJob :: RenderGlobals -> ServerPartT IO Response
processformNewJob = requireLogin $ \user rglobs -> do 
 NewJobFormData jn newjob <- getData'
 if null (B.unpack . unjobname $ jn)
  then return $ errW rglobs "error, blank job name"
  else do 
   res <- update (AddJob user jn newjob)
   case res of 
     Left err -> if isInfixOf "duplicate key" (lc err) 
                  then return $ errW rglobs "duplicate job name"
                  else return $ errW rglobs "error inserting job"
     Right () -> pageMyJobPosts rglobs


newUserPage :: RenderGlobals -> ServerPartT IO Response
newUserPage rglobs = do
  NewUserInfo user pass1 pass2 <- getData'
  etRes <- runErrorT $ setupNewUser (NewUserInfo user (pass1 :: B.ByteString) pass2) 
  case etRes of
    Left err -> return $ errW rglobs err 
    Right () -> startsess' user "/tutorial/registered"
  where
    setupNewUser :: NewUserInfo -> ErrorT String (ServerPartT IO) ()
    setupNewUser (NewUserInfo user pass1 pass2) = do
        when (B.null pass1 || B.null pass2) (throwError "blank password")
        when (pass1 /= pass2) (throwError "passwords don't match")
          --  Q: can return . Left be replaced with throwError?
          --  A: no. But you can return just plain Left with throwError.
        nameTakenHAppSState <- query $ IsUser user
        when nameTakenHAppSState (throwError "name taken")
        addUserVerifiedPass user pass1 pass2  

addUserVerifiedPass :: UserName -> B.ByteString -> B.ByteString -> ErrorT String (ServerPartT IO) ()
addUserVerifiedPass user pass1 pass2 =
  ErrorT $ newuser user pass1 pass2
  where
    newuser :: UserName -> B.ByteString -> B.ByteString -> ServerPartT IO (Either String ())
    newuser u@(UserName _) p1 p2 -- userExists
      | p1 /= p2 = return . Left $  "passwords did not match"
      | otherwise = update $ AddUser u $ B.pack $ scramblepass (B.unpack p1)
