{-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Main where
import Citeproc
import Citeproc.CslJson
import Control.Monad (when, unless, foldM)
import Control.Applicative ((<|>))
import Data.Bifunctor (second)
import Data.Maybe (fromMaybe)
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.IO as TIO
import qualified Data.ByteString.Lazy as BL
import qualified Data.Aeson as Aeson
import Data.Aeson.Encode.Pretty as AesonPretty
import Data.Ord (comparing)
import System.IO
import System.Exit
import System.Environment
import System.Console.GetOpt

main :: IO ()
main = do
  rawargs <- getArgs
  let (opts, args, errs) = getOpt Permute options rawargs
  unless (null errs) $ do
    mapM_ err errs
    exitWith $ ExitFailure 1
  opt <- foldM (flip ($)) defaultOpt opts
  when (optHelp opt) $ do
    putStr $ usageInfo "citeproc [OPTIONS] [FILE]" options
    exitSuccess
  when (optVersion opt) $ do
    putStrLn $ "citeproc version " ++ VERSION_citeproc
    exitSuccess
  format <- case optFormat opt of
              Just "html" -> return Html
              Just "json" -> return Json
              Just _      -> err "--format must be html or json"
              Nothing     -> return Html
  bs <- case args of
          [] -> BL.getContents
          (f:_) -> BL.readFile f
  case Aeson.eitherDecode bs of
    Left e -> err e
    Right (inp :: Inputs (CslJson Text)) -> do
      stylesheet <- case optStyle opt of
                      Just fp -> T.dropWhile (=='\xFEFF') <$> -- drop BOM
                                       TIO.readFile fp
                      Nothing ->
                        case inputsStyle inp of
                          Just s  -> return s
                          Nothing -> err "No style specified"
      references <- case optReferences opt of
                      Just fp -> do
                        raw <- BL.readFile fp
                        case Aeson.eitherDecode raw of
                          Left e   -> err e
                          Right rs -> return rs
                      Nothing ->
                        case inputsReferences inp of
                          Just rs  -> return rs
                          Nothing  -> err "No references specified"
      abbreviations <- case optAbbreviations opt of
                          Just fp -> do
                            raw <- BL.readFile fp
                            case Aeson.eitherDecode raw of
                              Left e   -> err e
                              Right ab -> return $ Just ab
                          Nothing -> return $ inputsAbbreviations inp
      let lang = optLang opt <|> inputsLang inp

      parseResult <-
        parseStyle (\_ -> return mempty) stylesheet
      case parseResult of
        Left e -> err (T.unpack $ prettyCiteprocError e)
        Right parsedStyle -> do
          let style = parsedStyle{ styleAbbreviations = abbreviations }
          let result=
                citeproc
                  defaultCiteprocOptions
                    { linkCitations = optLinkCitations opt
                    , linkBibliography = optLinkBibliography opt
                    }
                  style
                  lang
                  references
                  (fromMaybe [] (inputsCitations inp))
          let jsonResult :: Aeson.Value
              jsonResult =
                case format of
                   Json -> Aeson.object
                          [ ("citations", Aeson.toJSON $
                               map cslJsonToJson
                                   (resultCitations result))
                          , ("bibliography", Aeson.toJSON $
                               map (second cslJsonToJson)
                                   (resultBibliography result))
                          , ("warnings", Aeson.toJSON $ resultWarnings result)
                          ]
                   Html -> Aeson.object
                          [ ("citations", Aeson.toJSON $
                               map cslJsonToHtml
                                   (resultCitations result))
                          , ("bibliography", Aeson.toJSON $
                               map (second cslJsonToHtml)
                                   (resultBibliography result))
                          , ("warnings", Aeson.toJSON $ resultWarnings result)
                          ]
                          where cslJsonToHtml el =
                                  renderCslJson True mempty el
          BL.putStr $ AesonPretty.encodePretty'
                       AesonPretty.defConfig
                         { confIndent = AesonPretty.Spaces 2
                         , confCompare = AesonPretty.keyOrder
                             ["citations","bibliography","warnings"]
                             `mappend` comparing T.length
                         , confTrailingNewline = True }
                       jsonResult

data Format = Json | Html deriving (Show, Ord, Eq)

data Opt =
  Opt{ optStyle            :: Maybe String
     , optReferences       :: Maybe String
     , optAbbreviations    :: Maybe String
     , optFormat           :: Maybe String
     , optLang             :: Maybe Lang
     , optLinkCitations    :: Bool
     , optLinkBibliography :: Bool
     , optHelp             :: Bool
     , optVersion          :: Bool
     } deriving Show

defaultOpt :: Opt
defaultOpt =
  Opt { optStyle = Nothing
      , optReferences = Nothing
      , optAbbreviations = Nothing
      , optFormat = Nothing
      , optLang = Nothing
      , optLinkCitations = False
      , optLinkBibliography = False
      , optHelp = False
      , optVersion = False
      }

options :: [OptDescr (Opt -> IO Opt)]
options =
  [ Option ['s'] ["style"]
     (ReqArg (\fp opt -> return opt{ optStyle = Just fp }) "FILE")
     "CSL style file"
  , Option ['r'] ["references"]
     (ReqArg (\fp opt -> return opt{ optReferences = Just fp }) "FILE")
     "CSL JSON bibliography"
  , Option ['a'] ["abbreviations"]
     (ReqArg (\fp opt -> return opt{ optAbbreviations = Just fp }) "FILE")
     "CSL abbreviations table"
  , Option ['l'] ["lang"]
     (ReqArg (\lang opt ->
                 case parseLang (T.pack lang) of
                   Right l  -> return opt{ optLang = Just l }
                   Left msg -> err $ "Could not parse language tag:\n" ++ msg)
        "BCP 47 language tag")
     "Override locale"
  , Option ['f'] ["format"]
     (ReqArg (\format opt -> return opt{ optFormat = Just format }) "html|json")
     "Controls formatting of entries in result"
  , Option [] ["link-citations"]
     (NoArg (\opt -> return opt{ optLinkCitations = True }))
     "Enable hyperlinking of citations to the bibliography"
  , Option [] ["link-bibliography"]
     (NoArg (\opt -> return opt{ optLinkBibliography = True }))
     "Enable hyperlinking of bibliography entries"
  , Option ['h'] ["help"]
     (NoArg (\opt -> return opt{ optHelp = True }))
     "Print usage information"
  , Option ['V'] ["version"]
     (NoArg (\opt -> return opt{ optVersion = True }))
     "Print version number"
  ]

err :: String -> IO a
err s = do
  hPutStrLn stderr s
  exitWith $ ExitFailure 1
