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


import uk.ac.man.cs.lethe.internal.dl.owlapi.{OWLApiConverter, OWLApiInterface}

import java.io.File
import java.util.Date
import java.util.concurrent.TimeoutException
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.semanticweb.owlapi.io.OWLOntologyCreationIOException
import uk.ac.man.cs.lethe.internal.dl.datatypes.{DLHelpers, Ontology}
import uk.ac.man.cs.lethe.internal.dl.filters.OWLOntologyFilters
import uk.ac.man.cs.lethe.internal.dl.forgetting.{RoleForgetter, RoleForgetterS}
import uk.ac.man.cs.lethe.internal.dl.owlapi._


object RoleForgettingExperimenter { 

  implicit val exec = new CustomExecutionContext()

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

  var timeOut: Duration = _

  val DONT_FORGET = Set() //"by", "for", "of", "Thing", "hasPart", "hasPurpose", "Atom")

//   /**
//    * 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")

    JSON.parseFull(confString) match { 
      case Some(map: Map[String, Any]) =>  
	benchmarkOntologies = Set()
	runsPerOntology = map("runsPerOntology").asInstanceOf[Double].toInt
	numsOfWordsToForget = map("numsOfWordsToForget").asInstanceOf[List[Double]].map(_.toInt)
	maxUsageOfWord = map("maxUsageOfWord").asInstanceOf[Double]
	timeOut = map("timeOut").asInstanceOf[Double].toInt seconds


//         // 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) => benchmarkOntologies ++= 
				    new File(dir.asInstanceOf[String]).listFiles.map(_.getAbsolutePath)
	  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 => ;
	}
 
      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)

    var counter = 0

    benchmarkOntologies.toSeq.foreach { ont =>
      try { 
	if(ont!=null) { 
	  val owlOntology = try{ 
	    OWLApiInterface.getOWLOntology(ont)
	  } catch { 
	    case _:IllegalArgumentException => OWLApiInterface.getOWLOntology(new File(ont))
	  }

	  OWLOntologyFilters.restrictToALCH(owlOntology)

	  val ontology = OWLApiConverter.convert(owlOntology)

	  val signature = TreeSet[String]()++(ontology.roleSymbols -- DONT_FORGET)

	  numsOfWordsToForget.foreach { numOfWordsToForget => 
	    if(numOfWordsToForget<signature.size)
	      for(i <- 1 to runsPerOntology) { 

		val output = ont+"_"+numOfWordsToForget+"_"+i+".owl"
		  
		if(new File(output).exists())
		  println(output+" exists. Skipping.")
		else{ 
		  val wordsToForget = ran.shuffle(signature.toSeq).slice(0, numOfWordsToForget).toSet
		  if(counter>=continue){ 
		    println("\n\n\n\nExperiment number: "+counter.toString)
		    println("Ontology: "+ont)
		    println("Axioms: "+ontology.statements.size)
		    println("Max axiom size: "+ontology.statements.map(_.size).max)
		    println("Words to forget: "+numOfWordsToForget)
		    println("Run number: "+i)
		    
		    if(!simulate){ 
		      val wordsToForget = ran.shuffle(signature.toSeq).slice(0, numOfWordsToForget).toSet
		      val ontology = OWLApiConverter.convert(owlOntology)
		      if(counter<continue){
			println("skipped")
		      }else { 		      
			println("Size: "+ontology.size)
			println("Axioms: "+ontology.tbox.axioms.size)
			println()
			println("Forgetting the following words: "+wordsToForget)
			println()
			println("Started at "+(new Date()).toString)
			println()
			
			val futureResult = future[Ontology]{ 
			  RoleForgetterS.forget(ontology, wordsToForget)
			}
			
			try { 
			  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): "+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): "+result.statements.map(_.size).max)
			  println("Cyclic definers left: "+countDefiners(result))
			} catch { 
			  case _:TimeoutException => 
			    println("Timeout exceeded.")
			  exec.lastThread.getOrElse(throw new RuntimeException("Not started")).stop()
			}
			
			println("Finished at "+(new Date()).toString)		      
			new File("dlForgetting.log").delete()
			Runtime.getRuntime().exec("rm *.log")
		      }
		    
// 		      //	      println("Exporting to "+output)
// 		      //	      new OWLExporter().exportOntology(result, new File(output))
		      RoleForgetter.clean()
		    }
		  }
		  counter += 1
		}		   	      
	      }
	   }
	}
      } catch { 
	case e: OWLOntologyCreationIOException => println("Exception while loading ontology: "+e.toString)
	case e: MatchError => e.printStackTrace(); System.exit(1)
	case e: java.util.NoSuchElementException => e.printStackTrace(); System.exit(1)
	case e: Exception => 
	  println("Exception thrown: "+e.toString)
	  e.printStackTrace()
      }
    }
  }

  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 = """Forgetting the following words: Set\(([^\)]+)\)""".r
    val reNumber = """Words to forget: (\d+)""".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)
      
    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))
	}
	
	OWLOntologyFilters.restrictToALCH(owlOntology)
	
	val ontology = OWLApiConverter.convert(owlOntology)
	
	val wordsToForget = experiment.signature

	println("Size: "+ontology.size)
	println("Axioms: "+ontology.statements.size)
	println("Max axiom size: "+ontology.statements.map(_.size).max)
	println("Words to forget: "+experiment.numberToForget)
	println("Forgetting the following words: "+wordsToForget)
		      
	try { 
	  val futureResult = future[Ontology]{ 
	    RoleForgetterS.forget(ontology, wordsToForget)
	  }
	  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): "+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): "+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)

	RoleForgetter.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 countDefiners(ontology: Ontology) = { 
    ontology.atomicConcepts.filter(_.startsWith("_D")).size
  }

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

    if(args.size==0){ 
      println("Usage: Experimenter CONFIGURATION [SEED] [CONTINUE] [simulate]")
      println("       Experimenter redo [NEW_TIMEOUT]")
      System.exit(0)
    }
  
    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
    
    if(args.contains("simulate"))
      simulate = true
    
    if(args.size>1 && args(1)!="simulate")
      seed = args(1).toLong

    if(args.size>2 && args(2)!="simulate")
      continue = args(2).toInt

    run(simulate, seed, continue)
  }
}
