{-# LANGUAGE OverloadedStrings #-}

module Dhall.Freeze (
      freeze
    , hashImport
    ) where

import Dhall.Core
import Dhall.Import (load, hashExpression)
import Dhall.Parser (exprAndHeaderFromText, Src)
import Dhall.Pretty (annToAnsiStyle)

import System.Console.ANSI (hSupportsANSI)
import Data.Monoid ((<>))
import Data.Maybe (fromMaybe)
import Data.Text

import qualified Data.Text.Prettyprint.Doc                 as Pretty
import qualified Data.Text.Prettyprint.Doc.Render.Terminal as Pretty
import qualified Control.Exception
import qualified Data.Text.IO
import qualified System.IO

opts :: Pretty.LayoutOptions
opts =
    Pretty.defaultLayoutOptions
        { Pretty.layoutPageWidth = Pretty.AvailablePerLine 80 1.0 }

readInput :: Maybe FilePath -> IO Text
readInput = maybe fromStdin Data.Text.IO.readFile
    where
        fromStdin = System.IO.hSetEncoding System.IO.stdin System.IO.utf8 >> Data.Text.IO.getContents

hashImport :: Import -> IO Import
hashImport import_ = do
    expression <- Dhall.Import.load (Embed import_)
    let expressionHash = Just (Dhall.Import.hashExpression expression)
    let newImportHashed = (importHashed import_) { hash = expressionHash }
    return $ import_ { importHashed = newImportHashed }

parseExpr :: String -> Text -> IO (Text, Expr Src Import)
parseExpr src txt =
    case exprAndHeaderFromText src txt of
        Left err -> Control.Exception.throwIO err
        Right x  -> return x

freezeExpr :: (Text, Expr s Import) -> IO (Text, Expr s Import)
freezeExpr (t, e) = do
    e' <- traverse hashImport e
    return (t, e')

writeExpr :: Maybe FilePath -> (Text, Expr s Import) -> IO ()
writeExpr inplace (header, expr) = do
    let doc = Pretty.pretty header <> Pretty.pretty expr
    let layoutOptions = opts
    let stream = Pretty.layoutSmart layoutOptions doc

    case inplace of
        Just f ->
            System.IO.withFile f System.IO.WriteMode (\h ->
                Pretty.renderIO h (annToAnsiStyle <$> stream))

        Nothing -> do
            supportsANSI <- System.Console.ANSI.hSupportsANSI System.IO.stdout
            if supportsANSI
               then
                 Pretty.renderIO System.IO.stdout (annToAnsiStyle <$> Pretty.layoutSmart opts doc)
               else
                 Pretty.renderIO System.IO.stdout (Pretty.layoutSmart opts (Pretty.unAnnotate doc))

freeze :: Maybe FilePath -> IO ()
freeze inplace = do
    expr <- readInput inplace
    parseExpr srcInfo expr >>= freezeExpr >>= writeExpr inplace
        where
            srcInfo = fromMaybe "(stdin)" inplace