{-# OPTIONS_GHC -Wall #-}
module Elm.Compiler
    ( version
    , parseDependencies, Tag(..)
    , compile, Context(..), Result(..)
    , Localizer, dummyLocalizer
    , Error, errorToString, errorToJson, printError
    , Warning, warningToString, warningToJson, printWarning
    )
    where

import qualified Data.Aeson as Json
import qualified Data.Map as Map
import qualified Data.Text.Lazy as LazyText
import System.IO (Handle)

import qualified AST.Module as Module
import qualified AST.Module.Name as ModuleName
import qualified Compile
import qualified Docs.Check as Docs
import qualified Elm.Compiler.Version
import qualified Elm.Compiler.Module as PublicModule
import qualified Elm.Docs as Docs
import qualified Elm.Package as Package
import qualified Generate.JavaScript as JS
import qualified Parse.Module as Parse
import qualified Parse.Parse as Parse
import qualified Reporting.Annotation as A
import qualified Reporting.Bag as Bag
import qualified Reporting.Error as Error
import qualified Reporting.Render.Type as RenderType
import qualified Reporting.Report as Report
import qualified Reporting.Result as Result
import qualified Reporting.Warning as Warning



-- VERSION


version :: Package.Version
version =
  Elm.Compiler.Version.version



-- DEPENDENCIES


data Tag
  = Normal
  | Effect
  | Port


parseDependencies :: String -> Either [Error] (Tag, PublicModule.Raw, [PublicModule.Raw])
parseDependencies sourceCode =
  let
    (Result.Result _ _ answer) =
      Parse.parse sourceCode Parse.header

    getDeps (Module.Header sourceTag name _ _ _ imports) =
      let
        tag =
          case sourceTag of
            Module.Normal -> Normal
            Module.Port _ -> Port
            Module.Effect _ -> Effect
      in
        ( tag
        , name
        , map (fst . A.drop) imports
        )
  in
    Result.answerToEither (Error . A.map Error.Syntax) getDeps answer



-- COMPILATION


{-| Compiles Elm source code to JavaScript. -}
compile
    :: Context
    -> String
    -> PublicModule.Interfaces
    -> (Localizer, [Warning], Either [Error] Result)

compile context source interfaces =
  let
    (Context packageName isExposed dependencies) =
      context

    (Result.Result oneLocalizer warnings answer) =
      do  modul <- Compile.compile packageName dependencies interfaces source
          docs <- Result.format Error.Docs (docsGen isExposed modul)

          let interface = Module.toInterface packageName modul
          let javascript = JS.generate modul

          return (Result docs interface javascript)
  in
    ( Result.oneToValue dummyLocalizer Localizer oneLocalizer
    , Bag.toList Warning warnings
    , Result.answerToEither Error id answer
    )


data Context = Context
    { _packageName :: Package.Name
    , _isExposed :: Bool
    , _dependencies :: [PublicModule.Canonical]
    }


data Result = Result
    { _docs :: Maybe Docs.Documentation
    , _interface :: PublicModule.Interface
    , _js :: LazyText.Text
    }


docsGen :: Bool -> Module.Optimized -> Docs.Result w (Maybe Docs.Documentation)
docsGen isExposed (Module.Module name _ info) =
  if not isExposed then
    Result.ok Nothing

  else
    let
      toDocs checked =
        Just (Docs.fromCheckedDocs (ModuleName._module name) checked)
    in
      toDocs <$> Docs.check (Module.exports info) (Module.docs info)



-- DEALIASER


newtype Localizer =
    Localizer RenderType.Localizer


dummyLocalizer :: Localizer
dummyLocalizer =
    Localizer Map.empty



-- ERRORS


newtype Error =
    Error (A.Located Error.Error)


errorToString :: Localizer -> String -> String -> Error -> String
errorToString (Localizer localizer) location source (Error (A.A region err)) =
    Report.toString location region (Error.toReport localizer err) source


printError :: Handle -> Localizer -> String -> String -> Error -> IO ()
printError handle (Localizer localizer) location source (Error (A.A region err)) =
    Report.toHandle handle location region (Error.toReport localizer err) source


errorToJson :: Localizer -> String -> Error -> Json.Value
errorToJson (Localizer localizer) location (Error err) =
    Error.toJson localizer location err



-- WARNINGS


newtype Warning =
    Warning (A.Located Warning.Warning)


warningToString :: Localizer -> String -> String -> Warning -> String
warningToString (Localizer localizer) location source (Warning (A.A region wrn)) =
    Report.toString location region (Warning.toReport localizer wrn) source


printWarning :: Handle -> Localizer -> String -> String -> Warning -> IO ()
printWarning handle (Localizer localizer) location source (Warning (A.A region wrn)) =
    Report.toHandle handle location region (Warning.toReport localizer wrn) source


warningToJson :: Localizer -> String -> Warning -> Json.Value
warningToJson (Localizer localizer) location (Warning wrn) =
    Warning.toJson localizer location wrn


