-- GenI surface realiser
-- Copyright (C) 2012 Eric Kow
--
-- This program is free software; you can redistribute it and/or
-- modify it under the terms of the GNU General Public License
-- as published by the Free Software Foundation; either version 2
-- of the License, or (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program; if not, write to the Free Software
-- Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

{-# OPTIONS_GHC -fno-warn-orphans #-}
{-# LANGUAGE OverloadedStrings #-}
module NLP.GenI.LexicalSelection.Types where

import           Control.Monad.Writer
import           Data.List
import qualified Data.Map             as Map
import           Data.Poset
import           Data.Text            (Text)
import qualified Data.Text            as T

import           NLP.GenI.GeniVal
import           NLP.GenI.Pretty

-- | Left hand side of a path equation
data PathEqLhs = PeqInterface   Text
               | PeqJust        NodePathEqLhs
               | PeqUnknown     Text
  deriving (Eq, Ord)

-- ----------------------------------------------------------------------
-- * Path equations
-- ----------------------------------------------------------------------

-- | Path equations can either hit a feature or a node's lexeme attribute
data NodePathEqLhs = PeqFeat Text TopBottom Text
                   | PeqLex  Text
  deriving (Eq, Ord)

data TopBottom = Top | Bottom
  deriving (Eq, Ord)

type PathEqPair = (NodePathEqLhs, GeniVal)

-- | Parse a path equation using the GenI conventions
--   This always succeeds, but can return @Just warning@
--   if anything anomalous comes up
--   FIXME : make more efficient
parsePathEq :: Text -> Writer [LexCombineError] PathEqLhs
parsePathEq e =
    case T.splitOn "." e of
        (n:"top":r)     -> return (node n Top    r)
        (n:"bot":r)     -> return (node n Bottom r)
        [n,"lex"]       -> return (PeqJust (PeqLex n))
        ("top":r)       -> return (node "anchor" Top r)
        ("bot":r)       -> return (node "anchor" Bottom r)
        ("anchor":r)    -> return (node "anchor" Bottom r)
        ("interface":r) -> return (PeqInterface  (rejoin r))
        ("anc":r)       -> parsePathEq $ rejoin ("anchor":r)
        (n:r@(_:_))     -> tell [BoringError (tMsg n)] >> return (node n Top r)
        _               -> tell [BoringError iMsg    ] >> return (PeqUnknown e)
  where
    node n tb r = PeqJust $ PeqFeat n tb (rejoin r)
    rejoin = T.intercalate "."
    tMsg n = T.unwords
        [ "Interpreting path equation"
        , e
        , "as applying to top of"
        , n `T.snoc` '.'
        ]
    iMsg = "Could not interpret path equation" <+> e

showPathEqLhs :: PathEqLhs -> Text
showPathEqLhs p =
    case p of
        PeqJust (PeqFeat n tb att) -> squish [ n, fromTb tb, att ]
        PeqJust (PeqLex  n)        -> squish [ n, "lex" ]
        PeqInterface att           -> squish [ "interface", att ]
        PeqUnknown e               -> e
  where
    fromTb Top    = "top"
    fromTb Bottom = "bot"
    squish = T.intercalate "."

-- ----------------------------------------------------------------------
-- * Warnings
-- ----------------------------------------------------------------------

data LexCombineError =
       BoringError Text
     | FamilyNotFoundError Text
     | SchemaError [Text] LexCombineError2
 deriving Eq

data LexCombineError2 = EnrichError PathEqLhs
                      | StringError Text
 deriving (Eq, Ord)

instance Poset LexCombineError where
 leq (BoringError _) _                                 = True
 leq (SchemaError _ e1) (SchemaError _ e2)             = leq e1 e2
 leq (FamilyNotFoundError x1) (FamilyNotFoundError x2) = leq x1 x2
 leq (FamilyNotFoundError _)  (SchemaError _ _)        = True
 leq _ _ = False

instance Poset LexCombineError2 where
 leq (EnrichError e1) (EnrichError e2)  = leq e1 e2
 leq (EnrichError _ ) (StringError _ )  = True
 leq (StringError s1) (StringError s2)  = leq s1 s2
 leq _ _ = False

instance Poset PathEqLhs where
 leq l1 l2 = leq (showPathEqLhs l1) (showPathEqLhs l2)

instance Poset Text where
    leq l1 l2 = leq (T.unpack l1) (T.unpack l2)

instance Pretty LexCombineError where
    pretty e =
        body <+> suffix
      where
        (body, suffix) = showLexCombineError e

showLexCombineError :: LexCombineError -> (Text, Text)
showLexCombineError (SchemaError xs x) = (pretty  x, prettyCount (const "") "trees" ((), length xs))
showLexCombineError (BoringError s)    = (s, "")
showLexCombineError (FamilyNotFoundError f) = ("Family" <+> f <+> "not found in tree schema file", "")

instance Pretty LexCombineError2 where
    pretty (EnrichError p) =
        "Some trees discarded due to enrichment error on" <+> showPathEqLhs p
    pretty (StringError s) = s

compressLexCombineErrors :: [LexCombineError] -> [LexCombineError]
compressLexCombineErrors errs = schema2 ++ normal
 where
  isSchema (SchemaError _ _) = True
  isSchema _ = False
  (schema, normal) = partition isSchema errs
  schema2 = map (uncurry (flip SchemaError))
          . Map.toList
          $ Map.fromListWith (++) [ (l,ts) | SchemaError ts l <- schema ]