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

import scala.xml._
import java.nio.file.{Files, Paths, StandardOpenOption}
import java.io.{File, 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 = ""

      val files = new File(args(0))
        .listFiles
        .filter {
          !_.isDirectory
        }
        .sortBy {
          _.lastModified
        }

      val start = LocalDateTime.now().toString

      val successSourcesName = s"${args(0)}/${start}_success"
      val successSources = new File(successSourcesName)
      val errorSourcesName = s"${args(0)}/${start}_error"
      val errorSources = new File(errorSourcesName)

      if (successSources.mkdir() && errorSources.mkdir()) {

        for (f <- files) {
          val fSourceName = f.getName
          val fSourceNameArray = fSourceName.split('.')
          val fSourceNameExtension = fSourceNameArray.last.toLowerCase
          val now = LocalDateTime.now().toString

          if (fSourceNameExtension == "zip") Try {
            val fZip = new ZipFile(f)
            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"${fSourceName}_$fZipEntryName",
                    s"Converted_${fSourceName}_$fZipEntryName",
                    nsdXmlValid,
                    rtsXmlValid,
                    p)
                } match {
                  case Success((oXml, ti)) =>
                    XML.save(Paths.get(args(1), s"Converted_${fSourceName}_$fZipEntryName").toString, oXml, xmlDecl = true)
                    logString += s"$nowForZip\n$ti"
                    Files.copy(fZip.getInputStream(e), Paths.get(successSourcesName, s"${fSourceName}_$fZipEntryName"))
                  case Failure(ex) =>
                    logString += s"'$nowForZip\nImpossible to convert '${fSourceName}_$fZipEntryName': ${ex.getMessage}\n\n"
                    Files.copy(fZip.getInputStream(e), Paths.get(errorSourcesName, s"${fSourceName}_$fZipEntryName"))
                }
              }
            }
          } match {
            case Success(_) =>
            case Failure(ex) => logString += s"'$now\nImpossible to convert '$fSourceName': ${ex.getMessage}\n\n"
          }
          else Try {
            convert(
              new FileInputStream(f),
              s"$fSourceName",
              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"
              Files.copy(f.toPath, Paths.get(successSourcesName, f.getName))
            case Failure(ex) =>
              logString += s"'$now\nImpossible to convert '$fSourceName': ${ex.getMessage}\n\n"
              Files.copy(f.toPath, Paths.get(errorSourcesName, f.getName))
          }
        }

        Try {
          //files.foreach { _.delete }

          Files.write(
            Paths.get(args(0), logFileName),
            logString.getBytes("utf-8"),
            StandardOpenOption.CREATE,
            StandardOpenOption.TRUNCATE_EXISTING)

          if (successSources.listFiles.nonEmpty)
            Files.copy(Paths.get(args(0), logFileName), Paths.get(successSourcesName, logFileName))
          else
            successSources.delete

          if (errorSources.listFiles.isEmpty) errorSources.delete
        } match {
          case Success(_) => println(s"Converting has been finished. $logFileName located in ${args(0)}")
          case Failure(ex) => println(s"Error. Converting hasn`t been finished: ${ex.getMessage}")
        }
      } else
        println(s"Error. Converting hasn`t been finished: unable to create source moving directory.")
    }
  }

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