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

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


/**
 * Lifts an ordering of literals to an ordering of clauses
 */
class ClauseOrdering(ordering: Ordering[Literal]) extends Ordering[Clause] { 
  override def compare(clause1: Clause, clause2: Clause): Int = { 
    def compareLists(l1: Iterable[Literal], l2: Iterable[Literal]): Int = (l1, l2) match { 
      case (Nil, Nil) => 0
      case (_, Nil) => 1
      case (Nil, _) => -1
      case (a::as, b::bs) =>
	val cmp = ordering.compare(a,b)
	if(cmp!=0)
	  cmp
	else
	  compareLists(as, bs)
    }
    compareLists(clause1.literals.toList, clause2.literals.toList)
  }
}

/**
 * Prefer symbols from a given set
 */
class RestrictedOrdering(ordering: Ordering[Literal], preferedSymbols: Set[String]) extends Ordering[Literal] { 
  override def compare(l1: Literal, l2: Literal): Int = (l1, l2) match { 
    case (Literal(_, GenericPredicate(n1, _)), Literal(_, GenericPredicate(n2, _))) => 
      val preferN1 = preferedSymbols.contains(n1)
      val preferN2 = preferedSymbols.contains(n2)
      if(preferN1 && !preferN2)
	1
      else if(!preferN1 && preferN2)
	-1
      else
	ordering.compare(l1, l2)
    case _ => ordering.compare(l1, l2)
  }
}

/**
 * Ordering to solve the monadic class
 */
object MonadicOrdering extends Ordering[Literal] { 

  def contains(term1: Term, term2: Term): Boolean = { 
    term1 match { 
      case Function(_, args) if args.exists(Unifier.unifies(_,term2)) => true
      case _ => false
    }
  }

  override def compare(l1: Literal, l2: Literal) = { 
    val result = (l1, l2) match { 
      case (Literal(_, GenericPredicate(_, arg1::Nil)), 
	    Literal(_, GenericPredicate(_, arg2::Nil))) => { 
	      val contains12 = contains(arg1, arg2)
	      val contains21 = contains(arg2, arg1)
	      if(contains12 && !contains21)
		1
	      else if(contains21 && !contains12)
		-1
	      else (arg1, arg2) match { 
		case (Function(_, args1), Function(_, args2)) if args1.size>args2.size => 1
		case (Function(_, args1), Function(_, args2)) if args1.size<args2.size => -1
		case _ => l1.compareTo(l2)
	      }
	    }
      case (l1, l2) => l1.compareTo(l2)
    }
    result
  }
}

/**
 * Ordering to solve clauses from the guarded fragment
 * taken from "A Resolution Decision Procedure For The Guarded Fragment", Hans de Nivelle, 1998
 */
object GuardedFragmentOrdering extends ProperLiteralOrdering { 
  override def compare(l1: Literal, l2: Literal) = { 
    (bigger(l1, l2), bigger(l2,l1)) match { 
      case (true, false) => 1
      case (false, true) => -1
      case _ => l1.toString.compareTo(l2.toString)
    }
  }

  override def bigger(l1: Literal, l2: Literal) = 
    vardepth(l1)>vardepth(l2) || l2.variables.subsetOf(l1.variables)
  
  def vardepth(e: Expression): Integer = e match { 
    case e if ground(e) => -1
    case v: Variable => 0
    case Function(_, args) => args.map(vardepth).max+1
    case p: Predicate => p.args.map(vardepth).max+1
    case Literal(_, p) => vardepth(p)
  }
  
  def ground(e: Expression) = e.variables.isEmpty
    
}

class DepthBasedOrdering(ordering: Ordering[Literal]) extends Ordering[Literal] { 

  def varDepth(expr: Expression): Int = expr match { 
    case _: Variable => 0
    case _: Constant => -1
    case Function(_, args) => 1+args.map(varDepth).max
    case GenericPredicate(_, args) => 1+args.map(varDepth).max
    case Literal(_, p) => varDepth(p)
    case Clause(ls) => ls.map(varDepth).max
  }

  def vars(expr: Expression): Set[Variable] = expr match { 
    case v: Variable => Set(v)
    case _: Constant => Set()
    case Function(f, args) => args.flatMap(vars).toSet[Variable]
    case GenericPredicate(_, args) => args.flatMap(vars).toSet[Variable]
    case Literal(_, p) => vars(p)
    case Clause(ls) => ls.flatMap(vars)
  }

  override def compare(l1: Literal, l2: Literal) = { 
    val (d1, d2) = (varDepth(l1), varDepth(l2))
    val (v1, v2) = (vars(l1), vars(l2))
    if(d1>d2)
      1
    else if(d1<d2)
      -1
    else if(l1.predicate.args.size>l2.predicate.args.size)
      -1
    else if(l2.predicate.args.size>l1.predicate.args.size)
      1
    // else if(v1.subsetOf(v2) && !v2.subsetOf(v1))
    //    -1
    //  else if(v2.subsetOf(v1) && !v1.subsetOf(v2))
    //    1
    else
      ordering.compare(l1, l2)
  }
}

