{-# OPTIONS_GHC -Wall #-}
module DatabaseDesign.Ampersand.Classes.Relational 
   (Relational(..)
   ) where

import Data.Maybe
import DatabaseDesign.Ampersand.Core.AbstractSyntaxTree
import DatabaseDesign.Ampersand.Core.ParseTree       (Prop(..))
import DatabaseDesign.Ampersand.ADL1.Expression
import DatabaseDesign.Ampersand.Basics

fatal :: Int -> String -> a
fatal = fatalMsg "Classes.Relational"

class Association r => Relational r where
    multiplicities :: r -> [Prop]
    isProp :: r -> Bool  -- > tells whether the argument is a property
    isImin :: r -> Bool  -- > tells whether the argument is equivalent to I-
    isTrue :: r -> Bool  -- > tells whether the argument is equivalent to V
    isFalse :: r -> Bool  -- > tells whether the argument is equivalent to V-
    isFunction :: r -> Bool     
    isFunction r   = null ([Uni,Tot]>-multiplicities r)
    isTot :: r -> Bool  -- 
    isTot r = Tot `elem` multiplicities r
    isUni :: r -> Bool  -- 
    isUni r = Uni `elem` multiplicities r
    isSur :: r -> Bool  -- 
    isSur r = Sur `elem` multiplicities r
    isInj :: r -> Bool  -- 
    isInj r = Inj `elem` multiplicities r
    isRfx :: r -> Bool  -- 
    isRfx r = Rfx `elem` multiplicities r
    isIrf :: r -> Bool  -- 
    isIrf r = Irf `elem` multiplicities r
    isTrn :: r -> Bool  -- 
    isTrn r = Trn `elem` multiplicities r
    isSym :: r -> Bool  -- 
    isSym r = Sym `elem` multiplicities r
    isAsy :: r -> Bool  -- 
    isAsy r = Asy `elem` multiplicities r
    isIdent :: r -> Bool  -- > tells whether the argument is equivalent to I
    isEpsilon :: r -> Bool  -- > tells whether the argument is equivalent to I

--instance Relational Relation where
--    multiplicities rel 
--      = case rel of
--           Rel{}               -> multiplicities (reldcl rel)
--           V {}                -> [Tot]
--                                ++[Sur]
--                                ++[Inj | isSingleton (source rel)]
--                                ++[Uni | isSingleton (target rel)]
--                                ++[Asy | isEndo rel, isSingleton (source rel)]
--                                ++[Sym | isEndo rel]
--                                ++[Rfx | isEndo rel]
--                                ++[Trn | isEndo rel]
--           I{}                 -> [Uni,Tot,Inj,Sur,Sym,Asy,Trn,Rfx]
--    isProp rel = case rel of
--           Rel{}               -> null ([Asy,Sym]>-multiplicities (reldcl rel))
--           V{}                 -> isEndo rel && isSingleton (source rel)
--           I{}                 -> True
--    isImin rel  = isImin (makeDeclaration rel)   -- > tells whether the argument is equivalent to I-
--    isTrue rel = case rel of
--           Rel{}               -> False
--           V{}                 -> True
--           I{}                 -> False
--    isFalse _   = False
--    isIdent rel = case rel of       -- > tells whether the argument is equivalent to I
--                   Rel{} -> False
--                   V{}   -> isEndo rel && isSingleton (source rel)
--                   I{}   -> True

instance Relational Declaration where
    multiplicities d = case d of
           Sgn {}       -> case decprps_calc d of
                             Nothing -> decprps d
                             Just ps -> ps 
           Isn{}        -> [Uni,Tot,Inj,Sur,Sym,Asy,Trn,Rfx]
           Vs{}         -> [Tot,Sur]
    isProp d = case d of         -- > tells whether the argument is a property.
           Sgn {}       -> null ([Asy,Sym]>-multiplicities d)
           Isn{}        -> True
           Vs{}         -> isEndo (sign d) && isSingleton (source d)
    isImin _ = False  -- LET OP: Dit kan natuurlijk niet goed zijn, maar is gedetecteerd bij revision 913, toen straffeloos de Iscompl{} kon worden verwijderd.
    isTrue d = case d of 
           Vs{}         -> True
           _            -> False
    isFalse _ = False
    isIdent d = case d of
                 Isn{} -> True   -- > tells whether the argument is equivalent to I
                 _     -> False
    isEpsilon _ = False

isSingleton :: A_Concept -> Bool
isSingleton ONE = True
isSingleton _   = False

-- The function "multiplicities" does not only provide the multiplicities provided by the Ampersand user,
-- but tries to derive the most obvious multiplicity constraints as well. The more multiplicity constraints are known,
-- the better the data structure that is derived.
-- Not every constraint that can be proven is obtained by this function. This does not hurt Ampersand.
instance Relational Expression where        -- TODO: see if we can find more multiplicity constraints...
 multiplicities expr = case expr of
     EDcD dcl   -> multiplicities dcl
     EDcI{}     -> [Uni,Tot,Inj,Sur,Sym,Asy,Trn,Rfx]
     EEps a sgn -> [Tot | a == source sgn]++[Sur | a == target sgn] ++ [Uni,Inj]
     EDcV sgn   -> [Tot]
                 ++[Sur]
                 ++[Inj | isSingleton (source sgn)]
                 ++[Uni | isSingleton (target sgn)]
                 ++[Asy | isEndo sgn, isSingleton (source sgn)]
                 ++[Sym | isEndo sgn]
                 ++[Rfx | isEndo sgn]
                 ++[Trn | isEndo sgn]
     EBrk f     -> multiplicities f
     ECps (l,r) -> [m | m<-multiplicities l `isc` multiplicities r, m `elem` [Uni,Tot,Inj,Sur]] -- endo properties can be used and deduced by and from rules: many rules are multiplicities (TODO)
     EPrd (l,r) -> [Tot | isTot l]++[Sur | isSur r]++[Rfx | isRfx l&&isRfx r]++[Trn]
     EKl0 e'    -> [Rfx,Trn] `uni` (multiplicities e'>-[Uni,Inj])
     EKl1 e'    -> [    Trn] `uni` (multiplicities e'>-[Uni,Inj])
     ECpl e'    -> [p |p<-multiplicities e', p==Sym]
     EFlp e'    -> [fromMaybe m $ lookup m [(Uni,Inj),(Inj,Uni),(Sur,Tot),(Tot,Sur)] | m <- multiplicities e'] -- switch Uni<->Inj and Sur<->Tot, keeping the others the same
     EMp1{}     -> [Uni,Inj,Sym,Asy,Trn]
     _          -> []

 -- |  isTrue e == True   means that e is true, i.e. the population of e is (source e * target e).
 --    isTrue e == False  does not mean anything.
 --    the function isTrue is meant to produce a quick answer, without any form of theorem proving.
 isTrue expr
  = case expr of
     EEqu (l,r) -> l == r
     EImp (l,_) -> isTrue l
     EIsc (l,r) -> isTrue l && isTrue r
     EUni (l,r) -> isTrue l || isTrue r 
     EDif (l,r) -> isTrue l && isFalse r
     ECps (l,r) | null ([Uni,Tot]>-multiplicities l) -> isTrue r
                | null ([Sur,Inj]>-multiplicities r) -> isTrue l
                | otherwise                          -> isTrue l && isTrue r
     EPrd (l,r) -> isTrue l && isTrue r || isTot l && isSur r || isRfx l && isRfx r
     EKl0 e     -> isTrue e
     EKl1 e     -> isTrue e
     EFlp e     -> isTrue e
     ECpl e     -> isFalse e
     EDcD{}     -> False
     EDcI c     -> isSingleton c
     EEps i _   -> isSingleton i
     EDcV{}     -> True
     EBrk e     -> isTrue e
     _          -> False  -- TODO: find richer answers for ERrs, ELrs, EDia, ERad, and EMp1

 -- |  isFalse e == True   means that e is false, i.e. the population of e is empty.
 --    isFalse e == False  does not mean anything.
 --    the function isFalse is meant to produce a quick answer, without any form of theorem proving.
 isFalse expr
  = case expr of
     EEqu (l,r) -> l == notCpl r
     EImp (_,r) -> isFalse r
     EIsc (l,r) -> isFalse r || isFalse l
     EUni (l,r) -> isFalse r && isFalse l
     EDif (l,r) -> isFalse l || isTrue r
     ECps (l,r) -> isFalse r || isFalse l
     EPrd (l,r) -> isFalse r || isFalse l
     EKl0 e     -> isFalse e
     EKl1 e     -> isFalse e
     EFlp e     -> isFalse e
     ECpl e     -> isTrue e
     EDcD{}     -> False
     EDcI{}     -> False
     EEps{}     -> False
     EDcV{}     -> False
     EBrk e     -> isFalse e
     _          -> False  -- TODO: find richer answers for ERrs, ELrs, EDia, and ERad

 isProp expr = null ([Asy,Sym]>-multiplicities expr)

 -- |  The function isIdent tries to establish whether an expression is an identity relation.
 --    It does a little bit more than just test on ERel I _.
 --    If it returns False, this must be interpreted as: the expression is definitely not I, an may not be equal to I as far as the computer can tell on face value. 
 isIdent expr = case expr of
     EEqu (l,r) -> isIdent (EIsc (EImp (l,r), EImp (r,l)))    -- TODO: maybe derive something better?
     EImp (l,r) -> isIdent (EUni (ECpl l, r))                     -- TODO: maybe derive something better?
     EIsc (l,r) -> isIdent l && isIdent r
     EUni (l,r) -> isIdent l && isIdent r
     EDif (l,r) -> isIdent l && isFalse r
     ECps (l,r) -> isIdent l && isIdent r
     EKl0 e     -> isIdent e || isFalse e
     EKl1 e     -> isIdent e
     ECpl e     -> isImin e
     EDcD{}     -> False
     EDcI{}     -> True
     EEps i sgn -> if isEndo sgn && i==source expr then fatal 189 (show expr++" is endo") else
                   False
     EDcV sgn   -> isEndo sgn && isSingleton (source sgn)
     EBrk f     -> isIdent f
     EFlp f     -> isIdent f
     _          -> False  -- TODO: find richer answers for ELrs, ERrs, EDia, EPrd, and ERad
 isEpsilon e = case e of
                EEps{} -> True
                _      -> False
 
 isImin expr' = case expr' of       -- > tells whether the argument is equivalent to I-
     EEqu (l,r) -> isImin (EIsc (EImp (l,r), EImp (r,l)))       -- TODO: maybe derive something better?
     EImp (l,r) -> isImin (EUni (ECpl l, r))                  -- TODO: maybe derive something better?
     EIsc (l,r) -> isImin l && isImin r
     EUni (l,r) -> isImin l && isImin r
     EDif (l,r) -> isImin l && isFalse r
     ECpl e     -> isIdent e
     EDcD dcl   -> isImin dcl
     EDcI{}     -> False
     EEps{}     -> False
     EDcV{}     -> False
     EBrk f     -> isImin f
     EFlp f     -> isImin f
     _          -> False  -- TODO: find richer answers for ELrs, ERrs, and EDia