content: {-# LANGUAGE ScopedTypeVariables #-}
  {-# LANGUAGE Strict #-}
  module Main where
  
  import qualified Data.ByteString as B
  import Data.ByteString.Builder (hPutBuilder)
  import Djot ( ParseOptions(..), RenderOptions(..), SourcePosOption(..),
                parseDoc, renderHtml, renderDjot )
  import System.Environment (getArgs)
  import System.IO (stderr, stdout, hPutStrLn)
  import System.Exit ( ExitCode(ExitFailure), exitWith, exitSuccess )
  import Text.DocLayout (render)
  import Text.Read (readMaybe)
  import qualified Data.Text.IO as TIO
  
  data OutputFormat = Html | Djot | Ast
    deriving (Eq, Show)
  
  data WrapOption = Auto | Preserve | NoWrap
    deriving (Eq, Show)
  
  data Opts =
        Opts{ format :: OutputFormat
            , files :: [FilePath]
            , wrap :: WrapOption
            , columns :: Int
            , sourcePos :: SourcePosOption }
  
  parseOpts :: [String] -> IO Opts
  parseOpts = go Opts{ format = Html, files = [], wrap = Preserve, columns = 72,
                       sourcePos = NoSourcePos }
   where
     go opts [] = pure opts
     go opts ("--wrap" : as) =
       case as of
         "auto" : as' -> go opts{ wrap = Auto } as'
         "preserve" : as' -> go opts{ wrap = Preserve } as'
         "none" : as' -> go opts{ wrap = NoWrap } as'
         _ -> err "--wrap must be followed by auto, preserve, or none"
     go opts ("--columns" : as) =
       case as of
         (a:as') | Just n <- readMaybe a
           -> go opts{ columns = n } as'
         _ -> err "--columns must be followed by a number"
     go opts ("--to" : as) =
       case as of
         "djot" : as' -> go opts{ format = Djot } as'
         "html" : as' -> go opts{ format = Html } as'
         "ast" : as' -> go opts{ format = Ast } as'
         _ -> err "--to must be followed by djot, html, or ast"
     go opts ("--sourcepos" : as) =
       case as of
         ("none":as') -> go opts{ sourcePos = NoSourcePos } as'
         ("block":as') -> go opts{ sourcePos = BlockSourcePos } as'
         ("all":as') -> go opts{ sourcePos = AllSourcePos } as'
         _ -> err "--sourcepos takes an argument (none|block|all)"
     go _opts ("--help" : _) = do
       putStrLn "djoths [options] [files]"
       putStrLn "  --to djot|html*|ast"
       putStrLn "  --wrap auto|preserve*|none"
       putStrLn "  --columns NUMBER"
       putStrLn "  --sourcepos none*|block|all"
       putStrLn "  --help"
       exitSuccess
     go opts (xs@('-':_) : as) =
       case break (== '=') xs of  -- support e.g. '--columns=33'
         (ys, '=':zs) -> go opts (ys : zs : as)
         _ -> err $ "Unknown option " <> ('-':xs)
     go opts (f : as) = go opts{ files = files opts ++ [f] } as
  
  err :: String -> IO a
  err msg = do
    hPutStrLn stderr msg
    exitWith $ ExitFailure 1
  
  main :: IO ()
  main = do
    opts <- getArgs >>= parseOpts
    bs <- case files opts of
            [] -> B.getContents
            fs  -> mconcat <$> mapM B.readFile fs
    let popts = ParseOptions { sourcePositions = sourcePos opts }
    let ropts = RenderOptions { preserveSoftBreaks = wrap opts == Preserve }
    case parseDoc popts bs of
      Right doc -> do
        case format opts of
          Html -> hPutBuilder stdout $ renderHtml ropts doc
          Djot -> TIO.putStr $ render (case wrap opts of
                                         NoWrap -> Nothing
                                         Preserve -> Nothing
                                         Auto -> Just (columns opts))
                             $ renderDjot ropts doc
          Ast -> print doc
        exitSuccess
      Left e -> do
        hPutStrLn stderr e
        exitWith $ ExitFailure 1
  