import com.typesafe.config.{Config, ConfigFactory}

import scala.xml._
import java.nio.file.{Files, Path, Paths, StandardOpenOption}
import java.time.LocalDateTime

import scala.util.{Failure, Success, Try}
import xmlvalid._
import templates._

object Main {
  val conf: Config = ConfigFactory.load
  val debugMode: Boolean = conf.getBoolean("debug")
  val logFileName: String = conf.getString("log")

  implicit def templateFactory(source: Node): Template = {
    Try { (source \\ "trade").head.child
    .filter {
      case v: Elem => true
      case _ => false
    }(1).label } match {
      case Success(v) if v == "repo" =>
        if (debugMode) new RepoTemplate(source) with Logger else new RepoTemplate(source)
      case Success(v) if v == "bondBasketOption" =>
        if (debugMode) new BondBasketOptionTemplate(source) with Logger else new BondBasketOptionTemplate(source)
      case Success(v) if v == "bondOption" =>
        if (debugMode) new BondOptionTemplate(source) with Logger else new BondOptionTemplate(source)
      case Success(v) if v == "equityOption" =>
        if (debugMode) new EquityOptionTemplate(source) with Logger else new EquityOptionTemplate(source)
      case Success(v) if v == "commodityOption" =>
        if (debugMode) new CommodityOptionTemplate(source) with Logger else new CommodityOptionTemplate(source)
      case Success(v) if v == "bondForward" =>
        if (debugMode) new BondForwardTemplate(source) with Logger else new BondForwardTemplate(source)
      case Success(v) if v == "equityForward" =>
        if (debugMode) new EquityForwardTemplate(source) with Logger else new EquityForwardTemplate(source)
      case Success(v) if v == "commodityForward" =>
        if (debugMode) new CommodityForwardTemplate(source) with Logger else new CommodityForwardTemplate(source)
      case Success(v) if v == "commoditySwap" =>
        if (debugMode) new CommoditySwapTemplate(source) with Logger else new CommoditySwapTemplate(source)
      case Success(v) if v == "masterAgreementTerms" =>
        if (debugMode) new MasterAgreementTemplate(source) with Logger else new MasterAgreementTemplate(source)
      case Failure(_) =>
        Try { (source \ "markToMarketValuation").head } match {
          case Success(_) =>
            if (debugMode) new FairValueReportTemplate(source) with Logger else new FairValueReportTemplate(source)
          case Failure(_) => throw new Exception("Undefined trade type")
        }
    }
  }

  def main(args: Array[String]): Unit = {
    if (args.length != 2) println("Error: Undefined INPUT/OUTPUT directory. Usage: app INPUT OUTPUT")
    else if (!Files.exists(Paths.get(args(0)))) println("Error: INPUT directory doesn`t exist")
    else if (!Files.exists(Paths.get(args(1)))) println("Error: OUTPUT directory doesn`t exist")
    else {
      val p = new PrettyPrinter(160, 2)
      val nsdXmlValid = XMLValid(getClass.getResource("/nsd/nsd-ext-merged-schema.xsd"))
      val rtsXmlValid = XMLValid(getClass.getResource("/rts/fpml-recordkeeping-merged-schema.xsd"))
      var logString = ""

      for (v <- Files.walk(Paths.get(args(0))).toArray; f = v.asInstanceOf[Path]; if !Files.isDirectory(f)) {
        val fSourceName= f.getFileName.toString
        val fOutputName = s"Converted_$fSourceName"
        val now = LocalDateTime.now().toString

        Try {
          val sourceXml = XML.loadFile(f.toString)

          val template = Template(sourceXml)
          val outputXml = XML.loadString(p.format(template.render))

          val sourceXmlValid = if (template.isStraight) nsdXmlValid else rtsXmlValid
          val outputXmlValid = if (template.isStraight) rtsXmlValid else nsdXmlValid

          val msgCheckSource = Try {
            sourceXmlValid.loadString(sourceXml.mkString)
          } match {
            case Success(_) => s"'$fSourceName' successfully checked against schema"
            case Failure(ex) => s"'$fSourceName' isn`t wellformed or invalid: ${ex.getMessage}"
          }

          val msgCheckOutput = Try {
            outputXmlValid.loadString(outputXml.mkString)
          } match {
            case Success(_) => s"'$fOutputName' successfully checked against schema"
            case Failure(ex) => s"'$fOutputName' isn`t wellformed or invalid: ${ex.getMessage}"
          }

          XML.save(Paths.get(args(1), fOutputName).toString, outputXml, xmlDecl = true)

          template.info(s"$now\n$msgCheckSource\n$msgCheckOutput") + "\n\n"
        } match {
          case Success(ti) => logString += ti
          case Failure(ex) => logString += s"'$now\nImpossible to convert'$fSourceName': ${ex.getMessage}\n\n"
        }
      }

      Try {
        Files.write(
          Paths.get(args(1), logFileName),
          logString.getBytes("utf-8"),
          StandardOpenOption.CREATE,
          StandardOpenOption.TRUNCATE_EXISTING)
      } match {
        case Success(_) => println(s"Work has finished. $logFileName located in ${args(1)}")
        case Failure(ex) => println(s"Error. Work hasn`t finished: ${ex.getMessage}")
      }
    }
  }
}
