package uk.ac.man.cs.lethe.internal.normalForms

import scala.collection.mutable.HashMap
import scala.collection.mutable.MultiMap

import com.typesafe.scalalogging.Logger

import uk.ac.man.cs.lethe.internal.fol.datatypes._
import uk.ac.man.cs.lethe.internal.fol.unification.{ GeneralSubstitution, GeneralReplacement }
import uk.ac.man.cs.lethe.internal.forgetting.Forgetter
import uk.ac.man.cs.lethe.internal.forgetting.scan.SimpleScanForgetterSingleton

object DefinitionalCNFForgetter extends Forgetter[Formula, String] { 
//  implicit val (logger, formatter, appender) = ZeroLoggerFactory.newLogger(DefinitionalCNFLogging)

  val logger = Logger(DefinitionalCNFForgetter.getClass)

  override def steps = SimpleScanForgetterSingleton.steps

  override def forget(formula: Formula, symbols: Set[String]): Formula = { 
    val converter = new DefinitionalCNF()

    val clauses = converter.clauses(Skolemizer.skolemize(formula))
    val forgotten = SimpleScanForgetterSingleton.forget(clauses, symbols)

    val result = converter.translateBack(forgotten)

    logger.info("Forgetting result: \n"+ result.toString)

    result
  }
}

object DefinitionalCNFLogging

object DefinitionalCNFSingleton extends DefinitionalCNF

class DefinitionalCNF extends ClausesGenerator { 
//  implicit val (logger, formatter, appender) = ZeroLoggerFactory.newLogger(DefinitionalCNFLogging)

  val logger = Logger[DefinitionalCNF]

  var mainPredicate: Predicate = _

  def clauses(formula: Formula): Set[Clause] = { 
    var negatives: Set[Clause] = Set()
    var positives: Set[Clause] = Set()

    def genDefinitions(formula: Formula): Predicate = {
      val pred = PredicateGenerator.newPredicate(formula.toString, formula.variables)
      val pos = Literal(true, pred)
      val neg = Literal(false, pred)
      formula match { 
	case Literal(s, p) => 
	  positives += Clause(Set(pos, Literal(!s, p)))
	  negatives += Clause(Set(neg, Literal(s, p)))
	case Negation(f) => 
          val inner = genDefinitions(f)
	  positives += Clause(Set(pos, Literal(true, inner)))
	  negatives += Clause(Set(neg, Literal(false, inner)))
	case cs: Conjunction => 
          val inners = cs.conjuncts.map(genDefinitions(_))
	  positives += Clause(inners.map(Literal(false,_)) + pos)
	  negatives ++= inners.map(x => Clause(Set(neg, Literal(true, x))))
	case ds: Disjunction =>
         val inners = ds.disjuncts.map(genDefinitions(_))
          positives ++= inners.map(x => Clause(Set(pos, Literal(false, x))))
	  negatives += Clause(inners.map(Literal(true, _)) + neg)
	case i: Implication =>
	  val inner1 = genDefinitions(i.formula1)
	  val inner2 = genDefinitions(i.formula2)
          positives += Clause(Set(pos, Literal(true, inner1)))
	  positives += Clause(Set(pos, Literal(false, inner2)))
	  negatives += Clause(Set(neg, Literal(false, inner1), Literal(true, inner2)))
	case e: Equivalence =>
	  val inner1 = genDefinitions(e.formula1)
	  val inner2 = genDefinitions(e.formula2)
	  positives += Clause(Set(pos, Literal(true, inner1), Literal(true, inner2)))
	  positives += Clause(Set(pos, Literal(false, inner1), Literal(false, inner2)))
	  negatives += Clause(Set(neg, Literal(false, inner1), Literal(true, inner2)))
	  negatives += Clause(Set(neg, Literal(true, inner1), Literal(false, inner2)))
	// Assumption: Formula is already skolemized
      }
      pred
    }

    mainPredicate = genDefinitions(formula)

    logger.trace("Negatives: ")
    logger.trace(negatives.mkString("\n"))
    logger.trace("\nPositives: ")
    logger.trace(positives.mkString("\n"))
    logger.trace("\n")

    val clauses = (negatives++positives)

    clauses.toSet[Clause].flatMap(_.literals).toSet[Literal].foreach(x => logger.trace("DEFINITION: "+x.toString + " -> " +translateBack(x, clauses.toSet, true).toString))
    clauses
  }

  def translateBack(clauses: Iterable[Clause]): Formula = { 

   // clauses.flatMap(_.literals).toSet[Literal].foreach(x => logger.trace(x.toString ++= " becomes " +translateBack(x, clauses.toSet, true).toString))

    logger.info("translating back:\n"+ clauses.mkString("\n"))
    translateBack(Literal(true, mainPredicate), clauses.toSet, true)
  }

  def translateBack(literal: Literal, clauses: Set[Clause], polarity: Boolean): Formula = literal match { 
      case Literal(true, p) if !isDefined(p) => Literal(true, p)
      case Literal(false, p) if !isDefined(p) => Literal(false, p)
      case Literal(true, p) => getFormula(p, clauses, polarity)
      case Literal(false, p) => getFormula(p, clauses, !polarity)
    } 
 
  // 1  1   1   
  // 1  0   0
  // 0  1   0
  // 0  0   1

  def isDefined(predicate: Predicate) = predicate.name.startsWith("_L")

  def getFormula(predicate: Predicate, clauses: Set[Clause], polarity: Boolean): Formula = { 
    logger.trace(clauses.toString)
    logger.trace("unfold " +(if(polarity==false) "¬" else "") + predicate.toString)
    val definition = getDefinition(predicate, clauses, polarity)
    logger.trace("definition: " +definition.toString)
    val newClauses = clauses.filterNot(_.literals.exists(_.predicate.name==predicate.name))

    val result = 
      if(polarity==true)
	FolHelpers.conjunction(definition.map( 
	  c => 
	    FolHelpers.disjunction(unfoldDefinitions(c.literals.filterNot(l => l.positive==false && l.predicate.name==predicate.name), 
						     newClauses,
						     true))))
      else
	FolHelpers.conjunction(definition.map( 
	  c => 
	    FolHelpers.disjunction(unfoldDefinitions(c.literals.filterNot(l => l.positive==true && l.predicate.name==predicate.name), 
						     newClauses,
						     false))))
	
      
    logger.trace(result.toString)  
    result
  }

  def getDefinition(predicate: Predicate, clauses: Set[Clause], polarity: Boolean): Iterable[Clause] =
    clauses.collect { 
      case clause if clause.literals.exists(l => l.predicate.name==predicate.name && l.positive!=polarity) => 
	val definer = clause.literals.collectFirst({ case l: Literal if l.predicate.name==predicate.name && l.positive!=polarity => l}).get
        val subst = getSubst(definer.predicate, predicate)
	subst.apply(clause)
		   }

  def getSubst(pred1: Predicate, pred2: Predicate): GeneralSubstitution = { 
    new GeneralSubstitution(pred1.args.zip(pred2.args).map(x => new GeneralReplacement(x._1, x._2)).toSet[GeneralReplacement])
  }

  def unfoldDefinitions(literals: Set[Literal], clauses: Set[Clause], polarity: Boolean): Set[Formula] =
    literals.map(translateBack(_, clauses, polarity))
}



object PredicateGenerator { 
  var counter = 0

  def newPredicate(name: String, variables: Iterable[Variable]) = { 
    counter += 1
//    GenericPredicate("_L["+name+"]"+counter.toString, variables.toList)
    GenericPredicate("_L"+counter.toString, variables.toList)
  }

  def newPredicate(variables: Iterable[Variable]) = { 
    counter += 1
    GenericPredicate("_L"+counter.toString, variables.toList)
  }
}
