/*
   Copyright 2010 Aaron J. Radke

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/
package cc.drx

import scala.annotation.implicitNotFound

trait FileKind{
  def exts:List[String] //the precedence has meaning as the most common type and preferred conversion format

  def defaultExt:String = exts.headOption.getOrElse("undefined")

  override def toString = defaultExt

  private lazy val extsNormalized = exts.map{_.toLowerCase}.toSet
  final def is(f:File):Boolean = unapply(f)
  final def unapply(f:File):Boolean = extsNormalized contains f.ext.toLowerCase
}
object FileKind{
  object Markdown extends FileKind {def exts = List("markdown","md","kson")}
  object Word     extends FileKind {def exts = List("docx","doc")}  //these do not need to be case objects since unapply is defiend
  object Img      extends FileKind {def exts = List("emf","png")} //TODO add more and add the imagaicmagic converter

  def convert[A <: FileKind, B <: FileKind](src:File, tgt:File)
    (implicit fc:FileConverter[A,B], ec:EC):Future[File] = fc.convert(src,tgt)(ec)
}
@implicitNotFound("No FileConverter found for FileKind ${A} to ${B}") //string interpolation for these guys can not be prepended with s for StringContext
trait FileConverter[A <: FileKind,B <: FileKind]{   //<: is the subtype type parameter bounds
  def convert(src:File, tgt:File)(implicit ec:EC):Future[File]
  def defaultTargetExt:String
  def convert(src:File)(implicit ec:EC):Future[File] = convert(src, src.companion(defaultTargetExt))
}
/* Collection of default FileConverter type classes
Wow so much common boiler plate is required for the powerful use of type classes ... the concept of this file and all the types and plurality of types is just to remove relying on runtime pattern matching of the FileKind and let external users provide FileKinds and FileConverters

(src,tgt) match {
case (Word(),     Markdown()) => run(Shell("pandoc -f docx -t markdown -o", tgt.path, src.path))
case (Markdown(), Word())     => run(Shell("pandoc -f markdown -t docx -o", tgt.path, src.path)) //TODO add toc elements
case _ => Future.failed(new java.lang.Throwable(s"no converter for file type ${src.ext} to ${tgt.ext}"))
}
*/
object FileConverter{
  import FileKind._
  private def run(shell:Shell,tgt:File)(implicit ec:EC):Future[File] =  {
    // println(s"shell:$shell, tgt:${tgt.canon})
    shell.exitCode.flatMap{
      case 0 => Future(tgt)
      case x => Future.failed(new java.lang.Throwable(s"failed to convert with ${shell} $x"))
    }
  }
  //TODO add css file copied over from the resource directory
  private val pandocMarkdown = "markdown+simple_tables+yaml_metadata_block+raw_html"
  private def pandoc(from:String, to:String, src:File, tgt:File)(implicit ec:EC):Future[File] = {
    val cssFile = src.companion("css")
    val cssFlags = if(cssFile.isFile) s" --css=${cssFile.name}" else ""
    val shell = Shell(s"pandoc $cssFlags --from $from --to $to --standalone --atx-headers --section-divs --output")
    println(shell) //TODO 
    run(shell.apply(tgt.path, src.path), tgt)
  }

  //FIXME focuse on using this single method for the converters rather than the typeclasss since file ext can not be known ahead of time anyway
  def convert(src:File, tgt:File)(implicit ec:EC):Future[File] = {
    (src.ext, tgt.ext) match {
      case ("md",       fmt) => pandoc(pandocMarkdown, fmt, src, tgt)
      case ("markdown", fmt) => pandoc(pandocMarkdown, fmt, src, tgt)
      case ("docx",     "markdown") => pandoc(pandocMarkdown, "markdown", src, tgt)
      case  _ =>  Future.failed(new java.lang.IllegalArgumentException(s"no converter for ${src.ext} to ${tgt.ext}"))
    }
  }

  implicit object MarkdownToWord extends FileConverter[Markdown.type,Word.type]{
    def convert(src:File, tgt:File)(implicit ec:EC):Future[File] = pandoc(pandocMarkdown, "docx", src, tgt)
    def defaultTargetExt = Word.defaultExt
  }
  implicit object WordToMarkdown extends FileConverter[Word.type, Markdown.type]{
    def convert(src:File, tgt:File)(implicit ec:EC):Future[File] = pandoc("docx", pandocMarkdown, src, tgt)
    def defaultTargetExt = Markdown.defaultExt
  }
}
trait FileConverters{
  def convert(src:File,tgt:File)(implicit ec:EC):Future[File]
  def kinds:Set[FileKind]
}
object FileConverters{
  //auto detect kinds at runtime
  implicit object DefaultFileConverters extends FileConverters{
    import FileKind._
    import FileConverter._
    val kinds = Set(Markdown,Word)
    def convert(src:File,tgt:File)(implicit ec:EC):Future[File] = (src, tgt) match {
        case (Markdown(), Word()) => MarkdownToWord.convert(src,tgt)
        case (Word(), Markdown()) => WordToMarkdown.convert(src,tgt)
        case _ => Future.failed(new java.lang.Throwable(s"no converter for file type ${src.ext} to ${tgt.ext}"))
    }
  }
}