-- GenI surface realiser
-- Copyright (C) 2005-2009 Carlos Areces and 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 DeriveDataTypeable    #-}
{-# LANGUAGE FlexibleContexts      #-}
{-# LANGUAGE FlexibleInstances     #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings     #-}
{-# LANGUAGE TypeSynonymInstances  #-}

-- | Feature structures in GenI can be seen as a simple mapping from
--   attributes to values (no fancy recursion).
--
--   From an implementation standpoint, we do truck around lists of
--   'AvPair' quite a bit which unfortunately means we don't
--   guarantee things like uniqueness of attributes.  We may phase
--   this out over time in favour of 'FeatStruct'
module NLP.GenI.FeatureStructure where

import           Control.DeepSeq
import           Data.Binary
import           Data.Function     (on)
import           Data.Generics     (Data)
import           Data.List         (sortBy)
import qualified Data.Map          as Map
import           Data.Text         (Text)
import qualified Data.Text         as T
import           Data.Typeable     (Typeable)

import           NLP.GenI.General  (geniBug)
import           NLP.GenI.GeniShow
import           NLP.GenI.GeniVal
import           NLP.GenI.Pretty


-- ----------------------------------------------------------------------
-- Core types
-- ----------------------------------------------------------------------

-- | A list of attribute-value pairs. It's not a great idea to represent
--   feature structures with this because it allows for duplicates in the
--   attributes. But maybe sometimes you really do mean a list.
type Flist a  = [AvPair a]

-- | An attribute-value pair, the typical use being
--   @AvPair GeniVal@ or if you have something even simpler
--   @AvPair Text@
data AvPair a = AvPair { avAtt :: Text
                       , avVal :: a }
  deriving (Ord, Eq, Data, Typeable)

-- | Experimental, alternative representation of Flist
--   which guarantees uniqueness of keys
type FeatStruct a = Map.Map Text a

-- | A feature structure with no pairs
emptyFeatStruct :: FeatStruct a
emptyFeatStruct = Map.empty

-- | Convert an 'Flist' to a proper 'FeatStruct'
--   Unsafely assumes the keys are unique
mkFeatStruct :: Flist GeniVal -> FeatStruct GeniVal
mkFeatStruct fs = Map.fromListWith oops . map fromPair $ fs
  where
   fromPair (AvPair a v) = (a,v)
   oops _ _ = geniBug $
     "I've allowed a feature structure with multiple versions of a key"
     ++ " to sneak through: " ++ prettyStr fs

-- | Convert an 'FeatStruct' to a simpler to process 'Flist'
fromFeatStruct :: FeatStruct a -> Flist a
fromFeatStruct = sortFlist . map (uncurry AvPair) . Map.toList

-- if we decide to move over to this representation of feature structures
-- we can get rid of showFlist, etc and probably just use toAscList
instance Pretty (FeatStruct GeniVal) where
    pretty = pretty . fromFeatStruct

instance GeniShow (FeatStruct GeniVal) where
    geniShowText = geniShowText . fromFeatStruct

-- ----------------------------------------------------------------------
-- Basic functions
-- ----------------------------------------------------------------------

-- | Sort an Flist according with its attributes
sortFlist :: Flist a -> Flist a
sortFlist = sortBy (compare `on` avAtt)

-- Traversal

instance {-# OVERLAPPING #-} DescendGeniVal v => DescendGeniVal (AvPair v) where
  descendGeniVal s (AvPair a v) = {-# SCC "descendGeniVal" #-} AvPair a (descendGeniVal s v)

instance DescendGeniVal a => DescendGeniVal (String, a) where
  descendGeniVal s (n,v) = {-# SCC "descendGeniVal" #-} (n,descendGeniVal s v)

-- we actually get this for free since the two-tuple Functor instance only
-- applies to the second element
-- instance DescendGeniVal v => DescendGeniVal ([String], Flist v) where
--  descendGeniVal s (a,v) = {-# SCC "descendGeniVal" #-} (a, descendGeniVal s v)

instance Collectable a => Collectable (AvPair a) where
  collect (AvPair _ b) = collect b

-- Pretty printing and output format

instance Pretty (Flist GeniVal) where
    pretty = geniShowText

instance Pretty (AvPair GeniVal) where
    pretty = geniShowText

instance GeniShow gv => GeniShow (Flist gv) where
    geniShowText = squares . T.unwords . map geniShowText

instance GeniShow gv => GeniShow (AvPair gv) where
    geniShowText (AvPair a v) = a `T.append` ":" `T.append` geniShowText v

{-
instance Show (AvPair GeniVal) where
  show = showAv
-}

-- --------------------------------------------------------------------
-- Feature structure unification
-- --------------------------------------------------------------------

-- | 'unifyFeat' performs feature structure unification, under the
--   these assumptions about the input:
--
--    * Features are ordered
--
--    * The Flists do not share variables (renaming has already
--      been done.
--
--   The features are allowed to have different sets of attributes,
--   beacuse we use 'alignFeat' to realign them.
unifyFeat :: MonadUnify m
          => Flist GeniVal -> Flist GeniVal -> m (Flist GeniVal, Subst)
unifyFeat f1 f2 =
  {-# SCC "unification" #-}
  let (att, val1, val2) = unzip3 $ alignFeat f1 f2
  in att `seq`
     do (res, subst) <- unify val1 val2
        return (zipWith AvPair att res, subst)

-- | 'alignFeat' is a pre-procesing step used to ensure that feature structures
--   have the same set of keys.  If a key is missing in one, we copy it to the
--   other with an anonymous value.
--
--   The two feature structures must be sorted for this to work
alignFeat :: Flist GeniVal -> Flist GeniVal -> [(Text,GeniVal,GeniVal)]
alignFeat f1 f2 = alignFeatH f1 f2 []

-- | Helper for 'alignFeat'; ignore
alignFeatH :: Flist GeniVal -> Flist GeniVal -> [(Text,GeniVal,GeniVal)] -> [(Text,GeniVal,GeniVal)]
alignFeatH [] [] acc = reverse acc
alignFeatH [] (AvPair f v :x) acc = alignFeatH [] x ((f,mkGAnon,v) : acc)
alignFeatH x [] acc = alignFeatH [] x acc
alignFeatH fs1@(AvPair f1 v1:l1) fs2@(AvPair f2 v2:l2) acc =
   case compare f1 f2 of
     EQ -> alignFeatH l1 l2  ((f1, v1, v2) : acc)
     LT -> alignFeatH l1 fs2 ((f1, v1, mkGAnon) : acc)
     GT -> alignFeatH fs1 l2 ((f2, mkGAnon, v2) : acc)

-- --------------------------------------------------------------------
-- Fancy disjunction
-- --------------------------------------------------------------------

-- | Flatten a fancy disjunction attribute-value pair
--
--   See 'crushOne' for details
crushAvPair :: AvPair SchemaVal -> Maybe (AvPair GeniVal)
crushAvPair (AvPair a v) = AvPair a `fmap` crushOne v

-- | Flatten a fancy-disjunction feature structure
--
--   See 'crushOne' for details
crushFlist :: Flist SchemaVal -> Maybe (Flist GeniVal)
crushFlist = mapM crushAvPair

{-!
deriving instance Binary AvPair
deriving instance NFData AvPair
!-}

-- GENERATED START


instance (Binary a) => Binary (AvPair a) where
        put (AvPair x1 x2)
          = do put x1
               put x2
        get
          = do x1 <- get
               x2 <- get
               return (AvPair x1 x2)


instance (NFData a) => NFData (AvPair a) where
        rnf (AvPair x1 x2) = rnf x1 `seq` rnf x2 `seq` ()
-- GENERATED STOP