# -*- coding: utf-8 -*-
#
# Copyright 2013 whiteleaf. All rights reserved.
#

require "yaml"
require "tempfile"
require "open3"
require_relative "../downloader"
require_relative "../database"
require_relative "../template"
require_relative "../globalsetting"
require_relative "../helper"

module Command
  class Diff < CommandBase
    def oneline_help
      "アップデートされた小説の変更点を表示します"
    end

    def initialize
      super("[<target>] [options ...]")
      @opt.separator <<-EOS

  ・指定した小説のアップデート前後の変更部分を setting コマンドで指定した difftool で表示します。
  ・引数を指定しなかった場合は直前に更新した小説の変更点を表示します。

  Example:
    narou diff          # 直前に更新した小説の変更点を表示
    narou diff 6
    narou diff 6 -n 2   # 最新から2番目の差分との比較
    narou diff 6 -2     # -n 2 の省略した記述方法
    narou diff 6 2013.02.21@01;39;46   # 差分を直接指定

    # 差分表示用プログラムの指定
    narou setting difftool="C:\\Program Files\\WinMerge\\WinMergeU.exe"

    # difftoolに渡す引数(指定しなければ単純に新旧ファイルを引数に呼び出す)
    # 特殊な変数 %NEW : DLした最新データの差分用ファイルパス
    #            %OLD : 古い方の差分用ファイルパス
    narou setting difftool.arg='-e -x -ub -dl "NEW" -dr "OLD" %NEW %OLD'

  Options:
      EOS

      @options["number"] = 1
      @opt.on("-n NUM", "--number", "比較する差分を遡って指定する。最新のアップデートと直前のデータを比較するなら-n 1、2個前のアップデートなら-n 2。(デフォルトは-n 1)", Integer) do |number|
        @options["number"] = number if number > 1
      end
      @opt.on("-l", "--list", "指定した小説の差分一覧を表示する") {
        @options["list"] = true
      }
      @opt.on("-c", "--clean", "指定した小説の差分データを全て削除する") {
        @options["clean"] = true
      }
    end

    # 引数の中の -数字 オプション(-n 数字の省略形)を -n 数字 に変換する
    def short_number_option_parse(argv)
      argv.map! { |arg|
        if arg =~ /^-(\d+)$/
          ["-n", $1.to_s]
        else
          arg
        end
      }.flatten!
    end

    def execute(argv)
      short_number_option_parse(argv)
      super
      if argv.empty?
        latest = Database.instance.sort_by_last_update.first
        return unless latest
        id = latest["id"]
      else
        target = argv.shift
        id = Downloader.get_id_by_target(target)
      end
      unless id
        error "#{target} は存在しません"
        return
      end
      view_diff_version = argv.shift
      if view_diff_version
        if invalid_diff_version_string?(view_diff_version)
          error "差分指定の書式が違います(正しい例:2013.02.21@01;39;46)"
          return
        end
        @options["view_diff_version"] = view_diff_version
      end
      if @options["list"]
        display_diff_list(id)
        return
      end
      if @options["clean"]
        clean_diff(id)
        return
      end
      @difftool = GlobalSetting.get["global_setting"]["difftool"]
      unless @difftool
        error "difftool が設定されていません。narou setting で difftool を設定して下さい"
        return
      end
      exec_difftool(id)
    end

    def invalid_diff_version_string?(str)
      str !~ /^\d{4}\.\d{2}\.\d{2}@\d{2};\d{2};\d{2}$/
    end

    def clean_diff(id)
      cache_root_dir = Downloader.get_cache_root_dir(id)
      print Database.instance.get_data("id", id)["title"] + " の"
      unless File.exists?(cache_root_dir)
        puts "差分はひとつもありません"
        return
      end
      FileUtils.remove_entry_secure(cache_root_dir)
      puts "差分を削除しました"
    end

    def exec_difftool(id)
      temp_paths = create_temp_files(id) or return
      diff_cmd = create_difftool_command_string(*temp_paths.map { |temp|
        path = temp.path
        if Helper.os_windows?
          path.encode!(Encoding::Windows_31J)
        end
        %!"#{path}"!
      })
      begin
        res = Open3.capture3(diff_cmd)
      rescue Errno::ENOENT => e
        error e.message
        exit 1
      ensure
        temp_paths.each { |tmp| tmp.delete }
      end
      puts res[0] unless res[0].empty?
      error res[1] unless res[1].empty?
    end

    def create_difftool_command_string(left, right)
      diff_arg = GlobalSetting.get["global_setting"]["difftool.arg"]
      diff_cmd = %!"#{@difftool}" !
      if diff_arg
        diff_arg.gsub!("%NEW", left)
        diff_arg.gsub!("%OLD", right)
        diff_cmd += diff_arg
      else
        diff_cmd += [left, right].join(" ")
      end
      diff_cmd
    end

    def get_sorted_cache_list(id)
      list = Downloader.get_cache_list(id) or return nil
      list.sort_by { |v| File.basename(v) }.reverse
    end

    def create_temp_files(id)
      if @options["view_diff_version"]
        cache_root_dir = Downloader.get_cache_root_dir(id)
        if cache_root_dir
          cache_dir = File.join(cache_root_dir, @options["view_diff_version"])
          cache_dir = nil unless File.exists?(cache_dir)
        end
      else
        nth = @options["number"]
        list = get_sorted_cache_list(id)
        cache_dir = list[nth - 1] if list
      end
      unless cache_dir
        puts "差分データがありません"
        return nil
      end
      cache_section_list = Dir.glob("#{cache_dir}/*.yaml").sort_by { |path|
        File.basename(path, ".yaml").split(" ", 2)[0].to_i
      }
      if cache_section_list.length == 0
        puts "最新話のみのアップデートのようです"
        return nil
      end
      novel_dir = Downloader.get_novel_section_save_dir(Downloader.get_novel_data_dir_by_target(id))
      latest_novel_sections = []
      cache_sections = []
      cache_section_list.each do |path|
        match_latest_path = File.join(novel_dir, File.basename(path))
        latest_novel_sections << YAML.load_file(match_latest_path) if File.exists?(match_latest_path)
        cache_sections << YAML.load_file(path)
      end

      novel_info = Database.instance[id]

      sections = latest_novel_sections
      temp_new = Tempfile.open(["new", ".txt"])
      temp_new.write(Template.get("diff.txt", binding))
      temp_new.close

      sections = cache_sections
      temp_old = Tempfile.open(["old", ".txt"])
      temp_old.write(Template.get("diff.txt", binding))
      temp_old.close

      [temp_new, temp_old]
    end

    def display_diff_list(id)
      cache_list = get_sorted_cache_list(id)
      print Database.instance.get_data("id", id)["title"] + " の"
      if cache_list.empty?
        puts "差分はひとつもありません"
        return
      end
      puts "差分一覧"
      cache_list.each.with_index(1) do |cache_path, i|
        puts ("<bold><yellow>" + File.basename(cache_path) + "   -#{i}</yellow></bold>").termcolor
        cache_section_list = Dir.glob(File.join(cache_path, "*.yaml"))
        if cache_section_list.length > 0
          cache_section_list.map { |section_path|
            File.basename(section_path, ".yaml").split(" ", 2)
          }.sort_by { |v| v[0].to_i }.each { |index, subtitle|
            puts "   第#{index}部分 #{subtitle}"
          }
        else
          puts "   (最新話のみのアップデート)"
        end
      end
    end
  end
end