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

import scala.xml._
import java.nio.file.{Files, Path, Paths, StandardOpenOption}
import java.io.{FileInputStream, InputStream}
import java.util.zip.ZipFile
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")

  def main(args: Array[String]): Unit = {
    println("Converting NSD -> SPB has been started...")

    if (args.length != 2)
      println("Error. Converting hasn`t been finished: Undefined INPUT/OUTPUT directory.\nUsage: run INPUT OUTPUT")
    else if (!Files.exists(Paths.get(args(0))))
      println("Error. Converting hasn`t been finished: INPUT directory doesn`t exist")
    else if (!Files.exists(Paths.get(args(1))))
      println("Error. Converting hasn`t been finished: 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 fSourceNameArray = fSourceName.split('.')
        val fSourceNameBase = fSourceNameArray.head
        val fSourceNameExtension = fSourceNameArray.last.toLowerCase
        val now = LocalDateTime.now().toString

        if (fSourceNameExtension == "zip")  Try {
          val fZip = new ZipFile(f.toFile)
          val fZipEntries = fZip.entries

          while (fZipEntries.hasMoreElements) {
            val e = fZipEntries.nextElement
            if (!e.isDirectory) {
              val fZipEntryName = e.getName
              val nowForZip = LocalDateTime.now().toString

              Try {
                convert(
                  fZip.getInputStream(e),
                  s"${fSourceNameBase}_$fZipEntryName",
                  s"Converted_${fSourceNameBase}_$fZipEntryName",
                  nsdXmlValid,
                  rtsXmlValid,
                  p)
              } match {
                case Success((oXml, ti)) =>
                  XML.save(Paths.get(args(1), s"Converted_${fSourceNameBase}_$fZipEntryName").toString, oXml, xmlDecl = true)
                  logString += s"$nowForZip\n$ti"
                case Failure(ex) => logString += s"'$nowForZip\nImpossible to convert '${fSourceNameBase}_$fZipEntryName': ${ex.getMessage}\n\n"
              }
            }
          }
        } match {
          case Success(_) =>
          case Failure(ex) => logString += s"'$now\nImpossible to convert '$fSourceName': ${ex.getMessage}\n\n"
        }
        else Try {
          convert(
            new FileInputStream(f.toFile),
            s"$fSourceNameBase",
            s"Converted_$fSourceName",
            nsdXmlValid,
            rtsXmlValid,
            p)
        } match {
            case Success((oXml,ti)) =>
              XML.save(Paths.get(args(1), s"Converted_$fSourceName").toString, oXml, xmlDecl = true)
              logString += s"$now\n$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"Converting has been finished. $logFileName located in ${args(1)}")
        case Failure(ex) => println(s"Error. Converting hasn`t been finished: ${ex.getMessage}")
      }
    }
  }

  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 _ =>
        Try { (source \\ "markToMarketValuation").head } match {
          case Success(_) =>
            if (debugMode) new FairValueReportTemplate(source) with Logger else new FairValueReportTemplate(source)
          case _ => Try { (source \\ "masterAgreementTerms").head } match {
            case Success(_) =>
              if (debugMode) new MasterAgreementTemplate(source) with Logger else new MasterAgreementTemplate(source)
            case _ => Try { (source \\ "masterAgreementTermination").head } match {
              case Success(_) =>
                if (debugMode) new MasterAgreementTemplate(source) with Logger else new MasterAgreementTemplate(source)
              case _ => Try { (source \\ "executionStatus").head } match {
                case Success(_) =>
                  if (debugMode) new ExecutionStatusReportTemplate(source) with Logger
                  else new ExecutionStatusReportTemplate(source)
                case _ => throw new Exception("Undefined trade type")
              }
            }
          }
        }
    }
  }

  def convert(
      inputStream: InputStream,
      sName: String,
      oName: String,
      nsdScheme: XMLValid,
      rtsScheme: XMLValid,
      pp: PrettyPrinter): (Node, String) = {
    val sourceXml = XML.load(inputStream)

    val template = Template(sourceXml)
    /* temporary */
    val outputXml = XML.loadString(pp.format(template.render)
      .replace("RP0058900001", "BROKN")
      .replace("NDC000000000", "SPB01")
      .replace("НКО АО НРД", "ПАО «Санкт-Петербургская биржа»"))

    val sourceXmlValid = if (template.isStraight) nsdScheme else rtsScheme
    val outputXmlValid = if (template.isStraight) rtsScheme else nsdScheme

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

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

    (outputXml, template.info(s"$msgCheckSource\n$msgCheckOutput") + "\n\n")
  }
}
