{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards   #-}
{-# LANGUAGE ViewPatterns      #-}
module Dhall.Freeze
    ( 
      freeze
    , freezeImport
    , freezeRemoteImport
      
    , Scope(..)
    , Intent(..)
    ) where
import Data.Monoid ((<>))
import Data.Text
import Dhall.Parser (Src)
import Dhall.Pretty (CharacterSet)
import Dhall.Syntax (Expr(..), Import(..), ImportHashed(..), ImportType(..))
import Dhall.Util (Censor, Input(..))
import System.Console.ANSI (hSupportsANSI)
import qualified Control.Exception
import qualified Control.Monad.Trans.State.Strict          as State
import qualified Data.Text.Prettyprint.Doc                 as Pretty
import qualified Data.Text.Prettyprint.Doc.Render.Terminal as Pretty
import qualified Data.Text.Prettyprint.Doc.Render.Text     as Pretty.Text
import qualified Dhall.Core                                as Core
import qualified Dhall.Import
import qualified Dhall.Optics
import qualified Dhall.Pretty
import qualified Dhall.TypeCheck
import qualified Dhall.Util
import qualified System.AtomicWrite.Writer.LazyText        as AtomicWrite.LazyText
import qualified System.FilePath
import qualified System.IO
freezeImport
    :: FilePath
    
    -> Import
    -> IO Import
freezeImport directory import_ = do
    let unprotectedImport =
            import_
                { importHashed =
                    (importHashed import_)
                        { hash = Nothing
                        }
                }
    let status = Dhall.Import.emptyStatus directory
    expression <- State.evalStateT (Dhall.Import.loadWith (Embed unprotectedImport)) status
    case Dhall.TypeCheck.typeOf expression of
        Left  exception -> Control.Exception.throwIO exception
        Right _         -> return ()
    let normalizedExpression = Core.alphaNormalize (Core.normalize expression)
    
    Dhall.Import.writeExpressionToSemanticCache (Core.denote expression)
    let expressionHash = Dhall.Import.hashExpression normalizedExpression
    let newImportHashed = (importHashed import_) { hash = Just expressionHash }
    let newImport = import_ { importHashed = newImportHashed }
    return newImport
freezeRemoteImport
    :: FilePath
    
    -> Import
    -> IO Import
freezeRemoteImport directory import_ = do
    case importType (importHashed import_) of
        Remote {} -> freezeImport directory import_
        _         -> return import_
writeExpr :: Input -> (Text, Expr Src Import) -> CharacterSet -> IO ()
writeExpr inplace (header, expr) characterSet = do
    let doc =  Pretty.pretty header
            <> Dhall.Pretty.prettyCharacterSet characterSet expr
            <> "\n"
    let stream = Dhall.Pretty.layout doc
    let unAnnotated = Pretty.unAnnotateS stream
    case inplace of
        InputFile file ->
            AtomicWrite.LazyText.atomicWriteFile
                file
                (Pretty.Text.renderLazy unAnnotated)
        StandardInput -> do
            supportsANSI <- System.Console.ANSI.hSupportsANSI System.IO.stdout
            if supportsANSI
               then
                 Pretty.renderIO System.IO.stdout (Dhall.Pretty.annToAnsiStyle <$> stream)
               else
                 Pretty.renderIO System.IO.stdout unAnnotated
data Scope
    = OnlyRemoteImports
    
    | AllImports
    
data Intent
    = Secure
    
    
    | Cache
    
    
    
    
freeze
    :: Input
    -> Scope
    -> Intent
    -> CharacterSet
    -> Censor
    -> IO ()
freeze inplace scope intent characterSet censor = do
    let directory = case inplace of
            StandardInput  -> "."
            InputFile file -> System.FilePath.takeDirectory file
    (Dhall.Util.Header header, parsedExpression) <-
        Dhall.Util.getExpressionAndHeader censor inplace
    let freezeScope =
            case scope of
                AllImports        -> freezeImport
                OnlyRemoteImports -> freezeRemoteImport
    let freezeFunction = freezeScope directory
    let cache
            (ImportAlt
                (Core.shallowDenote -> Embed
                    (Import { importHashed = ImportHashed { hash = Just _expectedHash } })
                )
                import_@(Core.shallowDenote -> ImportAlt
                    (Embed
                        (Import { importHashed = ImportHashed { hash = Just _actualHash } })
                    )
                    _
                )
            ) = do
                
                return import_
        cache
            (Embed import_@(Import { importHashed = ImportHashed { hash = Nothing } })) = do
                frozenImport <- freezeFunction import_
                
                if frozenImport /= import_
                    then return (ImportAlt (Embed frozenImport) (Embed import_))
                    else return (Embed import_)
        cache expression = do
            return expression
    let rewrite expression =
            case intent of
                Secure ->
                    traverse freezeFunction expression
                Cache  ->
                    Dhall.Optics.transformMOf
                        Core.subExpressions
                        cache
                        expression
    frozenExpression <- rewrite parsedExpression
    writeExpr inplace (header, frozenExpression) characterSet