module Reporting.Result where

import Control.Applicative ((<|>))
import qualified Control.Monad as M
import Control.Monad.Except (Except, runExcept)

import qualified Reporting.Annotation as A
import qualified Reporting.Region as R
import qualified Reporting.PrettyPrint as P


-- TASK

data Result warning error result =
    Result
      (Maybe P.Dealiaser, [A.Located warning])
      (RawResult [A.Located error] result)
    deriving (Show)


data RawResult e a
    = Ok a
    | Err e
    deriving (Show)


-- HELPERS

ok :: a -> Result w e a
ok value =
  Result (Nothing, []) (Ok value)


throw :: R.Region -> e -> Result w e a
throw region err =
  Result (Nothing, []) (Err [A.A region err])


throwMany :: [A.Located e] -> Result w e a
throwMany errors =
  Result (Nothing, []) (Err errors)


from :: (e -> e') -> Except [A.Located e] a -> Result w e' a
from f except =
  case runExcept except of
    Right answer ->
        ok answer

    Left errors ->
        throwMany (map (A.map f) errors)


mapError :: (e -> e') -> Result w e a -> Result w e' a
mapError f (Result warnings rawResult) =
  Result warnings $
    case rawResult of
      Ok v ->
          Ok v

      Err msgs ->
          Err (map (A.map f) msgs)


warn :: R.Region -> w -> Result w e ()
warn region warning =
  Result (Nothing, [A.A region warning]) (Ok ())


addWarnings :: [A.Located w] -> Result w e a -> Result w e a
addWarnings newWarnings (Result (dealiaser, warnings) rawResult) =
  Result (dealiaser, newWarnings ++ warnings) rawResult


addDealiaser :: P.Dealiaser -> Result w e a -> Result w e a
addDealiaser dealiaser (Result (_, warnings) rawResult) =
  Result (Just dealiaser, warnings) rawResult


destruct :: (e -> b) -> (a -> b) -> RawResult e a -> b
destruct errFunc okFunc rawResult =
  case rawResult of
    Ok answer ->
        okFunc answer

    Err errors ->
        errFunc errors


-- EXTRA FANCY HELPERS

instance M.Functor (Result w e) where
  fmap func (Result warnings rawResult) =
      case rawResult of
        Ok a ->
            Result warnings (Ok (func a))

        Err msgs ->
            Result warnings (Err msgs)


instance Applicative (Result w e) where
  pure value =
      ok value

  (<*>) (Result context resultFunc) (Result context' resultVal) =
      Result (merge context context') $
          case (resultFunc, resultVal) of
            (Ok func, Ok val) ->
                Ok (func val)

            (Err msgs, Err msgs') ->
                Err (msgs ++ msgs')

            (Err msgs, _) ->
                Err msgs

            (_, Err msgs) ->
                Err msgs


instance M.Monad (Result w e) where
  return value =
      ok value

  (>>=) (Result context rawResult) callback =
      case rawResult of
          Err msg ->
              Result context (Err msg)

          Ok value ->
              let (Result context' rawResult') = callback value
              in
                  Result (merge context context') rawResult'


merge :: (Maybe a, [b]) -> (Maybe a, [b]) -> (Maybe a, [b])
merge (dealiaser, warnings) (dealiaser', warnings') =
  ( dealiaser <|> dealiaser'
  , warnings ++ warnings'
  )
