/*
   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

/** Rsync is not available on windows and does not provide a generic transformation and skip api*/
object Sync{
  /**default with ansi progress display*/
  def apply(transform:File => Action):Sync = Sync(transform, _.print)

  /**default direct mapping with ansi progress display*/
  def apply(srcDir:File, dstDir:File, dryRun:Boolean=false):Summary =
    Sync(f => Copy(f), _.print).apply(srcDir, dstDir, dryRun)

  //--support types
  abstract sealed trait Action{
    def dst:File
    def reason:Option[Symbol]
    def sync(src:File, dryRun:Boolean):Unit  //TODO return the success or failure to be reported...
  }
  //Action classes
  case class Skip(dst:File, reason:Option[Symbol]=None) extends Action {
    def sync(src:File, dryRun:Boolean) = {} //sync operations for Skip are no-ops
  }
  case class Copy(dst:File, reason:Option[Symbol]=None) extends Action {
    def sync(src:File, dryRun:Boolean) = if(!dryRun){
      val res = src.copyTo(dst) //use smart copy and create parent directories if they don't exists
      if(res.isFailure) println(Red(s"could not copy $src to $dst; $res"))
    }
  }
  //Alternative constructors
  object Skip{def apply(dst:File, reason:Symbol):Skip = Skip(dst, Some(reason))}
  object Copy{def apply(dst:File, reason:Symbol):Copy = Copy(dst, Some(reason))}

  case class ReasonCount(reasons:Map[Option[Symbol], Int]=Map.empty){
    def +(reason:Option[Symbol]) = {
      val count:Int = reasons.getOrElse(reason, 0)+1
      ReasonCount( reasons + (reason -> count) )
    }
    lazy val total:Int = reasons.values.sum
    override def toString = reasons.map{case (k,v) =>
      (k getOrElse Symbol("Unspecified")).name.replace(" ","_") + ":" + v
    }.mkString(" ")
  }
  case class Summary(copyCount:ReasonCount, skipCount:ReasonCount, action:Action){
    import Color._
    def ansi:String = Ansi.kson("Sync", "copy" -> s"{$copyCount}",  "skip" -> s"{$skipCount}")
    override def toString = Ansi.strip(ansi)
    /**nice ansi progress print*/
    def print:Unit = print(false)
    def printVerbose:Unit = print(true)
    def print(verbose:Boolean):Unit = {
      val reasonString = action.reason.map{s=>s" (${s.name})"}.getOrElse("")
      action match {
        case _:Skip =>
          if(verbose)            println(s"Sync skip "+ action.dst + reasonString)
          else                   Ansi.printtmp(ansi) //don't print all the cases
        case Copy(dst,reason) => println(s"Sync copy to "+ action.dst + reasonString)
      }
    }
    def +(action:Action):Summary = action match {
      case _:Copy =>  Summary(copyCount+action.reason,  skipCount,               action)
      case _:Skip =>  Summary(copyCount,                skipCount+action.reason, action)
    }
    def total = copyCount.total + skipCount.total
  }
}
import Sync._

/**Simple file sync with generic transformation definition*/
case class Sync(transform:File => Action, progress:Summary => Unit){
  /**use the default verbose file printing progress*/
  def verbose:Sync = Sync(transform, _.printVerbose)
  def apply(srcDir:File, dstDir:File, dryRun:Boolean=false):Summary = {
    //TODO make this also work if the src is a File instead of a directory
    //immediately find the canonical version of the src and dst, (for windows machines this is especially important to expand things like ~/ for the home directory)
    val srcDirCanon = srcDir.canon  //now the user passed in variables can be masked
    val dstDirCanon = dstDir.canon

    var summary = Summary(ReasonCount(),ReasonCount(),Copy(dstDir)) //initial sync summary zero and remote root dir

    // Macro.pp(srcDir, srcDirCanon, srcDirCanon.isFile, srcDirCanon.isDir)

    Timer.withHeader(Ansi.kson("Sync"+(if(dryRun)"(dry)" else ""), "src" -> srcDir, "dst" -> dstDir)){
      for(src <- srcDirCanon.walk if !src.isDir){ //TODO add walkSkipIf to skip entire dirs
         val srcRelative  = src relativeTo srcDirCanon
         //-- determine the action type
         val action = transform(srcRelative) match {
           case Copy(dstRelative,reason) => {
             val dst = dstDirCanon / dstRelative
             val reasonString = reason.map{"."+_.name}.getOrElse("") //add .subcategory reason
             def copy(reason:Symbol):Copy = Copy(dst, Symbol(reason.name + reasonString))
             // -- needs update tests on the absolute file locations
             // Essentially skip copy unless files are possibly different then force overwrite
             // Note: doing a diff(or sha1) is on the order of time to do a copy so just do a copy
             if (!dst.isFile) copy(Symbol("Missing"))
             else if(src.size != dst.size) copy(Symbol("DifferentSize"))
             else if(src.modified > dst.modified) copy(Symbol("Older"))
             else Skip(dst, Symbol("Exists" + reasonString))
           }
           case x => x //this skip reason from the relative transform should just pass through
         }
         progress(summary) //call the updated summary with the progress callback (report before action)
         action.sync(src,dryRun) // apply the sync action (skips are essentially null ops)
         summary += action // update the summary with a new count of the action types
      }
      println(summary.ansi)
      summary
    }
  }
}