{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE FlexibleInstances #-}
module Database.Design.Ampersand.FSpec.ShowMeatGrinder
  (makeMetaPopulationFile,MetaType(..))
where

import Data.List
import Data.Char
import Data.Ord
import Data.Hashable (hash) -- a not good enouqh function, but used for the time being. 
import Data.Typeable
import Database.Design.Ampersand.FSpec.FSpec
import Database.Design.Ampersand.FSpec.FSpecAux
import Database.Design.Ampersand.FSpec.Motivations
import Database.Design.Ampersand.FSpec.SQL
import Database.Design.Ampersand.FSpec.ToFSpec.NormalForms (conjNF)
import Database.Design.Ampersand.Basics
import Database.Design.Ampersand.Misc
import Database.Design.Ampersand.FSpec.ShowADL
import Database.Design.Ampersand.Core.AbstractSyntaxTree
import Database.Design.Ampersand.Classes.ConceptStructure

--import Data.Hashable
import Data.Maybe

fatal :: Int -> String -> a
fatal = fatalMsg "ShowMeatGrinder"

data MetaType = Generics | AST deriving (Show)

makeMetaPopulationFile :: MetaType -> FSpec -> (FilePath,String)
makeMetaPopulationFile mType fSpec
  = ("MetaPopulationFile"++show mType++".adl", content popKind mType fSpec)
    where popKind = case mType of
                      Generics -> generics fSpec
                      AST      -> metaPops fSpec 

content :: (FSpec -> [Pop]) -> MetaType -> FSpec -> String
content popKind mType fSpec = unlines
   ([ "{- Do not edit manually. This code has been generated!!!"
    , "    Generated with "++ampersandVersionStr
    , "    Generated at "++show (genTime (getOpts fSpec))
    , " "
    , "The populations defined in this file are the populations from the user's"
    , "model named '"++name fSpec++"'."
    , ""
    , "The order in which these populations are defined correspond with the order "
    , "in which Ampersand is defined in itself. Currently (Feb. 2015), this is hard-"
    , "coded. This means, that whenever Formal Ampersand changes, it might have "
    , "impact on the generator of this file. "
    , ""
    , "-}"
    , ""
    , "CONTEXT "++show mType++" IN ENGLISH -- (the language is chosen arbitrary, for it is mandatory but irrelevant."]
    ++ (concat.intersperse  []) (map (lines . showADL ) (popKind fSpec))
    ++
    [ ""
    , "ENDCONTEXT"
    ])
instance GenericPopulations FSpec where
 generics _ fSpec =
   filter (not.nullContent)
    (
    [ Pop "versionInfo" "Context"  "AmpersandVersion"
           [(uri fSpec, ampersandVersionStr)]
    , Pop "contextName" "Context" "ContextName"
           [(uri fSpec, name fSpec)]
    , Pop "dbName" "Context" "DatabaseName"
           [(uri fSpec, dbName (getOpts fSpec))]
    ]
  ++[ Comment " ", Comment $ "[Relations]--: (count="++(show.length.allDecls) fSpec++")"]
  ++   concatMap (generics fSpec) (allDecls fSpec)
  ++[ Comment " ", Comment $ "[Concepts]--: (count="++(show.length) [c | c <- allConcepts fSpec, c /= ONE]++")"]
  ++   concatMap (generics fSpec) [c | c <- allConcepts fSpec, c /= ONE]
  ++[ Comment " ", Comment $ "[TableColumnInfo]--: (count="++(show.length) allSqlPlugs++")"]
  ++   concatMap (generics fSpec) allSqlPlugs
  ++[ Comment " ", Comment $ "[Rules]--: (count="++(show.length.fallRules) fSpec++")"]
  ++   concatMap (generics fSpec) (fallRules fSpec)
  ++[ Comment " ", Comment $ "[Conjuncts]--: (count="++(show.length.vconjs) fSpec++")"]
  ++   concatMap (generics fSpec) (vconjs fSpec)
  ++[ Comment " ", Comment $ "[Roles]--: (count="++(show.length.fRoles) fSpec++")"]
  ++   concatMap (generics fSpec) (fRoles fSpec)
    )
  where
    allSqlPlugs = [plug | InternalPlug plug <- plugInfos fSpec]

instance MetaPopulations FSpec where
 metaPops _ fSpec =
   filter (not.nullContent)
    (
    [Comment  " ", Comment $ "PATTERN Context: ('"++name fSpec++"')"]
  ++[ Pop "name"   "Context" "ContextName"
           [(uri fSpec,name fSpec)]]
  ++[ Comment " ", Comment $ "PATTERN Patterns: (count="++(show.length.vpatterns) fSpec++")"]
  ++   concatMap (metaPops fSpec) ((sortBy (comparing name).vpatterns)    fSpec)
  ++[ Comment " ", Comment $ "PATTERN Specialization: (count="++(show.length.vgens) fSpec++")"]
  ++   concatMap (metaPops fSpec) (vgens          fSpec)
  ++[ Comment " ", Comment $ "PATTERN Concept: (count="++(show.length.allConcepts) fSpec++")"]
  ++   concatMap (metaPops fSpec) ((sortBy (comparing name).allConcepts)    fSpec)
  ++[ Comment " ", Comment $ "PATTERN Atoms: (count="++(show.length) (allAtoms fSpec)++")"]
  ++   concatMap (metaPops fSpec) (allAtoms fSpec)
--  ++[ Comment " ", Comment $ "PATTERN Sign: (count="++(show.length.allSigns) fSpec++")"]
--  ++   concatMap (metaPops fSpec) (allSigns fSpec)
  ++[ Comment " ", Comment $ "PATTERN Declaration: (count="++(show.length.allDecls) fSpec++")"]
  ++   concatMap (metaPops fSpec) (allDecls  fSpec)
  ++[ Comment " ", Comment $ "PATTERN Expression: (count="++(show.length.allExprs) fSpec++")"]
  ++   concatMap (metaPops fSpec) (allExprs  fSpec)
  ++[ Comment " ", Comment $ "PATTERN Rules: (count="++(show.length.fallRules) fSpec++")"]
  ++   concatMap (metaPops fSpec) ((sortBy (comparing name).fallRules)    fSpec)
  ++[ Comment " ", Comment $ "PATTERN Plugs: (count="++(show.length.plugInfos) fSpec++")"]
  ++   concatMap (metaPops fSpec) ((sortBy (comparing name).plugInfos)    fSpec)
  ++[ Comment " ", Comment $ "[InitialPopulation]--: (count="++(show.length.initialPops) fSpec++")"]
  ++   concatMap (metaPops fSpec) (allLinks fSpec)
  )
   where

instance MetaPopulations Pattern where
 metaPops fSpec pat =
   [ Comment " "
   , Comment $ " Pattern `"++name pat++"` "
   , Pop "patterns" "Context" "Pattern"
          [(uri fSpec,uri pat)]
   , Pop "name"    "Pattern" "PatternName"
          [(uri pat, name pat)]
   , Pop "rules"   "Pattern" "Rule"
          [(uri pat,uri x) | x <- ptrls pat]
   , Pop "declarations"   "Pattern" "Declaration"
          [(uri pat,uri x) | x <- ptdcs pat]
   , Pop "purpose"   "Pattern" "Purpose"
          [(uri pat,uri x) | x <- ptxps pat]
   ]
instance MetaPopulations A_Gen where
 metaPops fSpec gen =
  [ Pop "gens" "Context" "Gen"
          [(uri fSpec,uri gen)]
  , Pop "genspc"  "Gen" "PlainConcept"
          [(uri gen,uri(genspc gen))]
  , Pop "gengen"  "Gen" "PlainConcept"
          [(uri gen,uri c) | c<- case gen of
                                   Isa{} -> [gengen gen]
                                   IsE{} -> genrhs gen
          ]
  ]

instance GenericPopulations A_Concept where
 generics fSpec cpt =
   case cpt of
     PlainConcept{} ->
      [ Comment " "
      , Comment $ " Concept `"++name cpt++"` "
      , Pop "allConcepts" "Context" "Concept"
             [(uri fSpec,uri cpt)]
      , Pop "name" "Concept" "ConceptName"
             [(uri cpt, name cpt)]
      , Pop "affectedInvConjunctIds" "Concept" "ConjunctID"
             [(uri cpt, uri conj) | conj <- filterFrontEndInvConjuncts affConjs]
      , Pop "affectedSigConjunctIds" "Concept" "ConjunctID"
             [(uri cpt, uri conj) | conj <- filterFrontEndSigConjuncts affConjs]
      , Pop "conceptTableFields" "Concept" "TableColumn"
             [(uri cpt, uri fld) | fld <- tablesAndFields]
      ]
     ONE -> [
            ]
  where
   affConjs = fromMaybe [] (lookup cpt $ allConjsPerConcept fSpec)
   tablesAndFields = nub . concatMap (lookupCpt fSpec) $ cpt : largerConcepts (vgens fSpec) cpt
instance MetaPopulations A_Concept where
 metaPops fSpec cpt =
   case cpt of
     PlainConcept{} ->
      [ Comment " "
      , Comment $ " Concept `"++name cpt++"` "
      , Pop "concs" "Context" "PlainConcept"
           [(uri fSpec,uri cpt)]
      , Pop "name" "PlainConcept" "ConceptName"
             [(uri cpt, name cpt)]
      , Pop "cptdf" "PlainConcept" "ConceptDefinition"
             [(uri cpt,showADL cdef) | cdef <- conceptDefs  fSpec, name cdef == name cpt]
      , Pop "cptpurpose" "PlainConcept" "Purpose"
             [(uri cpt,showADL x) | lang <- allLangs, x <- fromMaybe [] (purposeOf fSpec lang cpt) ]
      ]
     ONE -> [
            ]
instance GenericPopulations PlugSQL where
  generics fSpec plug =
      [ Comment " "
      , Comment $ "Plug: '"++name plug++"'"
      , Pop "tableInfo" "Context" "DBTable"
               [(uri fSpec, uri plug)]
      ] ++ concatMap (generics fSpec) [(plug,fld) | fld <- plugFields plug]

instance GenericPopulations (PlugSQL,SqlField) where
  generics _ (plug,fld) =
      [ Pop "columninfo" "DBTable" "TableColumn"
                 [(uri plug, uri (plug,fld)) ]
      , Pop "concept" "TableColumn" "Concept"
                 [(uri (plug,fld), uri.target.fldexpr $ fld)]
      , Pop "unique" "TableColumn" "BOOLEAN"
                 [(uri (plug,fld), uri.flduniq $ fld)]
      , Pop "null" "TableColumn" "BOOLEAN"
                 [(uri (plug,fld), uri.fldnull $ fld)]
      ]
     
instance GenericPopulations Role where
  generics fSpec rol =
      [ Comment $ "Role: '"++name rol++"'"
      , Pop "allRoles" "Context" "Role"
                 [(uri fSpec, uri rol) ]
      , Pop "name" "Role" "RoleName"
                 [(uri rol, name rol) ]
      , Pop "maintains" "Role" "Rule"
                 [(uri rol, uri rul) | (rol',rul) <-  fRoleRuls fSpec, rol==rol' ]
      ]

instance MetaPopulations Atom where
 metaPops _ atm =
   [ Pop "key" "Atom" "AtomID" 
          [(uri atm, uri atm)]
   , Pop "atomvalue"  "Atom" "AtomValue"
          [(uri atm,(show.atmVal) atm)]
   ]
--instance MetaPopulations Sign where
-- metaPops _ sgn =
--      [ Pop "src" "Sign" "Concept"
--             [(uri sgn, uri (source sgn))]
--      , Pop "trg" "Sign" "Concept"
--             [(uri sgn, uri (target sgn))]
--      ]

instance GenericPopulations Declaration where
 generics fSpec dcl =
   case dcl of 
     Sgn{} ->
      [ Comment " "
      , Comment $ " Relation '"++name dcl++showSign (sign dcl)++"'"
      , Pop "allRelations" "Context" "Relation"
             [(uri fSpec, uri dcl)]
      , Pop "name" "Relation" "RelationName"
             [(uri dcl,name dcl)]
      , Pop "srcConcept" "Relation" "Concept"
             [(uri dcl,uri (source dcl))]
      , Pop "tgtConcept" "Relation" "Concept"
             [(uri dcl,uri (target dcl))]
      , Pop "table" "Relation" "DBTable"
             [(uri dcl,uri table)]
      , Pop "srcCol" "Relation" "DBTableColumn"
             [(uri dcl,uri (table,srcCol))]
      , Pop "tgtCol" "Relation" "DBTableColumn"
             [(uri dcl,uri (table,tgtCol))]
      , Pop "affectedInvConjunctIds" "Relation" "ConjunctID"
             [(uri dcl,uri conj) | conj <- filterFrontEndInvConjuncts affConjs ]
      , Pop "affectedSigConjunctIds" "Relation" "ConjunctID"
             [(uri dcl,uri conj) | conj <- filterFrontEndSigConjuncts affConjs ]
      ]
     Isn{} -> fatal 157 "Isn is not implemented yet"
     Vs{}  -> fatal 158 "Vs is not implemented yet"
   where
     (table,srcCol,tgtCol) = getDeclarationTableInfo fSpec dcl
     affConjs = fromMaybe [] (lookup dcl $ allConjsPerDecl fSpec)
instance MetaPopulations Declaration where
 metaPops fSpec dcl =
   case dcl of
     Sgn{} ->
      [ Comment " "
      , Comment $ " Declaration `"++name dcl++" ["++(name.source.decsgn) dcl++" * "++(name.target.decsgn) dcl++"]"++"` "
      , Pop "allDeclarations" "Context" "Declaration"
             [(uri fSpec,uri dcl)] 
      , Pop "name" "Declaration" "DeclarationName"
             [(uri dcl, name dcl)]
--      , Pop "sign" "Declaration" "Sign"
--             [(uri dcl,uri (sign dcl))]
      , Pop "source" "Relation" "Concept"
             [(uri dcl,uri (source dcl))]
      , Pop "target" "Relation" "Concept"
             [(uri dcl,uri (target dcl))]
      , Pop "decprL" "Declaration" "String"
             [(uri dcl,decprL dcl)]
      , Pop "decprM" "Declaration" "String"
             [(uri dcl,decprM dcl)]
      , Pop "decprR" "Declaration" "String"
             [(uri dcl,decprR dcl)]
      , Pop "decmean" "Declaration" "Meaning"
             [(uri dcl, show(decMean dcl))]
      , Pop "decpurpose" "Declaration" "Purpose"
             [(uri dcl, showADL x) | x <- explanations dcl]
      ]
     Isn{} -> 
      [ Comment " "
      , Comment $ " Declaration `I["++name (source dcl)++"]`"
--      , Pop "sign" "Declaration" "Sign"
--             [(uri dcl,uri (sign dcl))]
      , Pop "source" "Relation" "Concept"
             [(uri dcl,uri (source dcl))]
      , Pop "target" "Relation" "Concept"
             [(uri dcl,uri (target dcl))]
      ]
     Vs{}  -> fatal 158 "Vs is not implemented yet"

instance MetaPopulations A_Pair where
 metaPops _ pair =
      [ Pop "in" "Pair" "Relation"
             [(uri pair, uri (lnkDcl pair))]
      , Pop "l" "Pair" "Atom"
             [(uri pair, uri(lnkLeft pair))]
      , Pop "r" "Pair" "Atom"
             [(uri pair, uri(lnkRight pair))]
      ]

instance MetaPopulations Expression where
 metaPops _ e =
      [ Pop "exprvalue" "ExpressionID" "Expression"
             [(uri e, showADL e)]
      ]

instance GenericPopulations Rule where
 generics fSpec rul =
      [ Comment " "
      , Comment $ " Rule `"++name rul++"` "
      , Pop "allRules" "Context" "Rule"
             [(uri fSpec, uri rul)]
      , Pop "name"  "Rule" "RuleID"
             [(uri rul,name rul)]
      , Pop "ruleAdl"  "Rule" "Adl"
             [(uri rul,(showADL.rrexp) rul)]
      , Pop "origin"  "Rule" "Origin"
             [(uri rul,(show.origin) rul)]
      , Pop "meaning"  "Rule" "Meaning"
             [(uri rul,aMarkup2String m) | m <- (maybeToList . meaning (fsLang fSpec)) rul ]
      , Pop "message"  "Rule" "Message"
             [(uri rul,aMarkup2String m) | m <- rrmsg rul, amLang m == fsLang fSpec ]
      , Pop "srcConcept"  "Rule" "Concept"
             [(uri rul,(uri.source.rrexp) rul)]
      , Pop "tgtConcept"  "Rule" "Concept"
             [(uri rul,(uri.target.rrexp) rul)]
      , Pop "conjunctIds"  "Rule" "ConjunctID"
             [(uri rul,uri conj) | (rule,conjs)<-allConjsPerRule fSpec, rule==rul,conj <- conjs]
      ]++case rrviol rul of
        Nothing -> []
        Just pve ->
         [ Pop "pairView"  "Rule" "PairView"
              [(uri rul, uri pve)]
         ]++generics fSpec pve
      
      
instance GenericPopulations (PairView Expression) where
 generics fSpec pve = 
      [ Comment " "
      ]++concatMap makeSegment (zip [0..] (ppv_segs pve))
  where
    makeSegment :: (Int,PairViewSegment Expression) -> [Pop]
    makeSegment (i,pvs) =
      [ Pop "segment" "PairView" "PairViewSegment"
             [(uri pve,uri pvs)]
      , Pop "sequenceNr" "PairViewSegment" "Int"
             [(uri pvs,show i)]
      , Pop "segmentType" "PairViewSegment" "PairViewSegmentType"
             [(uri pvs, case pvs of 
                         PairViewText{} -> "Text"
                         PairViewExp{}  -> "Exp")
            ]
      ]++
      case pvs of
        PairViewText{} -> 
          [Pop "text" "PairViewSegment" "String"
               [(uri pvs, pvsStr pvs)] 
          ]
        PairViewExp{} -> 
          [Pop "srcOrTgt" "PairViewSegment" "SourceOrTarget"
               [(uri pvs, show (pvsSoT pvs))] 
          ,Pop "expTgt" "PairViewSegment" "Concept"
               [(uri pvs, uri (case pvsSoT pvs of
                                Src -> source (pvsExp pvs)
                                Tgt -> target (pvsExp pvs)
                              ))] 
          ,Pop "expSQL" "PairViewSegment" "MySQLQuery"
               [(uri pvs, prettySQLQuery fSpec 0 (pvsExp pvs))] 
          ]
         
      
instance MetaPopulations Rule where
 metaPops _ rul =
      [ Comment " "
      , Comment $ " Rule `"++name rul++"` "
      , Pop "name"  "Rule" "RuleName"
             [(uri rul,name rul)]
      , Pop "rrexp"  "Rule" "ExpressionID"
             [(uri rul,uri (rrexp rul))]
      , Pop "rrmean"  "Rule" "Meaning"
             [(uri rul,show(rrmean rul))]
      , Pop "rrpurpose"  "Rule" "Purpose"
             [(uri rul,showADL x) | x <- explanations rul]
      , -- The next population is from the adl pattern 'Plugs':
        Pop "sign" "Rule" "Sign"
             [(uri rul,uri (sign rul))]
      , Pop "declaredthrough" "PropertyRule" "Property"
             [(uri rul,show prp) | Just(prp,_) <- [rrdcl rul]]
      , Pop "decprps" "Declaration" "PropertyRule"
             [(uri dcl, uri rul) | Just(_,dcl) <- [rrdcl rul]]
      ]



instance MetaPopulations PlugInfo where
 metaPops _ plug = 
      [ Comment $ " Plug `"++name plug++"` "
      , Pop "maintains" "Plug" "Rule" [{-STILL TODO. -}] --HJO, 20150205: Waar halen we deze info vandaan??
      , Pop "in" "PlainConcept" "Plug"                 
             [(uri cpt,uri plug)| cpt <- concs plug]  
      , Pop "in" "Declaration" "Plug"
             [(uri dcl,uri plug)| dcl <- relsMentionedIn plug]
      ]      

instance MetaPopulations a => MetaPopulations [a] where
 metaPops fSpec = concatMap $ metaPops fSpec
 
instance GenericPopulations Conjunct where
 generics fSpec conj =
  [ Comment $ "Conjunct: '"++rc_id conj++"'."
  , Pop "allConjuncts" "Context" "Conjunct" 
         [(uri fSpec, uri conj)]
  , Pop "signalRuleNames" "Conjunct" "Rule" 
         [(uri conj,uri r) | r <- rc_orgRules conj, isFrontEndSignal r]
  , Pop "invariantRuleNames" "Conjunct" "Rule" 
         [(uri conj,uri r) | r <- rc_orgRules conj, isFrontEndInvariant  r]
  , Pop "violationsSQL" "Conjunct" "MySQLQuery" 
         [(uri conj , prettySQLQuery fSpec 0 (conjNF (getOpts fSpec) (notCpl (rc_conjunct conj)))
          )]
  ] 


-----------------------------------------------------
data Pop = Pop { popName ::String
               , popSource :: String
               , popTarget :: String
               , popPairs :: [(String,String)]
               }
         | Comment { comment :: String  -- Not-so-nice way to get comments in a list of populations. Since it is local to this module, it is not so bad, I guess...
                   }
instance ShowADL Pop where
 showADL pop =
  case pop of
      Pop{} -> "POPULATION "++ popName pop++
                  " ["++popSource pop++" * "++popTarget pop++"] CONTAINS"
              ++
              if null (popPairs pop)
              then "[]"
              else "\n"++indentA++"[ "++intercalate ("\n"++indentA++"; ") showContent++indentA++"]"
      Comment{} -> intercalate "\n" (map prepend (lines (comment pop)))
    where indentA = "   "
          showContent = map showPaire (popPairs pop)
          showPaire (s,t) = "( "++show s++" , "++show t++" )"
          prepend str = "-- " ++ str

class Unique a => AdlId a where
 uri :: a -> String
 uri = camelCase . uniqueShow False
-- All 'things' that are relevant in the meta-environment (RAP),
-- must be an instance of AdlId:
instance AdlId A_Concept
instance AdlId A_Gen
instance AdlId Atom
instance AdlId ConceptDef
instance AdlId Declaration
instance AdlId Expression
instance AdlId FSpec
instance AdlId A_Pair
instance AdlId Pattern
instance AdlId PlugInfo
instance AdlId PlugSQL
instance AdlId (PlugSQL,SqlField)
instance AdlId Purpose
instance AdlId Rule
instance AdlId Role
instance AdlId Sign
instance AdlId Conjunct
instance AdlId (PairView Expression)
  where uri x = show (typeOf x)++show (hash x)
instance AdlId (PairViewSegment Expression)
  where uri x = show (typeOf x)++show (hash (show (hash x) ++ show (origin x)))
instance AdlId Bool where
 uri = showUnique
instance AdlId a => AdlId [a] where
--instance AdlId (Declaration,Paire)



-- | remove spaces and make camelCase
camelCase :: String -> String
camelCase str = concatMap capitalize (words str)
  where
    capitalize [] = []
    capitalize (s:ss) = toUpper s : ss

nullContent :: Pop -> Bool
nullContent (Pop _ _ _ []) = True
nullContent _ = False
    
class MetaPopulations a where
 metaPops :: FSpec -> a -> [Pop]
class GenericPopulations a where
 generics :: FSpec -> a -> [Pop]   


--------- Below here are some functions copied from Generate.hs TODO: Clean up.
-- Because the signal/invariant condition appears both in generateConjuncts and generateInterface, we use
-- two abstractions to guarantee the same implementation.
isFrontEndInvariant :: Rule -> Bool
isFrontEndInvariant r = not (isSignal r) && not (ruleIsInvariantUniOrInj r)

isFrontEndSignal :: Rule -> Bool
isFrontEndSignal r = isSignal r

-- NOTE that results from filterFrontEndInvConjuncts and filterFrontEndSigConjuncts may overlap (conjunct appearing in both invariants and signals)
-- and that because of extra condition in isFrontEndInvariant (not (ruleIsInvariantUniOrInj r)), some parameter conjuncts may not be returned
-- as either inv or sig conjuncts (i.e. conjuncts that appear only in uni or inj rules) 
filterFrontEndInvConjuncts :: [Conjunct] -> [Conjunct]
filterFrontEndInvConjuncts conjs = filter (\c -> any isFrontEndInvariant $ rc_orgRules c) conjs

filterFrontEndSigConjuncts :: [Conjunct] -> [Conjunct]
filterFrontEndSigConjuncts conjs = filter (\c -> any isFrontEndSignal $ rc_orgRules c) conjs
