package uk.ac.man.cs.lethe.internal.application.statistics

import java.io.File

import scala.collection.mutable.{ MultiMap, HashMap, Set => MutSet }
import scala.io.Source



object ExperimentFilter { 
  val ALL_FAMILIES = Seq("AL", "ALC", "ALE", "ALU", "EL", "FL", "SIMPLEST")

  var ontologyInformations: HashMap[String, OntologyInformation] = new HashMap()

  abstract class TBoxForm

  var currentExp = ""

  object GENERAL_TBOX extends TBoxForm { 
    override def toString = "General TBox"
  }

  case class AcyclicTBox(expansionDepth: Int) extends TBoxForm { 
    override def toString = "Acyclic TBox (ExpDepth = "+expansionDepth+")"
  }


  var familyMap: MultiMap[String,String] = 
    new HashMap[String, MutSet[String]]() with MultiMap[String, String]

  var families: Seq[String] = Seq()

  var tboxFormMap: MultiMap[TBoxForm, String] = 
    new HashMap[TBoxForm, MutSet[String]]() with MultiMap[TBoxForm, String]

  def tboxForm(tboxForm: TBoxForm) = 
    tboxFormMap.getOrElseUpdate(tboxForm, MutSet[String]())

  var maxDepth = 0

  def ontologyInformation(ontology: String) = { 
    val name = new File(ontology).getName
    ontologyInformations.getOrElse(name, OntologyInformation(name))
  }

  def loadInformation(folder: File): Unit = { 
    if(currentExp==folder.getName)
      return

    loadFamilyMap(folder)
    loadTBoxForms(folder)
    loadSizes(folder)

    currentExp=folder.getName
  }

  def loadFamilyMap(folder: File): Unit = { 
    val familyFile = new File(folder,"families.txt")
    if(!familyFile.exists){ 
      println("Could'nt find"+familyFile.getAbsolutePath)
      return
    }
   
    familyMap = new HashMap[String, MutSet[String]]() with MultiMap[String, String]
 
    val FamilyRE = """(\w+): (.*)""".r
    
    Source.fromFile(familyFile)(io.Codec("ISO-8859-1")).getLines.foreach(_ match { 
      case FamilyRE(family, ontology) => { 
	familyMap.addBinding(family, ontology)
	ontologyInformations.put(ontology, OntologyInformation(name=ontology,family=family))
      }
      case _ => ;
    })

    families = ALL_FAMILIES.filter(familyMap.keySet)
//    println("Families: "+families)
  }



  def loadTBoxForms(folder: File): Unit = { 
    val tboxFormFile = new File(folder,"tbox_forms.txt")
    if(!tboxFormFile.exists){ 
      println("Couldn't find "+tboxFormFile.getAbsolutePath)
      return
    }

    val reException = """Exception: (.*)""".r
    val reGeneralTBox = """General TBox: (.*)""".r
    val reAcyclicTBox = """Acyclic TBox \(ExpDepth = (\d+)\): (.*)""".r

    try { 
      Source.fromFile(tboxFormFile)(io.Codec("ISO-8859-1")).getLines.foreach{ _ match { 
	case reException(ontology) => ;
	case reGeneralTBox(ontology) => { 
	  tboxFormMap.addBinding(GENERAL_TBOX, ontology)
	  var ontInf = ontologyInformations.getOrElse(ontology, OntologyInformation(ontology))
	  ontInf = OntologyInformation(ontology, family=ontInf.family, acyclic=false)
	  ontologyInformations.put(ontology, ontInf)
	}
	case reAcyclicTBox(depth, ontology) => { 
	  tboxFormMap.addBinding(AcyclicTBox(depth.toInt), ontology)
	  maxDepth = Math.max(depth.toInt, maxDepth)
	  var ontInf = ontologyInformations.getOrElse(ontology, OntologyInformation(ontology))
	  ontInf = OntologyInformation(ontology, family=ontInf.family, acyclic=true, depth=depth.toInt)
	  ontologyInformations.put(ontology, ontInf)
	}
	case string => ;
      }}
    } catch { 
      case t: Throwable => t.printStackTrace()
    }
  }

  def loadSizes(folder: File): Unit = { 
    val sizeFile = new File(folder.toString+"/sizes.txt")
    if(!sizeFile.exists){ 
      println("Couldn't find "+sizeFile.getAbsolutePath)
      return
    }

    val reException = """([^\s]*): Exception""".r
    val reSizes = """([^\s]*): (\d+) Axioms, (\d+) symbols""".r

    Source.fromFile(sizeFile).getLines.foreach{ _ match { 
      case reException(ontology) => ;
      case reSizes(ontology, axioms, symbols) => { 
	var ontInf = ontologyInformations.getOrElse(ontology, OntologyInformation(ontology))
	ontInf = OntologyInformation(ontology, 
				     axioms=axioms.toInt,
				     family=ontInf.family, 
				     acyclic=ontInf.acyclic, 
				     depth=ontInf.depth,
				     symbols=symbols.toInt)
//	println(ontInf)
	ontologyInformations.put(ontology, ontInf)
      }
      case string => ;
    }}
    
  }

  def emptyFilter: MappedFilter = MappedFilter(ALL_FAMILIES.map(_=>true).toList,
						 maxExpDepth = maxDepth)

  def ontsForFamilies(families: Set[String]): Set[String] = 
    families.flatMap(familyMap.getOrElse(_, Set[String]()))

  def expDepthBetween(min: Int, max: Int) = 
    (min to max).map(AcyclicTBox).toSet[TBoxForm].flatMap(tboxForm)

//applyOrElse(f, (_:TBoxForm)=>Set()))
}

abstract class ExperimentFilter { 
  def accepts(run: SingleRun): Boolean
}

object EmptyFilter extends ExperimentFilter { 
  override def accepts(run: SingleRun) = true
}

case class MappedFilter(familyBSeq: List[Boolean], 
			h: Boolean = false,
			i: Boolean = false,
			genTBox: Boolean=true,
			acTBox: Boolean=true,
			minExpDepth: Int=0,
			maxExpDepth: Int=ExperimentFilter.maxDepth,
			minSize: Int=0,
			maxSize: Int=Int.MaxValue-1) extends ExperimentFilter { 
  import ExperimentFilter._

//  println("Creating filter...")

  def this(string: String) = 
    this(ExperimentFilter.ALL_FAMILIES.map(string.split("_").toSet.contains).toList,
	 string.contains("_H_"),
	 string.contains("_I_"),
	 string.contains("GEN"),
	 string.contains("AC"),
	 """_MinED(\d+)""".r.findFirstMatchIn(string).get.group(1).toInt,
	 """_MaxED(\d+)""".r.findFirstMatchIn(string).get.group(1).toInt,
         """_MinS(\d+)""".r.findFirstMatchIn(string).get.group(1).toInt,
	 """_MaxS(\d+)""".r.findFirstMatchIn(string).get.group(1).toInt)

  val base: Set[String] = (0 to familyBSeq.size-1).collect{_ match { 
      case i if familyBSeq(i) => ALL_FAMILIES(i)
    } }.toSet


  val filteredFamilies: Set[String] = { 
    var result = base

    if(!h){ 
      result ++= result.map(_+"H")
    } else { 
      result = result.map(_+"H")
    }
    if(!i){ 
      result ++= result.map(_+"I")
    } else { 
      result = result.map(_+"I")
    }
//    println("filtered familes: "+result)
    result
  }

  val ontologies = ontsForFamilies(filteredFamilies)

//  print(ontsForFamilies(filteredFamilies))
  
  override def accepts(run: SingleRun): Boolean = { 
    val ontologyName = new File(run.ontology).getName

    if(!ontologies(ontologyName))
      return false
    else if(!genTBox && tboxFormMap.contains(GENERAL_TBOX) && tboxFormMap(GENERAL_TBOX)(ontologyName))
      return false
    else if(!acTBox && expDepthBetween(0, maxDepth)(ontologyName))
      return false
    else if(!expDepthBetween(minExpDepth, maxExpDepth)(ontologyName) && !tboxForm(GENERAL_TBOX)(ontologyName))
      return false
    else if(run.inputSize<minSize || run.inputSize>maxSize)
      return false
    else
      return true
  }

  def familyString = base.mkString("_")

  def stringRep: String = { 
    var result = familyString
    if(h)
      result += "_H_"
    if(i)
      result += "_I_"
    if(genTBox)
      result += "_GEN"
    if(acTBox)
      result += "_AC"
    result += "_MinED"+minExpDepth.toString
    result += "_MaxED"+maxExpDepth.toString
    result += "_MinS"+minSize.toString
    result += "_MaxS"+maxSize.toString
    result
  }

//  println("Created filter.")
}

case class OntologyInformation(var name: String = "",
			       var size: Int = 0,
			       var axioms: Int = 0,
			       var family: String = "",
			       var acyclic: Boolean = false,
			       var depth: Int = 0,
			       var symbols: Int = 0){ 

}

case class SingleRun(var ontology: String = "",
		     var signature: Set[String] = Set(),
		     var experimentNumber: Int = 0,
		     var completed: Boolean = true,
		     var inputSize: Int = 0,
		     var definersLeft: Int = -1,
		     var moduleSize: Int = -1,
		     var symbolsToForget: Int = 0,
		     var outputSize: Int = -1,
		     var startTime: Long = -1L,
		     var finishTime: Long = -1L,
		     var finished: Boolean = false,
		     var outputAxioms: Int = 0,
		     var signatureSize:Int = -1,
		     var axiomsInput: Int = 0,
		     var maxAxiomSizeInput: Int = 0,
		     var maxAxiomSizeOutput: Int = 0,
		     var numberRestrictions: Int = 0,
		     var outputABoxSize: Int = -1,
		     var outputABoxAssertions: Int = -1,
		     var disjunctiveAssertions: Int = 0,
		     var disjunctionsEliminated: Boolean = false) { 

  var nominals: Int = 0
  var started: Boolean = false

  def duration = { 
    assert(finishTime != -1L, "Finish Time:")
    (finishTime-startTime).toDouble/1000
  }
}
