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

import uk.ac.man.cs.lethe.internal.dl.filters.AxiomAnalysis
import uk.ac.man.cs.lethe.internal.dl.owlapi.ModuleExtractor
import uk.ac.man.cs.lethe.internal.dl.owlapi.{OWLApiConfiguration, OWLApiConverter, OWLApiInterface, OWLParser}

import java.io.File
import java.util.Date
import java.util.concurrent.TimeoutException
import uk.ac.man.cs.lethe.internal.dl.forgetting.abox.ABoxForgetter
import uk.ac.man.cs.lethe.internal.dl.interpolation.OntologyInterpolator

import scala.collection.immutable.TreeSet
import scala.concurrent._
import scala.concurrent.duration._
import scala.io.Source
import scala.util.matching.Regex.Match
import scala.util.Random
import scala.util.parsing.json.JSON

//import org.json4s._
//import org.json4s.native._

import org.semanticweb.owlapi.io.OWLOntologyCreationIOException
import org.semanticweb.owlapi.model._
import uk.ac.man.cs.lethe.internal.dl.datatypes._

import uk.ac.man.cs.lethe.internal.dl.filters.OWLOntologyFilters
import uk.ac.man.cs.lethe.internal.dl.forgetting.DirectALCForgetter
import uk.ac.man.cs.lethe.internal.dl.forgetting.{ ConceptAndRoleForgetter, SHQForgetter}
import uk.ac.man.cs.lethe.internal.dl.owlapi._

object InterpolationExperimenter { 

  implicit val exec = new CustomExecutionContext()

  var benchmarkOntologies: Set[String] = _
  
  var runsPerOntology: Int = _
  
  var sizesOfSignature: Iterable[Int] = _
  
  var maxUsageOfWord: Double = _

  var timeOut: Duration = _

  var dontForget = Set[String]() //"by", "for", "of", "Thing", "hasPart", "hasPurpose", "Atom")

  var coherentSignatures: Boolean = _

  var noRoleForgetting: Boolean = _

  var shqForgetting: Boolean = false

  var probabilistic: Boolean = false
  var probability: Double = 0.5

  var aboxForgetting: Boolean = false
  
  
  /**
   * Load settings from json file.
   *
   * The file is expected to have the correct format. No error checking is performed by this function. 
   */
  def loadConfiguration(configurationFile: File) = { 
    val confString = Source.fromFile(configurationFile).getLines().mkString("\n")

//    val json = JsonMethods.parse(confString)

//    json match { 
    JSON.parseFull(confString) match { 
      case Some(map: Map[String, Any]) =>  
	benchmarkOntologies = Set()
      runsPerOntology = map("runsPerOntology").asInstanceOf[Double].toInt
      sizesOfSignature = map("sizesOfSignature").asInstanceOf[List[Double]].map(_.toInt)
      maxUsageOfWord = map("maxUsageOfWord").asInstanceOf[Double]
      timeOut = map("timeOut").asInstanceOf[Double].toInt seconds
      
      // check whether we want to chose coherent signatures
      map.get("coherentSignatures") match { 
	case Some(thing) => println("Using coherent signatures"); coherentSignatures = true
	case None => coherentSignatures = false
      }

      // do not forget roles when computing uniform interpolant
      map.get("noRoleForgetting") match { 
	case Some(thing) => println("no role forgetting"); noRoleForgetting = true
	case None => noRoleForgetting = false
      }

      // Check if ontology list is given
      map.get("benchmarkOntologies") match { 
	case Some(ontologies) => benchmarkOntologies ++= ontologies.asInstanceOf[List[String]]
	case None => ;
      }

      map.get("benchmarkOntologiesDir") match { 
	case Some(dir: String) if new File(dir).exists() => benchmarkOntologies ++= 
	new File(dir).listFiles.map(_.getAbsolutePath)
	case None => ;
	case Some(dir) => println("Warning! Invalid directory: "+dir.toString)
      }

      map.get("ignore") match { 
	case Some(list) => dontForget ++= list.asInstanceOf[List[String]]
	case None => ;
      }

      map.get("benchmarkRoot") match { 

	// There is a benchmark root given
	case Some(root) =>
	  var benchmark = new Benchmark(new File(root.asInstanceOf[String]))
	
	// check whether we want to filter the benchmark 
	map.get("expressivities") match { 
	  case Some(exps) => 
	    benchmark = benchmark.filterOntologies("expressivity", exps.asInstanceOf[List[String]])
	  case None => ;
	}
	map.get("excludeFamilies") match { 
	  case Some(exps) => 
	    println("Exclude families: "+exps.asInstanceOf[List[String]].mkString(", "))
	  benchmark = benchmark.filterOutOntologies("expressivity", exps.asInstanceOf[List[String]])
	  case None => ;
	}

        // Add all ontologies of the benchmark
        benchmarkOntologies ++= benchmark.ontologies
	
	// There is no benchmark root given
	case None => ;
      }

      map.get("ignoreOntologies") match { 
	case Some(ontologies) =>
	  benchmarkOntologies --= ontologies.asInstanceOf[List[String]]
	case None => ;
      }
      
      map.get("dontForget") match { 
	case Some(dont) => dontForget ++= dont.asInstanceOf[List[String]]
	case None => ;
      }

      map.get("probabilistic") match { 
	case Some(_) => { 
	  probabilistic = true
	  println("Probabilistic signature selection, probability for each symbol: "+probability)
	}
	case _ => probabilistic = false;
      }

      case other => println("nothing parsed: "+other.toString)
    }

  }

  /**
   * Run the experiments
   *
   * simulate: don't run experiments, just create output
   * seed: seed for the random number generator
   * continue: continue canceled experiment, skip all runs below that number
   */
  def run(simulate: Boolean, seed: Long, continue: Int) = { 

    val ran = new Random(seed)

    println("Seed: "+seed.toString)
    println("Time-out set to "+timeOut.toString)

    var forgetter = 
      if(aboxForgetting)
	ABoxForgetter
      else if(shqForgetting)
	SHQForgetter
      else if(noRoleForgetting) 
	DirectALCForgetter 
      else
	ConceptAndRoleForgetter

    ConceptAndRoleForgetter.filter=true

    val interpolator = new OntologyInterpolator(forgetter)
    interpolator.dontForget = dontForget

    var counter = 0

    println("Not forgetting "+dontForget)

    OWLApiConfiguration.SIMPLIFIED_NAMES=false


    //    ran.shuffle(benchmarkOntologies.toSeq).foreach { ont =>
    benchmarkOntologies.toSeq.foreach {  ont => 
      try { 
	if(ont!=null) { 

	  println("Trying "+ont+"...")
	  
	  var owlOntology = try{ 
	    OWLApiInterface.getOWLOntology(ont)
	  } catch { 
	    case _:IllegalArgumentException => OWLApiInterface.getOWLOntology(new File(ont))
	  }

	  AxiomAnalysis.analyse(owlOntology) // give us some nice information about this thing

	  if(forgetter==DirectALCForgetter)
	    OWLOntologyFilters.restrictToALCH(owlOntology)

	  val ontology = OWLApiConverter.convert(owlOntology)

	  val signature = OWLApiInterface.getSignature(owlOntology)//.filter(s => 
	  //	  s.isInstanceOf[OWLClass]||s.isInstanceOf[OWLObjectProperty])
	  

	  var skipSucceedingSignatures=false

	  sizesOfSignature.foreach { sizeOfSignature => 
	    if(sizeOfSignature>=signature.size){ 
	      println("Skipped (sizeOfSignature>=signature.size)")
	      // } else if(skipSucceedingSignatures){ 
	      //   println("Skipped (succeeding signatures Timeout) ("+ontology+", "+signature.size+")")
	    } else
	      if(sizeOfSignature<signature.size){ 
		skipSucceedingSignatures=true
		for(i <- 1 to runsPerOntology) { 
		  println("\n\n\n\nExperiment number: "+counter.toString)

		  val output = ont+"_"+sizeOfSignature+"_"+i+".owl"
		  
		  if(new File(output).exists())
		    println(output+" exists. Skipping.")
		  else{ 
		    println("Ontology: "+ont)
		    println("Ontology's signature size: "+signature.size)
		    println("Run number: "+i)
		    
		    if(!simulate){ 
		      var referenceSignature = if(coherentSignatures)
			ModuleExtractor.coherentSignature(owlOntology, sizeOfSignature)
					       else
						 signature

		      var interpolantsSignature = 
			if(!probabilistic)
			  ran.shuffle(referenceSignature.toSeq).slice(0, sizeOfSignature).toSet
			else 
			  signature.filter(a => ran.nextDouble<probability)

		      if(counter<continue){
			println("skipped")
		      }else { 
			println("Size: "+ontology.size)
			println("Axioms: "+ontology.statements.size)
			println("Max axiom size: "+ontology.statements.map(_.size).max)
			println("ABox size: "+ontology.abox.size)
			println("ABox assertions: "+ontology.abox.assertions.size)
			println("Qualified Number Restrictions: "+countRealQualifiedNumberRestrictions(ontology))
			println()
			println("Desired signature: "+(interpolantsSignature.map(_.getIRI.getFragment)))
			println("Desired signature size: "+interpolantsSignature.size)
			println()
			println("Started at "+(new Date()).toString)
			println()
			
			try { 
			  val futureResult = future[Ontology]{ 
			    interpolator.uniformInterpolantInternalFormat(owlOntology, interpolantsSignature.toSet[OWLEntity])
			  }
			  var result = Await.result(futureResult, timeOut)
			  println("Size of the output: "+result.size)
			  println("Number of axioms: "+result.tbox.axioms.size)
			  println("Max axiom size (output): "+(if(result.statements.isEmpty) 0 else result.statements.map(_.size).max))
			  result = DLHelpers.splitConjunctions(result)
			  println("Size of the output (split conjunctions): "+result.size)
			  println("Number of axioms (split conjunctions): "+result.tbox.axioms.size)
			  println("Max axiom size (split conjunctions): "+(if(result.statements.isEmpty) 0 else result.statements.map(_.size).max))
			  println("Output ABox size: "+result.abox.size)
			  println("Output ABox assertions: "+result.abox.assertions.size)
			  println("Cyclic definers left: "+countDefiners(result))
			  val caDisjunctions = result.abox.assertions.filter(_.isInstanceOf[DisjunctiveConceptAssertion]).size
			  println("Nominals: " + countNominals(result))
			  println("Number Restrictions in result: "+countNumberRestrictions(result))
			  println("Qualified Number Restrictions in result: "+countRealQualifiedNumberRestrictions(result))
			  skipSucceedingSignatures=false
			} catch { 
			  case e: RuntimeException => 
			    e.printStackTrace() 
			  println("Runtime exception: "+e.getMessage())
			  case e: TimeoutException => 
			    println("Timeout exceeded.")
			  exec.lastThread.getOrElse(throw new RuntimeException("Not started.")).stop()
			}
			(new File("dlForgetting.log")).delete()
			(new File("formulaPreparations.log")).delete()
			Runtime.getRuntime().exec("rm *.log")
			println("Finished at "+(new Date()).toString)
		      }
		      counter += 1
		      System.gc()
		      //	      println("Exporting to "+output)
		      //	      new OWLExporter().exportOntology(result, new File(output))
		      ConceptAndRoleForgetter.clean()
		    }
		  }		   
		}
	      }
				  }
	}
      } catch { 
	case e: OWLOntologyCreationIOException => println("Exception while loading ontology: "+e.toString)
	case e: Exception => 
	  println("Exception thrown: "+e.toString)
	e.printStackTrace()
      }
				     }
  }

  def countDefiners(ontology: Ontology) = { 
    ontology.atomicConcepts.filter(_.startsWith("_D")).size
  }

  def countNominals(ontology: Ontology) = { 
    var counter = 0
    ontology.foreachNested(c => c match { 
      case _: NominalSet => counter +=1
      case _ => ;
    })
    counter
  }

  def countNumberRestrictions(ontology: Ontology) = { 
    var counter = 0
    ontology.foreachNested(c => c match { 
      case _: MinNumberRestriction => counter += 1
      case _: MaxNumberRestriction => counter += 1
      case c => ;
    })
    counter
  }

  def countRealQualifiedNumberRestrictions(ontology: Ontology) = { 
    var counter = 0
    ontology.foreachNested(c => c match { 
      case MinNumberRestriction(n,r,f) if n>1 && f!=TopConcept => counter += 1
      case MaxNumberRestriction(n,r,f) if n>0 && f!=TopConcept => counter += 1
      case _ => ;
    })
    counter
  }


  def parseLastOntologies() = { 

    var pastOntologies = Set[String]()

    val resultsFile: File = new File("results.txt")
    
    val resultsLines = Source.fromFile(resultsFile).getLines()

    val reOntologyName = """Ontology: (.+)""".r

    val lines = resultsLines.foreach{ line =>
      reOntologyName.findFirstMatchIn(line).foreach{ 
	pastOntologies += _.group(1) 
      }
    }

    println("Ignoring: "+pastOntologies)

    benchmarkOntologies --= pastOntologies
  }

  case class FailedExperiment(var ontology: String, var signature: Set[String], var numberToForget: Int) { 
    def this(ontology: String) = this(ontology,Set(),-1)
    def this() = this("")
  }
  
  var failedExperiments: Set[FailedExperiment] = Set()

  def parsePastResults() = { 
    val resultsFile: File = new File("results.txt")    
    val resultsLines = Source.fromFile(resultsFile).getLines()

    val reOntologyName = """Ontology: (.+)""".r
    val reSignature = """Desired signature: Set\(([^\)]+)\)""".r
    val reNumber = """Forgetting (\d+) symbols.""".r
    val reFailed = """Timeout exceeded.""".r
    val reFinished = """Finished at .*""".r

    var currentExperiment = new FailedExperiment()
    var failed = false

    val lines = resultsLines.foreach{ line =>
      reOntologyName.findFirstMatchIn(line).foreach{ m: Match =>
	currentExperiment = new FailedExperiment(m.group(1))
	failed = false  
      }

      reSignature.findFirstMatchIn(line).foreach{ m: Match =>
	currentExperiment.signature = m.group(1).split(", ").toSet[String]
      }
				    
      reNumber.findFirstMatchIn(line).foreach{ m: Match =>
	currentExperiment.numberToForget = m.group(1).toInt
      }

      if(reFailed.findFirstMatchIn(line)!=None && !currentExperiment.signature.isEmpty) { 
	   failedExperiments += currentExperiment
	   failed = true
	 }
				     
      if(reFinished.findFirstMatchIn(line)!=None && !failed) { 
	failedExperiments -= currentExperiment // in case it failed in an earlier attempt
      }
    }

//    failedExperiments = 
 //     failedExperiments.filterNot(e => ignoreExperiments(FailedExperiment(e.ontology, e.signature,-1)))
  }


  def redoFailedExperiments(timeOut: Duration) = { 
    val experimentsInOrder = failedExperiments.toSeq.sortBy(_.numberToForget)

    val forgetter = if(noRoleForgetting) 
	DirectALCForgetter 
      else 
	ConceptAndRoleForgetter

    val interpolator = new OntologyInterpolator(forgetter)
    interpolator.dontForget = dontForget
      
    experimentsInOrder.foreach{ experiment =>
      try{ 

	println("\n\n\n\nRedoing with timeout "+timeOut.toString)
	println("Ontology: "+experiment.ontology)
	println("Signature size: "+experiment.signature.size)
	
	var owlOntology = try{ 
	  OWLApiInterface.getOWLOntology(experiment.ontology)
	} catch { 
	  case _:IllegalArgumentException => OWLApiInterface.getOWLOntology(new File(experiment.ontology))
	}
	
	if(forgetter==DirectALCForgetter)
	  OWLOntologyFilters.restrictToALCH(owlOntology)
	
	val ontology = OWLApiConverter.convert(owlOntology)
	
	val signature = OWLApiInterface.getSignature(owlOntology)
	val interpolantsSignature = signature.filter{ s: OWLEntity => experiment.signature(s.getIRI.getFragment)}
	
	println("Size: "+ontology.size)
	println("Axioms: "+ontology.statements.size)
	println("Max axiom size: "+ontology.statements.map(_.size).max)
	println()
	println("Desired signature: "+interpolantsSignature.map(_.getIRI.getFragment))
	println()
	println("Started at "+(new Date()).toString)
	println()
		      
	try { 
	  val futureResult = future[Ontology]{ 
	    interpolator.uniformInterpolantInternalFormat(owlOntology, interpolantsSignature)
	  }
	  var result = Await.result(futureResult, timeOut)
	  println("Size of the output: "+result.size)
	  println("Number of axioms: "+result.tbox.axioms.size)
	  println("Max axiom size (output): "+(if(result.statements.isEmpty) 0 else result.statements.map(_.size).max))
	  result = DLHelpers.splitConjunctions(result)
	  println("Size of the output (split conjunctions): "+result.size)
	  println("Number of axioms (split conjunctions): "+result.tbox.axioms.size)
	  println("Max axiom size (split conjunctions): "+(if(result.statements.isEmpty) 0 else result.statements.map(_.size).max))
	  println("Cyclic definers left: "+countDefiners(result))
	} catch { 
	  case e: RuntimeException => println("Runtime exception: "+e.getMessage())
	  case e: TimeoutException => 
	    println("Timeout exceeded.")
	    exec.lastThread.getOrElse(throw new RuntimeException("Not started.")).stop()
	  case e: OutOfMemoryError => e.printStackTrace(); println("Stopped process"); System.exit(1)
	}
	(new File("dlForgetting.log")).delete()
	(new File("formulaPreparations.log")).delete()
	println("Finished at "+(new Date()).toString)

	ConceptAndRoleForgetter.clean()
//	System.gc()
      } catch { 
	case e: OWLOntologyCreationIOException => println("Exception while loading ontology: "+e.toString)
	case e: OutOfMemoryError => e.printStackTrace(); println("Stopped due memory error"); System.exit(1)
	case e: Exception => 
	  println("Exception thrown: "+e.toString)
	e.printStackTrace()
      }
     }
  }

  def main(_args: Array[String]): Unit = { 

    if(_args.size==0){ 
      println("Usage: Experimenter CONFIGURATION [SEED [CONTINUE]] [--ont ontology] [simulate] [continue]")
      println("Usage: Experimenter redo TIME_OUT")
      println("Further parameters: --aboxForgetting --shq") 
      System.exit(0)
    }


    var args = _args.toSeq

    if(args.contains("--aboxForgetting")){ 
      args = args.filter(_!="--aboxForgetting")
      aboxForgetting = true
    }
  
    if(args(0)=="redo"){ 
      parsePastResults()
      val newTimeout = args(1).toInt.seconds
      redoFailedExperiments(newTimeout)
      System.exit(0)
    }
  
    loadConfiguration(new File(args(0)).getAbsoluteFile())
    
    var simulate = false
    var seed = (new Date()).getTime()
    var continue = 0
    
    var bContinue = false


    if(args.contains("continue")){ 
      args = args.filter(_!="continue")
      parseLastOntologies()
    }
    

    if(args.contains("simulate")){ 
      args = args.filter(_!="simulate")
      simulate = true
    }
    
    if(args.size>1 && args(1)=="--ont"){ 
      benchmarkOntologies = Set(args(2))
      args = args.filterNot(s => s=="--ont"||s==args(2)) //oops
    }

    println(args)

    if(args.exists(_=="--shq")){ 
      println("SHQ forgetting")
      args = args.filterNot(_=="--shq")
      shqForgetting = true
    }

    if(args.size>1)
      seed = args(1).toLong

    if(args.size>2)
      continue = args(2).toInt

    
    // benchmarkOntologies.foreach{ ont => 
    //   println("Ontology: "+ont)

    //   val owlOntology = OWLApiInterface.getOWLOntology(new File(ont))
    //   val signature = OWLApiInterface.getSignature(owlOntology)

    //   var ontology = OWLApiConverter.convert(owlOntology)
    //   ontology = OntologyFilter.restrictToSHQ(ontology)
    //   println("Signature size: "+signature.size)
    //   println("Size: "+ontology.size)
    //   println("Axioms: "+ontology.statements.size)
    //   println("Max axiom size: "+ontology.statements.map(_.size).max)
    //   println("Number restrictions: "+countNumberRestrictions(ontology))
    //   println()
    // }
//    System.exit(0)

    run(simulate, seed, continue)
  }
}

object SHQStatistics { 
  // Class to count number restrictions occuring in ontologies

  def main(args: Array[String]) = { 
    val file = new File(args(0))
    var ont = OWLParser.parseFile(new File(args(0)))
    ont = OntologyFilter.restrictToSHQ(ont)

    println(file.getName+": %7d TBox Axioms" format ont.tbox.axioms.size)
    println(file.getName+": %.1f average axiom size" format ont.tbox.size.toDouble/ont.tbox.axioms.size)
    println(file.getName+": %7d symbols in signature" format ont.signature.size)
    println(file.getName+": %7d transitive roles in SHQ fragment" format ont.rbox.axioms.filter(_.isInstanceOf[TransitiveRoleAxiom]).size)
    println(file.getName+": %7d Number Restrictions in SHQ fragment" format 
	    InterpolationExperimenter.countNumberRestrictions(ont))
    println(file.getName+": %7d 'Real' Qualified Number Restrictions in SHQ fragment" format 
	    InterpolationExperimenter.countRealQualifiedNumberRestrictions(ont))
  }
}
