package code.util

import java.util.regex.{Matcher, Pattern}
import java.io.{File, IOException}
import com.google.common.io.{Files, ByteStreams}
import code.util.Util.using
import collection.mutable.ListBuffer

/**
 * rdiff-backupのコマンドを実行するためのインターフェースです。
 */
object RDiffBackup {

  /**
   * rdiff-backupのコマンド
   * @todo 設定で変更できるようにする？
   */
  private val COMMAND: String = "rdiff-backup"

  /**
   * rdiff-backup -l の実行結果から履歴のタイムスタンプを抽出するための正規表現
   */
  private val PATTERN: Pattern = Pattern.compile("\\.([0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\\+09:00)\\.")

  /**
   * 引数で指定したファイルもしくはディレクトリのバックアップ日付のリストを返却します。
   *
   * @param dir ファイルまたはディレクトリ
   * @return バックアップ日付のリスト
   */
  def list(dir: String): List[String] = {
    var result = new ListBuffer[String]
    val builder: ProcessBuilder = new ProcessBuilder(COMMAND, "-l", dir)
    val process: Process = builder.start

    using(process.getInputStream){ in =>
      val text: String = new String(ByteStreams.toByteArray(in), "UTF-8")
      val matcher: Matcher = PATTERN.matcher(text)
      while (matcher.find) {
        result += matcher.group(1)
      }
    }
    return result.toList
  }

  /**
   * 引数で指定したファイルもしくはディレクトリのうち、指定日時以降に変更されているもののリストを返却します。
   *
   * @param path ファイルまたはディレクトリ
   * @param timestamp タイムスタンプ
   * @return 指定日時以降に変更されたファイルまたはディレクトリのリスト
   */
  def changes(path: String, timestamp: String): List[ChangeDto] = {
    val result = new ListBuffer[ChangeDto]
    val builder: ProcessBuilder = new ProcessBuilder(COMMAND, "--list-changed-since", timestamp, path)
    val process: Process = builder.start

    using(process.getInputStream){ in =>
      val text: String = new String(ByteStreams.toByteArray(in), "UTF-8")
      text.split("\n").foreach(line => {
        val index: Int = line.indexOf(' ')
        if (index >= 0) {
          result += ChangeDto(line.substring(0, index), line.substring(index + 1).trim)
        }
      })
    }

    return result.toList
  }

  /**
   * 引数で指定した日時のファイルもしくはディレクトリを一時ディレクトリにリストアします。
   * 一時ディレクトリはアクションでの処理の終了後に{@link #removeTemporaryDirectory()}で削除してください。
   *
   * @param path ファイルまたはディレクトリ
   * @param timestamp リストアする履歴のタイムスタンプ
   * @return 一時ディレクトリ
   */
  def restore[R] (path: String, timestamp: String)(func: File => R): R = {
    // 一時ディレクトリを作成
    val tmpDir: File = new File(System.getProperty("java.io.tmpdir"), RequestUtil.getRequest.getSession.getId)
    tmpDir.mkdir
    // リストア
    val builder: ProcessBuilder = new ProcessBuilder(COMMAND, "-r", timestamp, path, new File(tmpDir, getFileName(path)).getAbsolutePath)
    val process: Process = builder.start
    using(process.getInputStream){ in =>
      ByteStreams.toByteArray(in)
    }

    try {
      // コールバック関数の呼び出し
      func(tmpDir)

    } finally {
      // 一時ディレクトリを削除
      if (tmpDir.exists && tmpDir.isDirectory) {
        try {
          Files.deleteRecursively(tmpDir)
        } catch {
          case ex: IOException => {}
        }
      }
    }
  }

  /**
   * パスから末尾のファイル名もしくはディレクトリ名部分を取得します。
   *
   * @param path ファイルまたはディレクトリのパス
   * @return ファイル名またはディレクトリ名
   */
  private def getFileName(path: String): String = {
    val fixedPath = if (path.endsWith("/")) {
      path.substring(0, path.length - 1)
    } else {
      path
    }
    val index: Int = fixedPath.lastIndexOf('/')
    if (index >= 0) {
      return fixedPath.substring(index + 1)
    }
    // TODO nullを返すべきではない？
    return null
  }

}