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

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

import com.typesafe.scalalogging.Logger

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

  val logger = Logger(Prenexing.getClass)

  def antiPrenexNormalForm(formula: Formula): Formula = { 
    def inner(f: Formula): Formula = f match { 
      case UniversalQuantification(x, Disjunction(ds)) => { 
	val qs = ds.filter(_.contains(x))
	Disjunction(Set(inner(Disjunction(ds--qs)), 
			FolHelpers.universal(x, inner(Disjunction(qs)))))
      }
      case ExistentialQuantification(x, Disjunction(ds)) => { 
	val qs = ds.filter(_.contains(x))
	Disjunction(Set(inner(Disjunction(ds -- qs)),
			FolHelpers.existential(x, inner(Disjunction(qs)))))
      }
      case UniversalQuantification(x, Conjunction(cs)) => { 
	Conjunction(cs.map(c => if(c.contains(x)) inner(UniversalQuantification(x, c)) else inner(c) ))
      }
      case Disjunction(ds) => { 
	val es = ds.filter(_.isInstanceOf[ExistentialQuantification])
	var v = VariableBuilder.newVariable()
	FolHelpers.disjunction((ds--es).map(inner) + FolHelpers.existential(v, Disjunction(es.map(_ match { 
	  case ExistentialQuantification(v2, f) => Substitut(v2,v).apply(inner(f))
	}))))
      }
      case Conjunction(cs) => Conjunction(cs.map(inner))
      case _ => f
    }

    val nnf = UnstructuralTransformations.negationNormalForm(formula)
    logger.debug("NNF: " + nnf.toString)
    inner(nnf)
  }

  def prenexNormalForm(formula: Formula): Formula = { 

    def inner(formula: Formula): Formula = formula match { 
      case Negation(f) => new Negation(inner(f))
      case ExistentialQuantification(v,f) => new ExistentialQuantification(v, inner(f))
      case UniversalQuantification(v,f) => new UniversalQuantification(v, inner(f))
      case Conjunction(cs1) => {
	val cs = cs1.map(inner)
	if(cs.exists(_.isInstanceOf[UniversalQuantification])){ 

	  // possibly change several occurences to use the same variable

	  val newVar = VariableBuilder.newVariable()
	  val newFs = cs.map(x => x match { 
	    case UniversalQuantification(v, f) => (new Substitut(v, newVar)).apply(f)
	    case _ => x
	  })
	  new UniversalQuantification(newVar, inner(new Conjunction(newFs)))
	}

	else { 

	  // only change the next occurrence

	  cs.find(_.isInstanceOf[ExistentialQuantification]) match { 
	    case Some(exis: ExistentialQuantification) => { 
	      val newVar = VariableBuilder.newVariable()
	      val subst = new Substitut(exis.variable, newVar)
	      val newConj = new Conjunction((cs - exis) + subst.apply(exis.formula))
	      new ExistentialQuantification(newVar, inner(newConj))
	    } 
	    case None => new Conjunction(cs)
	  }
	}
      }
      case Disjunction(ds1) => { 
	val ds = ds1.map(inner)
	if(ds.exists(_.isInstanceOf[ExistentialQuantification])){ 

	  // possibly change several occurences to use the same variable

	  val newVar = VariableBuilder.newVariable()
	  val newFs = ds.map(x => x match { 
	    case ExistentialQuantification(v, f) => new Substitut(v, newVar).apply(f)
	    case _ => x
	  })
	  new ExistentialQuantification(newVar, inner(new Disjunction(newFs)))
	}

	else { 

	  // only change the next occurrence

	  ds.find(_.isInstanceOf[UniversalQuantification]) match { 
	    case Some(exis: UniversalQuantification) => { 
	      val newVar = VariableBuilder.newVariable()
	      val subst = new Substitut(exis.variable, newVar)
	      val newDisj = new Disjunction((ds - exis) + subst.apply(exis.formula))
	      new UniversalQuantification(newVar, inner(newDisj))
	    } 
	    case None => new Disjunction(ds)
	  }
	}
      }
      case _ => formula
    }

    val nnf = UnstructuralTransformations.negationNormalForm(formula)
    logger.debug("NNF: " + nnf.toString)
    inner(nnf)
  }

}
