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

import uk.ac.man.cs.lethe.internal.fol.unification._
import uk.ac.man.cs.lethe.internal.fol.datatypes._

import com.typesafe.scalalogging.Logger


object Config { 
  val selectionFunction = SelectEverything
}


abstract class SelectionFunction { 
  def isSelected(clause: Clause, literal: Literal): Boolean
}

object SelectEverything extends SelectionFunction { 
  override def isSelected(clause: Clause, literal: Literal) = true
}

object SimpleSelectionFunction extends SelectionFunction { 
  override def isSelected(clause: Clause, literal: Literal) = 
    !literal.positive
}

abstract class Rule {
  protected def doesApply: Boolean

  val applies: Boolean = doesApply
  def apply(): Clause

  val selectedLiteral: Literal
  val mainClause: Clause // main clause of this rule 
  val clauses: Iterable[Clause] // clauses on which this rule operates
} 

abstract class RuleGetter { 
  def getRules(clauses: Iterable[Clause], clause: Clause): Iterable[Rule]

  def getRules(clauses: Set[Clause], newClauses: Set[Clause]): Iterable[Rule] =
    newClauses.flatMap(getRules(clauses, _))
}

class RuleNotApplicableException extends Exception

object ResolutionRuleGetter extends RuleGetter {
//  implicit val (logger, formatter, appender) = ZeroLoggerFactory.newLogger(this)

  val logger = Logger(ResolutionRuleGetter.getClass)

  override def getRules(clauses: Iterable[Clause], clause: Clause) = 
    for(clause1 <- clauses;
	literal1 <- clause1.literals if !literal1.positive;
	literal2 <- clause.literals if Config.selectionFunction.isSelected(clause, literal2))
    yield new ResolutionRule(clause1, literal1, clause, literal2)
	
  // get rule applications for selected clause and literal
  def getRules(clauses: Iterable[Clause], clause: Clause, literal: Literal) = { 
    assert(clause.contains(literal))
    for(clause2 <- clauses;
	literal2 <- clause2.literals if Config.selectionFunction.isSelected(clause2, literal2))
    yield new ResolutionRule(clause, literal, clause2, literal2)
  }
}

class ResolutionRule(var clause1:Clause, var literal1:Literal, var clause2:Clause, var literal2:Literal) extends Rule {
  import ResolutionRuleGetter._

  assert(clause1.contains(literal1), clause1.toString+" does not contain "+literal1.toString)
  assert(clause2.contains(literal2), clause2.toString+" does not contain "+literal2.toString)
  
  private var unifier: Substitution = _

  override val selectedLiteral = literal1
  override val mainClause = clause1
  override val clauses = List(clause1, clause2)

  var uniqClause1: Clause = _
  var uniqLiteral1: Literal = _

  override def doesApply = { 
    literal1.positive != literal2.positive && { 
    //literal1.positive == !literal2.positive && { 
      //Make sure clauses don't share variables
      val (uniqueSubst, c) = Unifier.makeVarsUnique(clause1)
      uniqClause1 = c
      uniqLiteral1 = uniqueSubst.apply(literal1)
      
      try {
	unifier = Unifier.unify(uniqLiteral1.predicate, literal2.predicate)
	true
      } catch {
       	case e: NotMatchableException => false
      }
    } 
  }

  override def apply() = {
    if(!applies)
      throw new RuleNotApplicableException()
    logger.trace("Resolving " +clause1.toString + " and " +clause2.toString + " on " +literal1.toString + " and " +literal2.toString)
    
    val result = unifier.apply(uniqClause1.without(uniqLiteral1).merge(clause2.without(literal2)))
    
    assert(result.literals.size <= clause1.literals.size + clause2.literals.size - 2,
result.toString+" from "+clause1.toString+" and "+clause2.toString)
    
    logger.trace("New clause: " +result.toString)

    result
  }

  override def toString = { 
    "Resolution on "+clause1+" and "+clause2 + " on " + literal1 + " and " + literal2
  }
}


object OrderedResolutionRuleGetter extends RuleGetter {
//  implicit val (logger, formatter, appender) = ZeroLoggerFactory.newLogger(this)

  val logger = Logger(OrderedResolutionRuleGetter.getClass)

  override def getRules(clauses: Iterable[Clause], clause: Clause) = 
    for(clause1 <- clauses)
    yield new ResolutionRule(clause1, clause1.literals.head, clause, clause.literals.head)
	
}

class FactoringRule(clause: Clause, literal1: Literal, literal2: Literal) extends Rule {
  import FactoringRuleGetter._
  

  override val selectedLiteral = literal2
  override val mainClause = clause
  override val clauses = List(clause)

  private var unifier: Substitution = _
 
  override def doesApply = { 
    try {
      unifier = Unifier.unify(literal1, literal2)
      true
    } catch {
      case e: NotMatchableException => false
    }
  }
  
  override def apply() = {
    if(!applies)
      throw new RuleNotApplicableException()
    
    logger.trace("Factoring " +clause.toString + " on " +literal1.toString + " and " +literal2.toString)

    assert(clause.contains(literal1), clause.toString+" must contain " + literal1.toString + " ("+clause._literals.toString+")")
    assert(clause.contains(literal2), clause.toString+" must contain " + literal2.toString + " ("+clause._literals.toString+")")
    
    val result = unifier.apply(clause.without(literal1))

    assert(result.literals.size <= clause.literals.size - 1)
    
    logger.trace("New clause: " +result.toString)
    
    result
  }

  override def toString = 
    "Factoring in "+clause+" on "+literal1+" and "+literal2
}

object FactoringRuleGetter extends RuleGetter {
  //implicit val (logger, formatter, appender) =
  //ZeroLoggerFactory.newLogger(this)
  val logger = Logger(FactoringRuleGetter.getClass)

  override def getRules(clauses: Iterable[Clause], clause: Clause) = 
    getRules(clause)

  def getRules(clause: Clause) =
    for(literal1 <- clause.literals;
	literal2 <- clause.literals if literal2 != literal1)
    yield new FactoringRule(clause, literal1, literal2)



}

class ParamodulationRule(clause1: Clause, identity: IdentityPredicate, clause2: Clause, term: Term) extends Rule {
//    implicit val logger = ParamodulationRuleGetter.logger
//    implicit val formatter = ParamodulationRuleGetter.formatter
//    implicit val appender = ParamodulationRuleGetter.appender

  val logger = Logger[ParamodulationRule]

    assert(clause1.contains(new Literal(true, identity)))
    assert(clause2.contains(term))

    override val selectedLiteral = new Literal(true, identity)
    override val mainClause = clause2
    override val clauses = List(clause1, clause2)

    private var unifier: Substitution = _

    override def doesApply = { 
      try {
    	unifier=Unifier.unify(identity.arg1, term)
	true
      } catch {
        case e: NotMatchableException => false
      }
    }

    override def apply() = {
        logger.trace("paramodulation on " +clause1.toString + " and " +clause2.toString + " on " +identity.toString)

        val subst = new GeneralReplacement(term, identity.arg2)
	val clause2x = subst.apply(clause2)
	val result = unifier.apply(clause1.without(new Literal(true, identity)).merge(clause2x))
	
        logger.trace("New clause: "+result)

	result
    }
}

object ParamodulationRuleGetter extends RuleGetter {     
//  implicit val (logger, formatter, appender) =
//  ZeroLoggerFactory.newLogger(this)

  val logger = Logger(ParamodulationRuleGetter.getClass)

  override def getRules(clauses:Iterable[Clause], clause:Clause) = 
    for(clause1 <- clauses;
	identity <- clause1.literals.toList.collect{ case Literal(true, id: IdentityPredicate) => id };
	term <- clause.terms)
    yield new ParamodulationRule(clause1, identity, clause, term)
}


// Only return rules that work on literals of the given set of predicates
class RestrictedRuleGetter(getter: RuleGetter, predicates: Set[String]) extends RuleGetter { 
  override def getRules(clauses: Iterable[Clause], clause: Clause) = { 
    getter.getRules(clauses, clause).filter(x => predicates.contains(x.selectedLiteral.predicate.name))
  }
}

class CombinedRuleGetter(getters: Iterable[RuleGetter]) extends RuleGetter { 

  override def getRules(clauses: Iterable[Clause], clause: Clause) = { 
    getters.flatMap(x => x.getRules(clauses,clause))
  }
}


