module Text.Trifecta.Diagnostic 
  ( DiagnosticLevel(..)
  , Diagnostic(..)
  ) where

import Control.Applicative
import Control.Comonad
import Data.Bifunctor
import Data.Functor.Apply
import Data.Bifoldable
import Data.Bitraversable
import Data.Foldable
import Data.Traversable
import Data.Monoid
import Data.List.NonEmpty hiding (map)
import Data.Semigroup
import Data.Semigroup.Foldable
import Data.Semigroup.Traversable
import Data.Semigroup.Bifoldable
import Data.Semigroup.Bitraversable
import Text.Trifecta.Bytes
import Text.Trifecta.Delta
import Text.Trifecta.Render
import Text.PrettyPrint.Free
import System.Console.Terminfo.PrettyPrint

data DiagnosticLevel = Note | Warning | Error | Fatal
  deriving (Eq,Ord,Show,Read)

instance Semigroup DiagnosticLevel where
  (<>) = max

instance Pretty DiagnosticLevel where
  pretty p = prettyTerm p *> empty

instance PrettyTerm DiagnosticLevel where
  prettyTerm Note    = text "note"
  prettyTerm Warning = magenta $ text "warning"
  prettyTerm Error   = red $ text "error"
  prettyTerm Fatal   = red $ text "fatal"

data Diagnostic l m = Diagnostic !Render l m [Diagnostic l m]

instance Renderable (Diagnostic l m) where
  render (Diagnostic r _ _ _) = r

instance HasDelta (Diagnostic l m) where
  delta (Diagnostic r _ _ _) = delta r

instance HasBytes (Diagnostic l m) where
  bytes = bytes . delta

instance Extend (Diagnostic l) where
  extend f d@(Diagnostic r l _ xs) = Diagnostic r l (f d) (map (extend f) xs)

instance Comonad (Diagnostic l) where
  extract (Diagnostic _ _ m _) = m

instance (Pretty l, Pretty m) => Pretty (Diagnostic l m) where
  pretty (Diagnostic r l m xs) = vsep $
    [ pretty (delta r) <> char ':' <+> pretty l <> char ':' <+> pretty m
    , pretty r
    ] ++ if Prelude.null xs then [] else [indent 2 (prettyList xs)]
  prettyList = Prelude.foldr ((<>) . pretty) empty

instance (PrettyTerm l, PrettyTerm m) => PrettyTerm (Diagnostic l m) where
  prettyTerm (Diagnostic r l m xs) = vsep $ 
    [ prettyTerm (delta r) <> char ':' <+> prettyTerm l <> char ':' <+> prettyTerm m
    , prettyTerm r
    ] ++ if Prelude.null xs then [] else [indent 2 (prettyTermList xs)]
  prettyTermList = Prelude.foldr ((<>) . prettyTerm) empty


instance (Pretty l, Pretty m) => Show (Diagnostic l m) where
  showsPrec d = showsPrec d . pretty

instance Functor (Diagnostic l) where
  fmap f (Diagnostic r l m xs) = Diagnostic r l (f m) $ map (fmap f) xs

instance Bifunctor Diagnostic where
  bimap f g (Diagnostic r l m xs) = Diagnostic r (f l) (g m) $ map (bimap f g) xs

instance Foldable (Diagnostic l) where
  foldMap f (Diagnostic _ _ m xs) = f m `mappend` foldMap (foldMap f) xs

instance Traversable (Diagnostic l) where
  traverse f (Diagnostic r l m xs) = Diagnostic r l <$> f m <*> traverse (traverse f) xs

instance Foldable1 (Diagnostic l) where
  foldMap1 f (Diagnostic _ _ m []) = f m
  foldMap1 f (Diagnostic _ _ m (x:xs)) = f m <> foldMap1 (foldMap1 f) (x:|xs)

instance Traversable1 (Diagnostic l) where
  traverse1 f (Diagnostic r l m [])     = fmap (\fm -> Diagnostic r l fm []) (f m)
  traverse1 f (Diagnostic r l m (x:xs)) = (\fm (y:|ys) -> Diagnostic r l fm (y:ys)) 
                                      <$> f m 
                                      <.> traverse1 (traverse1 f) (x:|xs)

instance Bifoldable Diagnostic where
  bifoldMap f g (Diagnostic _ l m xs) = f l `mappend` g m `mappend` foldMap (bifoldMap f g) xs

instance Bitraversable Diagnostic where
  bitraverse f g (Diagnostic r l m xs) = Diagnostic r <$> f l <*>  g m <*> traverse (bitraverse f g) xs
  
instance Bifoldable1 Diagnostic where
  bifoldMap1 f g (Diagnostic _ l m [])     = f l <> g m
  bifoldMap1 f g (Diagnostic _ l m (x:xs)) = f l <> g m <> foldMap1 (bifoldMap1 f g) (x:|xs)

instance Bitraversable1 Diagnostic where
  bitraverse1 f g (Diagnostic r l m [])     = (\fl gm -> Diagnostic r fl gm []) <$> f l <.> g m
  bitraverse1 f g (Diagnostic r l m (x:xs)) = (\fl gm (y:|ys) -> Diagnostic r fl gm (y:ys)) 
                                      <$> f l
                                      <.> g m
                                      <.> traverse1 (bitraverse1 f g) (x:|xs)