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

import uk.ac.man.cs.lethe.internal.dl.filters.{AxiomAnalysis, OWLOntologyFilters}
import uk.ac.man.cs.lethe.internal.dl.owlapi.{OWLApiConfiguration, 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.Random
import scala.util.parsing.json.JSON
import org.semanticweb.owlapi.io.OWLOntologyCreationIOException
import uk.ac.man.cs.lethe.internal.dl.datatypes.{DLHelpers, DisjunctiveConceptAssertion, Ontology, OntologyFilter}
import uk.ac.man.cs.lethe.internal.dl.forgetting.DirectALCForgetter
import uk.ac.man.cs.lethe.internal.dl.forgetting.abox.ABoxForgetter
import uk.ac.man.cs.lethe.internal.dl.owlapi._


object ForgettingExperimenter { 

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

  var aboxForgetting: Boolean = false

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

  /**
   * 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 => ;
	}

        map.get("probabilistic") match { 
	  case Some(_) => 
	    probabilistic = true
	  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)

    var counter = 0

    val forgetter = if(aboxForgetting)
      ABoxForgetter
    else
      DirectALCForgetter

    OWLApiConfiguration.SIMPLIFIED_NAMES=false

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

	  AxiomAnalysis.analyse(owlOntology)

//	  OWLOntologyFilters.restrictToALCH(owlOntology)

	  var ontology = OWLApiConverter.convert(owlOntology)
	  val oldSize = ontology.size
	  ontology = OntologyFilter.restrictToALC(ontology)
	  println("Reduced size from "+oldSize+" to "+ontology.size+" when restricting to ALC")

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

	  numsOfWordsToForget.foreach { numOfWordsToForget => 
	    if(!probabilistic && numOfWordsToForget>=ontology.atomicConcepts.size){ 
	      println("Skipped (numOfWordsToForget>=ontology.atomicConcepts.size)")
	    } else
	      for(i <- 1 to runsPerOntology) { 
		println("\n\n\n\nExperiment number: "+counter.toString)

		val output = ont+"_"+numOfWordsToForget+"_"+i+".owl"
		  
		if(new File(output).exists())
		  println(output+" exists. Skipping.")
		else{ 
		  println("Ontology: "+ont)
		  if(!probabilistic)
		    println("Words to forget: "+numOfWordsToForget)
		  println("Run number: "+i)
		    
		  if(!simulate){ 
		    val wordsToForget = 
		      if(probabilistic){ 
			val r = signature.filter(_ => Random.nextDouble<probability)
			println("Forgetting "+ r.size+" out of "+signature.size+" symbols")
			r
		      }
		      else
			ran.shuffle(signature.toSeq).slice(0, numOfWordsToForget).toSet
		    if(counter<continue){
		      println("skipped")
		    }else { 		      
		      println("Size: "+ontology.size)
		      println("Statements: "+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()
		      println("Forgetting the following words: "+wordsToForget)
		      println()
		      println("Started at "+(new Date()).toString)
		      println()
		      
		      val futureResult = future[Ontology]{ 
			forgetter.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): "+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("Disjunctive Concept Assertions: "+caDisjunctions)
		      } 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")
		    }
		    counter += 1
		      //	      println("Exporting to "+output)
		      //	      new OWLExporter().exportOntology(result, new File(output))
		    DirectALCForgetter.clean()
		  }
		}		   	
	      }				 
	   }
	}
      } 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()
      }
    }
  }

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


  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
    println("Remaining: "+benchmarkOntologies)
  }


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

    var args=_args

    if(args.size==0){ 
      println("Usage: Experimenter CONFIGURATION [SEED] [CONTINUE] [simulate]")
      System.exit(0)
    }
    if(args.contains("continue")){ 
      args = args.filter(_!="continue")
      parseLastOntologies()
    }

    if(args.contains("--aboxForgetting")){ 
      args = args.filter(_!="--aboxForgetting")
      aboxForgetting = true
    }


    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)
  }
}
