{-# LANGUAGE RankNTypes #-}
-----------------------------------------------------------------------------
-- Copyright 2014, Open Universiteit Nederland. This file is distributed
-- under the terms of the GNU General Public License. For more information,
-- see the file "LICENSE.txt", which is included in the distribution.
-----------------------------------------------------------------------------
-- |
-- Maintainer  :  bastiaan.heeren@ou.nl
-- Stability   :  provisional
-- Portability :  portable (depends on ghc)
--
-- Main module for feedback services
--
-----------------------------------------------------------------------------
--  $Id: Default.hs 6541 2014-05-14 18:44:36Z bastiaan $

module Ideas.Main.Default
   ( defaultMain, newDomainReasoner
     -- extra exports
   , Some(..), serviceList, metaServiceList, Service
   , module Ideas.Service.DomainReasoner
   ) where

import Control.Exception
import Control.Monad
import Data.IORef
import Data.Maybe
import Data.Time
import Ideas.Common.Id
import Ideas.Common.Utils (useFixedStdGen, Some(..))
import Ideas.Common.Utils.TestSuite
import Ideas.Encoding.ModeJSON (processJSON)
import Ideas.Encoding.ModeXML (processXML)
import Ideas.Main.BlackBoxTests
import Ideas.Main.Documentation
import Ideas.Main.LoggingDatabase
import Ideas.Main.Options hiding (fullVersion)
import Ideas.Service.DomainReasoner
import Ideas.Service.FeedbackScript.Analysis
import Ideas.Service.Request
import Ideas.Service.ServiceList
import Ideas.Service.Types (Service)
import Network.CGI
import Prelude hiding (catch)
import System.IO
import System.IO.Error (ioeGetErrorString)
import qualified Ideas.Main.Options as Options

defaultMain :: DomainReasoner -> IO ()
defaultMain dr = do
   startTime <- getCurrentTime
   flags     <- getFlags
   if null flags
      then defaultCGI dr startTime
      else defaultCommandLine dr flags

-- Invoked as a cgi binary
defaultCGI :: DomainReasoner -> UTCTime -> IO ()
defaultCGI dr startTime = do
   logRef <- newIORef (return ())
   runCGI $ do
      addr   <- remoteAddr       -- the IP address of the remote host making the request
      cgiBin <- scriptName       -- get name of binary
      raw    <- getInput "input" -- read input
      input  <- case raw of
                   Nothing -> fail "Invalid request: environment variable \"input\" is empty"
                   Just s  -> return s
      (req, txt, ctp) <- liftIO $ process dr (Just cgiBin) input
      -- save logging action for later
      when (useLogging req) $
         liftIO $ writeIORef logRef $
            logMessage req input txt addr startTime
      setHeader "Content-type" ctp
      -- Cross-Origin Resource Sharing (CORS) prevents browser warnings
      -- about cross-site scripting
      setHeader "Access-Control-Allow-Origin" "*"
      output txt
   -- log request to database
   join (readIORef logRef)
   -- if something goes wrong
 `catch` \ioe -> runCGI $ do
   setHeader "Content-type" "text/plain"
   setHeader "Access-Control-Allow-Origin" "*"
   output ("Invalid request\n" ++ ioeGetErrorString ioe)

-- Invoked from command-line with flags
defaultCommandLine :: DomainReasoner -> [Flag] -> IO ()
defaultCommandLine dr flags = do
   hSetBinaryMode stdout True
   useFixedStdGen -- always use a predictable "random" number generator
   mapM_ doAction flags
 where
   doAction flag =
      case flag of
         -- information
         Version -> putStrLn ("IDEAS, " ++ versionText)
         Help    -> putStrLn helpText
         -- process input file
         InputFile file ->
            withBinaryFile file ReadMode $ \h -> do
               input <- hGetContents h
               (_, txt, _) <- process dr Nothing input
               putStrLn txt
         -- blackbox tests
         Test dir -> do
            tests  <- blackBoxTests dr dir
            result <- runTestSuiteResult True tests
            printSummary result
         -- generate documentation pages
         MakePages dir ->
            makeDocumentation dr dir
         -- feedback scripts
         MakeScriptFor s    -> makeScriptFor dr s
         AnalyzeScript file -> parseAndAnalyzeScript dr file

process :: DomainReasoner -> Maybe String -> String -> IO (Request, String, String)
process dr cgiBin input =
   case discoverDataFormat input of
      Just XML  -> processXML (Just 5) dr cgiBin input
      Just JSON -> processJSON (Just 5) (isJust cgiBin) dr input
      _ -> fail "Invalid input"

newDomainReasoner :: IsId a => a -> DomainReasoner
newDomainReasoner a = mempty
   { reasonerId  = newId a
   , version     = shortVersion
   , fullVersion = Options.fullVersion
   }