{-# LANGUAGE CPP #-}
import Text.Highlighting.Illuminate
import System.Environment
import qualified Text.Html as H
import qualified Text.XHtml as X
import Control.Monad
-- Note: ghc >= 6.12 (base >=4.2) supports unicode through iconv
-- So we use System.IO.UTF8 only if we have an earlier version
#if MIN_VERSION_base(4,2,0)
import Prelude
import System.IO (hPutStrLn)
#else
import Prelude hiding (putStr, putStrLn, readFile, getContents, writeFile)
import System.IO.UTF8
#endif
import System.IO (stderr)
import System.Exit
import Data.List (intercalate)
import Data.Char (toLower)
import System.Console.GetOpt

data Flag = Help
          | Fragment
          | Version
          | List
          | NumberLines
          | StartNum String
          | Outfile String
          | Syntax String
          | Style String
          | Format String 
          deriving (Eq, Show)

cmdOpts :: [OptDescr Flag]
cmdOpts =
  [ Option ['h'] ["help"] (NoArg Help)   "show usage message"
  , Option ['v'] ["version"] (NoArg Version)   "print version"
  , Option ['f'] ["fragment"] (NoArg Fragment)  "fragment, without document header"
  , Option ['l'] ["list"] (NoArg List)   "list available language syntaxes"
  , Option ['n'] ["number"] (NoArg NumberLines)  "number lines"
  , Option []    ["start"] (ReqArg StartNum "NUMBER")  "start numbering at"
  , Option ['o'] ["outfile"] (ReqArg Outfile "FILE")  "name of output file"
  , Option []    ["syntax"] (ReqArg Syntax "SYNTAX")  "specify language syntax to use"
  , Option ['s'] ["style"] (ReqArg Style "STYLE")  "specify highlighting style"
  , Option ['t'] ["to"] (ReqArg Format "FORMAT")  "to format [ansi|latex|html|xhtml|htmlcss|xhtmlcss]"
  ]

getSyntax :: [Flag] -> Maybe String
getSyntax [] = Nothing
getSyntax (Syntax s : _) = Just s
getSyntax (_:xs) = getSyntax xs

getStartNum :: [Flag] -> Int
getStartNum [] = 1
getStartNum (StartNum n : _) = read n
getStartNum (_:xs) = getStartNum xs

getOutfile :: [Flag] -> String
getOutfile [] = "-" 
getOutfile (Outfile f : _) = f
getOutfile (_:xs) = getOutfile xs

getFormat :: [Flag] -> String
getFormat [] = "ansi"
getFormat (Format f : _) = map toLower f
getFormat (_:xs) = getFormat xs

getStyle :: [Flag] -> String
getStyle [] = "colorful" 
getStyle (Style s : _) = map toLower s
getStyle (_:xs) = getStyle xs

toUnixLineEndings :: String -> String
toUnixLineEndings = filter (/='\r')

main :: IO ()
main = do
  (opts, fnames, errs) <- getArgs >>= return . getOpt Permute cmdOpts
  prg <- getProgName
  let usageHeader = prg ++ " [options] [files...]"
  
  when (not . null $ errs) $
    ioError (userError $ concat errs ++ usageInfo usageHeader cmdOpts)

  let showLexer l = putStrLn $ name l ++ " (" ++
                               intercalate ", " (aliases l) ++ "): " ++
                               intercalate ", " (filenames l) 
  when (List `elem` opts) $
     mapM_ showLexer lexers >> exitWith ExitSuccess

  when (Help `elem` opts) $
     hPutStrLn stderr (usageInfo usageHeader cmdOpts) >>
     exitWith (ExitFailure 1)

  when (Version `elem` opts) $
     -- TODO add version using cabal
     putStrLn (prg ++ " " ++ " -- (c) 2010 John MacFarlane") >> 
     exitWith ExitSuccess

  s <- case fnames of
          []    -> getContents
          (x:_) -> readFile x

  let lexer = case getSyntax opts of
                  Just x  -> lexerByName x
                  Nothing -> case fnames of
                                []    -> Nothing
                                (x:_) -> lexerByFilename x

  let (output, fname) = case getOutfile opts of
                              "-"   -> (putStr, "-")
                              f     -> (writeFile f, f)

  let numberlines = NumberLines `elem` opts
  let startnum = getStartNum opts
  let fragment = Fragment `elem` opts

  style <- case getStyle opts of
                 "colorful"   -> return colorful
                 "hscolour"   -> return hscolour
                 "monochrome" -> return monochrome
                 x            -> hPutStrLn stderr ("Unknown style `" ++ x ++ "'") >>
                                 exitWith (ExitFailure 3)

  let options = defaultOptions { optStyle = style
                               , optNumberLines = numberlines
                               , optStartNumber = startnum
                               , optAnchors = numberlines }

  let inHtml includes x = if fragment
                             then foldr (H.renderHtml' 0) "" (H.getHtmlElements x)
                             else H.renderHtml $ H.header H.<< includes H.+++ H.body H.<< x
  let inXHtml includes x = if fragment
                              then X.showHtmlFragment x
                              else X.showHtml $ X.header X.<< includes X.+++ X.body X.<< x

  let addLaTeXHeadFoot x = if fragment then x else unlines
                           [ "\\documentclass{report}"
                           , "\\usepackage{fancyvrb}"
                           , "\\usepackage[usenames,dvipsnames]{color}"
                           , "\\begin{document}"
                           , x
                           , "\\end{document}" ]

  let tokens = case tokenize lexer $ toUnixLineEndings s of
                    Right toks  -> toks
                    Left err    -> error $ show err

  case getFormat opts of
         "html"       -> output $ inHtml [H.thetitle H.<< fname] $
                         toHtmlInline options tokens
         "xhtml"      -> output $ inXHtml [X.thetitle X.<< fname] $
                         toXHtmlInline options tokens
         "htmlcss"    -> output $ inHtml [ H.thetitle H.<< fname
                                         , H.style H.! [H.thetype "text/css"] H.<<
                                             cssFor options ] $
                         toHtmlCSS options tokens
         "xhtmlcss"   -> output $ inXHtml [ X.thetitle X.<< fname
                                          , X.style X.! [X.thetype "text/css"] X.<<
                                             cssFor options ] $
                         toXHtmlCSS options tokens
         "latex"      -> output $ addLaTeXHeadFoot $ toLaTeX options tokens
         "ansi"       -> output $ toANSI options tokens
         x            -> hPutStrLn stderr ("Unknown format `" ++ x ++ "'") >>
                         exitWith (ExitFailure 5)

