-- Copyright (C) 1991-2002, The Numerical Algorithms Group Ltd.
-- All rights reserved.
-- Copyright (C) 2007-2010, Gabriel Dos Reis.
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions are
-- met:
--
--     - Redistributions of source code must retain the above copyright
--       notice, this list of conditions and the following disclaimer.
--
--     - Redistributions in binary form must reproduce the above copyright
--       notice, this list of conditions and the following disclaimer in
--       the documentation and/or other materials provided with the
--       distribution.
--
--     - Neither the name of The Numerical Algorithms Group Ltd. nor the
--       names of its contributors may be used to endorse or promote products
--       derived from this software without specific prior written permission.
--
-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
-- IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
-- TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
-- PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
-- OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
-- EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
-- PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
-- PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
-- LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
-- NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-- SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


import simpbool
import g_-util
namespace BOOT

hasCat(domainOrCatName,catName) ==
  catName="Type"  -- every domain is a Type
    or constructorHasCategoryFromDB [domainOrCatName,:catName]

showCategoryTable con ==
  [[b,:val] for (key :=[a,:b]) in HKEYS _*HASCATEGORY_-HASH_*
     | a = con and (val := HGET(_*HASCATEGORY_-HASH_*,key))]

displayCategoryTable(:options) ==
  conList := IFCAR options
  SETQ($ct,MAKE_-HASHTABLE 'ID)
  for (key:=[a,:b]) in HKEYS _*HASCATEGORY_-HASH_* repeat
    HPUT($ct,a,[[b,:HGET(_*HASCATEGORY_-HASH_*,key)],:HGET($ct,a)])
  for id in HKEYS $ct | null conList or MEMQ(id,conList) repeat
    sayMSG [:bright id,'"extends:"]
    PRINT HGET($ct,id)

genCategoryTable() ==
  SETQ(_*ANCESTORS_-HASH_*,  MAKE_-HASHTABLE 'ID)
  SETQ(_*HASCATEGORY_-HASH_*,MAKE_-HASHTABLE 'UEQUAL)
  genTempCategoryTable()
  domainList:=
    [con for con in allConstructors()
      | getConstructorKindFromDB con = "domain"]
  domainTable:= [addDomainToTable(con,getConstrCat catl) for con
    in domainList | catl := getConstructorCategoryFromDB con]
  -- $nonLisplibDomains, $noCategoryDomains are set in BUILDOM BOOT
  specialDs := SETDIFFERENCE($nonLisplibDomains,$noCategoryDomains)
  domainTable:= [:[addDomainToTable(id, getConstrCat eval([id]).3)
    for id in specialDs], :domainTable]
  for [id,:entry] in domainTable repeat
    for [a,:b] in encodeCategoryAlist(id,entry) repeat
      HPUT(_*HASCATEGORY_-HASH_*,[id,:a],b)
  simpTempCategoryTable()
  -- compressHashTable _*ANCESTORS_-HASH_*
  simpCategoryTable()
  -- compressHashTable _*HASCATEGORY_-HASH_*

simpTempCategoryTable() ==
  for id in HKEYS _*ANCESTORS_-HASH_* repeat
    for (u:=[a,:b]) in getConstructorAncestorsFromDB id repeat
      u.rest := simpHasPred b

simpCategoryTable() == main where
  main() ==
    for key in HKEYS _*HASCATEGORY_-HASH_* repeat
      entry := HGET(_*HASCATEGORY_-HASH_*,key)
      null entry => HREM(_*HASCATEGORY_-HASH_*,key)
      change :=
        atom opOf entry => simpHasPred entry
        [[x,:npred] for [x,:pred] in entry | npred := simpHasPred pred]
      HPUT(_*HASCATEGORY_-HASH_*,key,change)

simpHasPred(pred,:options) == main where
  main() ==
    $hasArgs: local := IFCDR IFCAR options
    simp pred
  simp pred ==
    pred is [op,:r] =>
      op = "has" => simpHas(pred,first r,second r)
      op = 'HasCategory => simp ["has",first r,simpDevaluate second r]
      op = 'HasSignature =>
         [op,sig] := simpDevaluate second r
         ["has",first r,['SIGNATURE,op,sig]]
      op = 'HasAttribute =>
        form := ["has",a := first r,['ATTRIBUTE,b := simpDevaluate second r]]
        simpHasAttribute(form,a,b)
      op in '(AND OR NOT) =>
        null (u := MKPF([simp p for p in r],op)) => nil
        u is '(QUOTE T) => true
        simpBool u
      op = 'hasArgs => ($hasArgs => $hasArgs = r; pred)
      null r and opOf op = "has" => simp first pred
      pred is '(QUOTE T) => true
      op1 := LASSOC(op,'((and . AND)(or . OR)(not . NOT))) => simp [op1,:r]
      simp first pred   --REMOVE THIS HACK !!!!
    pred in '(T etc) => pred
    null pred => nil
    pred
  simpDevaluate a == EVAL substitute('QUOTE,'devaluate,a)
  simpHas(pred,a,b) ==
    b is ['ATTRIBUTE,attr] => simpHasAttribute(pred,a,attr)
    b is ['SIGNATURE,op,sig] => simpHasSignature(pred,a,op,sig)
    IDENTP a or hasIdent b => pred
    npred := eval pred
    IDENTP npred or null hasIdent npred => npred
    pred
  eval (pred := ["has",d,cat]) ==
    x := hasCat(first d,first cat)
    y := rest cat =>
      npred := or/[p for [args,:p] in x | y = args] => simp npred
      false  --if not there, it is false
    x

simpHasSignature(pred,conform,op,sig) == --eval w/o loading
  IDENTP conform => pred
  [conname,:args] := conform
  n := #sig
  u := LASSOC(op,getConstructorOperationsFromDB conname)
  candidates := [x for (x := [sig1,:.]) in u | #sig1 = #sig]  or return false
  match := or/[x for (x := [sig1,:.]) in candidates
                | sig = sublisFormal(args,sig1)] or return false
  simpHasPred(match is [sig,.,:p] and sublisFormal(args,p) or true)

simpHasAttribute(pred,conform,attr) ==  --eval w/o loading
  IDENTP conform => pred
  conname := opOf conform
  getConstructorKindFromDB conname = "category" =>
      simpCatHasAttribute(conform,attr)
  asharpConstructorName? conname =>
    p := LASSOC(attr,getConstructorAttributesFromDB conname) =>
      simpHasPred sublisFormal(rest conform,p)
  infovec := dbInfovec conname
  k := LASSOC(attr,infovec.2) or return nil --if not listed then false
  k = 0 => true
  $domain => kTestPred k    --from koOps
  predvec := $predvec or sublisFormal(rest conform,
      getConstructorPredicatesFromDB conname)
  simpHasPred predvec.(k - 1)

simpCatHasAttribute(domform,attr) ==
  conform := getConstructorForm opOf domform
  catval :=  EVAL mkEvalable conform
  if atom KDR attr then attr := IFCAR attr
  pred :=
    u := LASSOC(attr,catval . 2) => first u
    return false                            --exit: not there
  pred = true => true
  EVAL SUBLISLIS(rest domform,rest conform,pred)

hasIdent pred ==
  pred is [op,:r] =>
    op = 'QUOTE => false
    or/[hasIdent x for x in r]
  pred = '_$ => false
  IDENTP pred => true
  false

addDomainToTable(id,catl) ==
  alist:= nil
  for cat in catl repeat
    cat is ['CATEGORY,:.] => nil
    cat is ['IF,pred,cat1,:.] =>
      newAlist:=
        [[a,:quickAnd(pred,b)] for [a,:b] in getCategoryExtensionAlist0 cat1]
      alist:= [:alist,:newAlist]
    alist:= [:alist,:getCategoryExtensionAlist0 cat]
  [id,:alist]

domainHput(table,key:=[id,:a],b) ==
  HPUT(table,key,b)

genTempCategoryTable() ==
  --generates hashtable with key=categoryName and value of the form
  --     ((form . pred) ..) meaning that
  --           "IF pred THEN ofCategory(key,form)"
  --  where form can involve #1, #2, ... the parameters of key
  for con in allConstructors()  repeat
    getConstructorKindFromDB con = "category" =>
      addToCategoryTable con
  for id in HKEYS _*ANCESTORS_-HASH_* repeat
    item := HGET(_*ANCESTORS_-HASH_*, id) 
    for (u:=[.,:b]) in item repeat
      u.rest := simpCatPredicate simpBool b
    HPUT(_*ANCESTORS_-HASH_*,id,listSort(function GLESSEQP,item))

addToCategoryTable con ==
  -- adds an entry to $tempCategoryTable with key=con and alist entries
  u := CAAR getConstructorModemapFromDB con --domain
  alist := getCategoryExtensionAlist u
  HPUT(_*ANCESTORS_-HASH_*,first u,alist)
  alist

encodeCategoryAlist(id,alist) ==
  newAl:= nil
  for [a,:b] in alist repeat
    [key,:argl] := a
    newEntry:=
      argl => [[argl,:b]]
      b
    u:= assoc(key,newAl) =>
      argl => u.rest := encodeUnion(id,first newEntry,rest u)
      if newEntry ~= rest u then
        p:= moreGeneralCategoryPredicate(id,newEntry,rest u) => u.rest := p
        sayMSG '"Duplicate entries:"
        PRINT [newEntry,rest u]
    newAl:= [[key,:newEntry],:newAl]
  newAl

encodeUnion(id,new:=[a,:b],alist) ==
  u := assoc(a,alist) =>
    u.rest := moreGeneralCategoryPredicate(id,b,rest u)
    alist
  [new,:alist]

moreGeneralCategoryPredicate(id,new,old) ==
  old = 'T or new = 'T => 'T
  old is ["has",a,b] and new is ["has",=a,c] =>
    tempExtendsCat(b,c) => new
    tempExtendsCat(c,b) => old
    ['OR,old,new]
  mkCategoryOr(new,old)

mkCategoryOr(new,old) ==
  old is ['OR,:l] => simpCategoryOr(new,l)
  ['OR,old,new]

simpCategoryOr(new,l) ==
  newExtendsAnOld:= false
  anOldExtendsNew:= false
  ["has",a,b] := new
  newList:= nil
  for pred in l repeat
    pred is ["has",=a,c] =>
      tempExtendsCat(c,b) => anOldExtendsNew:= true
      if tempExtendsCat(b,c) then newExtendsAnOld:= true
      newList:= [pred,:newList]
    newList:= [pred,:newList]
  if not newExtendsAnOld then newList:= [new,:newList]
  newList is [.] => first newList
  ['OR,:newList]

tempExtendsCat(b,c) ==
  or/[first c = a for [[a,:.],:.] in getConstructorAncestorsFromDB first b]

getCategoryExtensionAlist0 cform ==
  [[cform,:'T],:getCategoryExtensionAlist cform]

getCategoryExtensionAlist cform ==
  --avoids substitution as much as possible
  u:= getConstructorAncestorsFromDB first cform => formalSubstitute(cform,u)
  mkCategoryExtensionAlist cform

formalSubstitute(form:=[.,:argl],u) ==
  isFormalArgumentList argl => u
  EQSUBSTLIST(argl,$FormalMapVariableList,u)

isFormalArgumentList argl ==
  and/[x=fa for x in argl for fa in $FormalMapVariableList]

mkCategoryExtensionAlist cform ==
  not cons? cform => nil
  cop := first cform
  MEMQ(cop, $CategoryNames) => mkCategoryExtensionAlistBasic cform
  catlist := formalSubstitute(cform, first getConstructorExports(cform, true))
  extendsList:= nil
  for [cat,:pred] in catlist repeat
    newList := getCategoryExtensionAlist0 cat
    finalList :=
      pred = 'T => newList
      [[a,:quickAnd(b,pred)] for [a,:b] in newList]
    extendsList:= catPairUnion(extendsList,finalList,cop,cat)
  extendsList

-- following code to handle Unions Records Mapping etc.
mkCategoryExtensionAlistBasic cform ==
  cop := first cform
--category:= eval cform
  category :=      -- changed by RSS on 7/29/87
    macrop cop => eval cform
    apply(cop, rest cform)
  extendsList:= [[x,:'T] for x in category.4.0]
  for [cat,pred,:.] in category.4.1 repeat
    newList := getCategoryExtensionAlist0 cat
    finalList :=
      pred = 'T => newList
      [[a,:quickAnd(b,pred)] for [a,:b] in newList]
    extendsList:= catPairUnion(extendsList,finalList,cop,cat)
  extendsList

catPairUnion(oldList,newList,op,cat) ==
  for pair in newList repeat
    u:= assoc(first pair,oldList) =>
      rest u = rest pair => nil
      u.rest := addConflict(rest pair,rest u) where addConflict(new,old) ==
        quickOr(new,old)
    oldList:= [pair,:oldList]
  oldList

simpCatPredicate p ==
  p is ['OR,:l] =>
    (u:= simpOrUnion l) is [p] => p
    ['OR,:u]
  p

simpOrUnion l ==
  if l then simpOrUnion1(first l,simpOrUnion rest l)
  else l

simpOrUnion1(x,l) ==
  null l => [x]
  p:= mergeOr(x,first l) => [p,:rest l]
  [first l,:simpOrUnion1(x,rest l)]

mergeOr(x,y) ==
  x is ["has",a,b] and y is ["has",=a,c] =>
    testExtend(b,c) => y
    testExtend(c,b) => x
    nil
  nil

testExtend(a:=[op,:argl],b) ==
  (u:= getConstructorAncestorsFromDB op) and (val:= LASSOC(b,u)) =>
    formalSubstitute(a,val)
  nil

getConstrCat(x) ==
-- gets a different representation of the constructorCategory from the
-- lisplib, which is a list of named categories or conditions
  x:= if x is ['Join,:y] then y else [x]
  cats:= NIL
  for y in x repeat
    y is ['CATEGORY,.,:z] =>
      for zz in z repeat cats := makeCatPred(zz, cats, true)
    cats:= [y,:cats]
  cats:= nreverse cats
  cats


makeCatPred(zz, cats, thePred) ==
  if zz is ['IF,curPred := ["has",z1,z2],ats,.] then
    ats := if ats is ['PROGN,:atl] then atl else [ats]
    for at in ats repeat
      if at is ['ATTRIBUTE,z3] and not atom z3 and
        constructor? first z3 then
          cats:= [['IF,quickAnd(["has",z1,z2], thePred),z3,'%noBranch],:cats]
      at is ['IF, pred, :.] =>
        cats := makeCatPred(at, cats, curPred)
  cats

getConstructorExports(conform,:options) == categoryParts(conform,
  getConstructorCategoryFromDB opOf conform,IFCAR options)

categoryParts(conform,category,:options) == main where
  main() ==
    cons? := IFCAR options  --means to include constructors as well
    $attrlist: local := nil
    $oplist  : local := nil
    $conslist: local := nil
    conname := opOf conform
    for x in exportsOf(category) repeat build(x,true)
    $attrlist := listSort(function GLESSEQP,$attrlist)
    $oplist   := listSort(function GLESSEQP,$oplist)
    res := [$attrlist,:$oplist]
    if cons? then res := [listSort(function GLESSEQP,$conslist),:res]
    if getConstructorKindFromDB conname = "category" then
      tvl := TAKE(#rest conform,$TriangleVariableList)
      res := SUBLISLIS($FormalMapVariableList,tvl,res)
    res
  build(item,pred) ==
    item is ['SIGNATURE,op,sig,:.] => $oplist   := [[opOf op,sig,:pred],:$oplist]
    --note: opOf is needed!!! Bug in compiler puts in (One) and (Zero)
    item is ['ATTRIBUTE,attr] =>
      constructor? opOf attr =>
        $conslist := [[attr,:pred],:$conslist]
        nil
      opOf attr = 'nothing => 'skip
      $attrlist := [[opOf attr,IFCDR attr,:pred],:$attrlist]
    item is ['TYPE,op,type] =>
        $oplist := [[op,[type],:pred],:$oplist]
    item is ['IF,pred1,s1,s2] =>
      build(s1,quickAnd(pred,pred1))
      s2 => build(s2,quickAnd(pred,['NOT,pred1]))
    null item => 'ok
    item = "%noBranch" => 'ok
    item is ['PROGN,:r] => for x in r repeat build(x,pred)
    systemError '"build error"
  exportsOf(target) ==
    target is ['CATEGORY,.,:r] => r
    target is ['Join,:r,f] =>
      for x in r repeat $conslist := [[x,:true],:$conslist]
      exportsOf f
    $conslist := [[target,:true],:$conslist]
    nil

--------------------> NEW DEFINITION (override in patches.lisp.pamphlet)
compressHashTable ht ==
-- compresses hash table ht, to give maximal sharing of cells
  sayBrightlyNT '"compressing hash table..."
  $found: local := MAKE_-HASHTABLE 'UEQUAL
  for x in HKEYS ht repeat compressSexpr(HGET(ht,x),nil,nil)
  sayBrightly   "done"
  ht

compressSexpr(x,left,right) ==
-- recursive version of compressHashTable
  atom x => nil
  u:= HGET($found,x) =>
    left => left.first := u
    right => right.rest := u
    nil
  compressSexpr(first x,x,nil)
  compressSexpr(rest x,nil,x)
  HPUT($found,x,x)

squeezeList(l) ==
-- changes the list l, so that is has maximal sharing of cells
  $found:local:= NIL
  squeeze1 l

squeeze1(l) ==
-- recursive version of squeezeList
  x:= first l
  y:=
    atom x => x
    z:= member(x,$found) => first z
    $found:= [x,:$found]
    squeeze1 x
  l.first := y
  x:= rest l
  y:=
    atom x => x
    z:= member(x,$found) => first z
    $found:= [x,:$found]
    squeeze1 x
  l.rest := y

updateCategoryTable(cname,kind) ==
  $updateCatTableIfTrue =>
    kind = 'package => nil
    kind = 'category => updateCategoryTableForCategory(cname)
    updateCategoryTableForDomain(cname,getConstrCat(
      getConstructorCategoryFromDB cname))
--+
  kind = 'domain =>
    updateCategoryTableForDomain(cname,getConstrCat(
      getConstructorCategoryFromDB cname))

updateCategoryTableForCategory(cname) ==
  clearTempCategoryTable([[cname,'category]])
  addToCategoryTable(cname)
  for id in HKEYS _*ANCESTORS_-HASH_* repeat
      for (u:=[.,:b]) in getConstructorAncestorsFromDB id repeat
        u.rest := simpCatPredicate simpBool b

updateCategoryTableForDomain(cname,category) ==
  clearCategoryTable(cname)
  [cname,:domainEntry]:= addDomainToTable(cname,category)
  for [a,:b] in encodeCategoryAlist(cname,domainEntry) repeat
    HPUT(_*HASCATEGORY_-HASH_*,[cname,:a],b)
  $doNotCompressHashTableIfTrue = true => _*HASCATEGORY_-HASH_*
  -- compressHashTable _*HASCATEGORY_-HASH_*

clearCategoryTable($cname) ==
  MAPHASH('clearCategoryTable1,_*HASCATEGORY_-HASH_*)

clearCategoryTable1(key,val) ==
  (first key=$cname)=> HREM(_*HASCATEGORY_-HASH_*,key)
  nil

clearTempCategoryTable(catNames) ==
  for key in HKEYS(_*ANCESTORS_-HASH_*) repeat
    MEMQ(key,catNames) => nil
    extensions:= nil
    for (extension:= [catForm,:.]) in getConstructorAncestorsFromDB key
      repeat
        MEMQ(first catForm,catNames) => nil
        extensions:= [extension,:extensions]
    HPUT(_*ANCESTORS_-HASH_*,key,extensions)