{-# LANGUAGE DeriveAnyClass    #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards   #-}

-- | This module contains the implementation of the @dhall format@ subcommand

module Dhall.Format
    ( -- * Format
      Format(..)
    , FormatMode(..)
    , format
    ) where

import Control.Exception (Exception)
import Data.Monoid ((<>))
import Dhall.Pretty (CharacterSet(..), annToAnsiStyle)
import Dhall.Util (Censor, Input(..), Header(..))

import qualified Data.Text.Prettyprint.Doc                 as Pretty
import qualified Data.Text.Prettyprint.Doc.Render.Terminal as Pretty.Terminal
import qualified Data.Text.Prettyprint.Doc.Render.Text     as Pretty.Text
import qualified Control.Exception
import qualified Data.Text.IO
import qualified Dhall.Pretty
import qualified Dhall.Util
import qualified System.AtomicWrite.Writer.LazyText        as AtomicWrite.LazyText
import qualified System.Console.ANSI
import qualified System.IO

data NotFormatted = NotFormatted
    deriving (Exception)

instance Show NotFormatted where
    show _ = ""

-- | Arguments to the `format` subcommand
data Format = Format
    { characterSet :: CharacterSet
    , censor       :: Censor
    , formatMode   :: FormatMode
    }

{-| The `format` subcommand can either `Modify` its input or simply `Check`
    that the input is already formatted
-}
data FormatMode
    = Modify { inplace :: Input }
    | Check  { path :: Input }

-- | Implementation of the @dhall format@ subcommand
format
    :: Format
    -> IO ()
format (Format {..}) = do
    let layoutHeaderAndExpr (Header header, expr) =
            Dhall.Pretty.layout
                (   Pretty.pretty header
                <>  Dhall.Pretty.prettyCharacterSet characterSet expr
                <>  "\n")

    let layoutInput input = do
            headerAndExpr <- Dhall.Util.getExpressionAndHeader censor input

            return (layoutHeaderAndExpr headerAndExpr)

    case formatMode of
        Modify {..} -> do
            docStream <- layoutInput inplace

            case inplace of
                InputFile file -> do
                    AtomicWrite.LazyText.atomicWriteFile
                        file
                        (Pretty.Text.renderLazy docStream)

                StandardInput -> do
                    supportsANSI <- System.Console.ANSI.hSupportsANSI System.IO.stdout

                    Pretty.Terminal.renderIO
                        System.IO.stdout
                        (if supportsANSI
                            then (fmap annToAnsiStyle docStream)
                            else (Pretty.unAnnotateS docStream))

        Check {..} -> do
            originalText <- case path of
                InputFile file -> Data.Text.IO.readFile file
                StandardInput  -> Data.Text.IO.getContents

            docStream <- case path of
                InputFile _    -> layoutInput path
                StandardInput  -> do
                    headerAndExpr <- Dhall.Util.getExpressionAndHeaderFromStdinText censor originalText
                    return (layoutHeaderAndExpr headerAndExpr)

            let formattedText = Pretty.Text.renderStrict docStream

            if originalText == formattedText
                then return ()
                else Control.Exception.throwIO NotFormatted