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

require "fileutils"
require_relative "ini"
require_relative "downloader"

class NovelSetting
  INI_NAME = "setting.ini"
  INI_ERB_BINARY_VERSION = 1.2
  REPLACE_NAME = "replace.txt"

  attr_accessor :id, :author, :title, :archive_path, :replace_pattern, :settings

  #
  # データベースに登録されている小説の設定を取得する
  #
  def self.load(target, ignore_force, ignore_default)
    setting = create(target, ignore_force, ignore_default)
    data = Downloader.get_data_by_target(target)
    setting.id = data["id"]
    setting.author = data["author"]
    setting.title = data["title"]
    setting
  end

  #
  # 小説設定オブジェクトを作成する
  #
  # target には小説ID等の他、小説保存フォルダを指定できる。
  # テキストファイル変換時はデータベースに登録されていないので load ではなくこちらを使用する
  #
  def self.create(target, ignore_force, ignore_default)
    setting = new(target, ignore_force, ignore_default)
    setting.load_settings
    setting.set_attribute
    setting.load_replace_pattern
    setting
  end

  def initialize(target, ignore_force, ignore_default)
    if File.directory?(target.to_s)
      archive_path = target
    else
      archive_path = Downloader.get_novel_data_dir_by_target(target).to_s
    end
    @archive_path = File.expand_path(archive_path)
    @ignore_force = ignore_force
    @ignore_default = ignore_default
    @replace_pattern = []
    @settings = {}
  end

  def ini_path
    File.join(@archive_path, INI_NAME)
  end

  def load_setting_ini
    Ini.load_file(ini_path) rescue Ini.load("")
  end

  #
  # 小説変換時の設定値読込
  #
  # 設定値の優先順位は
  # 1. narou setting コマンドで設定した force.*
  # 2. setting.ini
  # 3. narou setting コマンドで設定した default.*
  # 4. ORIGINAL_SETTINGS
  #
  def load_settings
    @settings.clear
    ini = load_setting_ini
    force_settings = @ignore_force ? {} : NovelSetting.load_force_settings
    default_settings = @ignore_default ? {} : NovelSetting.load_default_settings
    ORIGINAL_SETTINGS.each do |element|
      name, value, type = element[:name], element[:value], element[:type]
      if force_settings.include?(name)
        @settings[name] = force_settings[name]
      elsif ini["global"].include?(name) && type_eq_value(type, ini["global"][name])
        @settings[name] = ini["global"][name]
      elsif default_settings.include?(name)
        @settings[name] = default_settings[name]
      else
        @settings[name] = value
      end
    end
    # デフォルト設定以外を読み込む
    ini["global"].each do |key, value|
      unless @settings.include?(key)
        @settings[key] = value
      end
    end
  end

  #
  # local_settings から group.name 形式のデータを取得
  #
  # { name: value, ... } 形式のハッシュとして返す
  #
  def self.load_settings_by_pattern(pattern)
    res = Inventory.load("local_setting").map { |name, value|
      if name =~ /^#{pattern}\.(.+)$/
        [$1, value]
      else
        nil
      end
    }.compact.flatten
    Hash[*res]
  end

  #
  # force.* 設定を取得
  #
  def self.load_force_settings
    load_settings_by_pattern("force")
  end

  #
  # default.* 設定を取得
  #
  def self.load_default_settings
    load_settings_by_pattern("default")
  end

  def get_value_by_original(name)
    index = ORIGINAL_SETTINGS_KEY_INDEXES[name]
    index ? ORIGINAL_SETTINGS[index] : nil
  end

  #
  # 設定を保存
  #
  def save_settings
    original_settings = NovelSetting.get_original_settings
    default_settings = NovelSetting.load_default_settings
    novel_setting = self
    Template.write(INI_NAME, @archive_path, binding, INI_ERB_BINARY_VERSION, Template::OVERWRITE)
  end

  def type_eq_value(type, value)
    type_of_value = Helper.type_of_value(value)
    case type
    when :string, :select, :multiple
      :string == type_of_value
    else
      type == type_of_value
    end
  end

  #
  # 指定された設定の型チェック
  #
  def check_value_of_type(name, value)
    original = get_value_by_original(name)
    return unless original
    unless type_eq_value(original[:type], value)
      raise Helper::InvalidVariableType, original[:type]
    end
  end

  #
  # 設定データ用アクセサ定義
  #
  def set_attribute
    @settings.each_key do |key|
      instance_eval <<-EOS
        def #{key}
          @settings["#{key}"]
        end

        def #{key}=(value)
          check_value_of_type("#{key}", value)
          @settings["#{key}"] = value
        end
      EOS
    end
  end

  #
  # 配列風のアクセサ定義
  #
  def [](name)
    @settings[name]
  end

  def []=(name, value)
    unless value.nil?
      check_value_of_type(name, value)
    end
    @settings[name] = value
  end

  #
  # replace.txt による置換定義を読み込む
  #
  def load_replace_pattern
    @replace_pattern.clear
    replace_txt_path = File.join(@archive_path, REPLACE_NAME)
    if File.exist?(replace_txt_path)
      @replace_pattern = Narou.parse_replace_txt(File.read(replace_txt_path, mode: "r:BOM|UTF-8"))
    end
    @replace_pattern
  end

  #
  # replace.txt に設定を書き戻す
  #
  def save_replace_pattern
    replace_txt_path = File.join(@archive_path, REPLACE_NAME)
    Narou.write_replace_txt(replace_txt_path, @replace_pattern)
  end

  def self.get_original_settings
    ORIGINAL_SETTINGS
  end

  ORIGINAL_SETTINGS = [
    # name: 変数名
    # type: 変数の型
    # value: 初期値
    # help: 説明(setting.ini に書き出される)
    {
      name: "enable_inspect",
      type: :boolean,
      value: false,
      help: "小説に対する各種調査を実行する。結果を表示するには narou inspect コマンドを使用"
    },
    {
      name: "enable_convert_num_to_kanji",
      type: :boolean,
      value: true,
      help: "数字の漢数字変換を有効にする"
    },
    {
      name: "enable_kanji_num_with_units",
      type: :boolean,
      value: true,
      help: "漢数字変換した場合、千・万などに変換する"
    },
    {
      name: "kanji_num_with_units_lower_digit_zero",
      type: :integer,
      value: 3,
      help: "〇(ゼロ)が最低この数字以上付いてないと千・万などをつける対象にしない"
    },
    {
      name: "enable_alphabet_force_zenkaku",
      type: :boolean,
      value: false,
      help: "アルファベットを強制的に全角にする。falseの場合英文は半角、それ以外は全角になる"
    },
    {
      name: "enable_half_indent_bracket",
      type: :boolean,
      value: true,
      help: "行頭かぎ括弧に二分アキを挿入する"
    },
    {
      name: "enable_auto_indent",
      type: :boolean,
      value: true,
      help: "自動行頭字下げ機能。行頭字下げが行われているかを判断し、適切に行頭字下げをするか"
    },
    {
      name: "enable_force_indent",
      type: :boolean,
      value: false,
      help: "行頭字下げを必ず行うか。enable_auto_indent の設定は無視される"
    },
    {
      name: "enable_auto_join_in_brackets",
      type: :boolean,
      value: true,
      help: "かぎ括弧内自動連結を有効にする\n例)\n「~~~!\n ***?」  → 「~~~! ***?」"
    },
    {
      name: "enable_auto_join_line",
      type: :boolean,
      value: true,
      help: "行末が読点で終わっている部分を出来るだけ連結する"
    },
    {
      name: "enable_enchant_midashi",
      type: :boolean,
      value: true,
      help: "[#改ページ]直後の行に中見出しを付与する(テキストファイルを直接変換する場合のみの設定)"
    },
    {
      name: "enable_author_comments",
      type: :boolean,
      value: true,
      help: "作者コメントを検出する(テキストファイルを直接変換する場合のみの設定)"
    },
    {
      name: "enable_erase_introduction",
      type: :boolean,
      value: false,
      help: "前書きを削除する"
    },
    {
      name: "enable_erase_postscript",
      type: :boolean,
      value: false,
      help: "後書きを削除する"
    },
    {
      name: "enable_ruby",
      type: :boolean,
      value: true,
      help: "ルビ処理を有効にする"
    },
    {
      name: "enable_illust",
      type: :boolean,
      value: true,
      help: "挿絵タグを有効にする(false なら削除)"
    },
    {
      name: "enable_transform_fraction",
      type: :boolean,
      value: false,
      help: "○/×表記を×分の○表記に変換する。日付表記(10/23)と誤爆しやすいので注意"
    },
    {
      name: "enable_transform_date",
      type: :boolean,
      value: false,
      help: "日付表記(20yy/mm/dd)を任意の形式(date_formatで指定)に変換する"
    },
    {
      name: "date_format",
      type: :string,
      value: "%Y年%m月%d日",
      help: "書式は http://bit.ly/1m5e3w7 を参考"
    },
    {
      name: "enable_convert_horizontal_ellipsis",
      type: :boolean,
      value: true,
      help: "中黒(・)を並べて三点リーダーもどきにしているのを三点リーダーに変換する"
    },
    {
      name: "enable_convert_page_break",
      type: :boolean,
      value: false,
      help: "`to_page_break_threshold` で設定した個数以上連続する空行を改ページに変換する"
    },
    {
      name: "to_page_break_threshold",
      type: :integer,
      value: 10,
      help: "ここで設定した値が `enable_convert_page_break` に反映される"
    },
    {
      name: "enable_display_end_of_book",
      type: :boolean,
      value: true,
      help: "小説の最後に本を読み終わった表示をする"
    },
    {
      name: "enable_add_date_to_title",
      type: :boolean,
      value: false,
      help: "変換後の小説のタイトルに最新話掲載日や更新日等の日付を付加する"
    },
    {
      name: "title_date_format",
      type: :string,
      value: "(%-m/%-d)",
      help: <<-EOS
enable_add_date_to_title で付与する日付のフォーマット。書式は http://bit.ly/1m5e3w7 を参照。
Narou.rb専用の書式として下記のものも使用可能。
$s 2045年までの残り時間(10分単位の4桁の36進数)
$t 小説のタイトル
$ns 小説が掲載されているサイト名
$nt 小説種別(短編 or 連載)
      EOS
    },
    {
      name: "title_date_align",
      type: :select,
      value: "right",
      help: "enable_add_date_to_title が有効な場合に付与される日付の位置。left(タイトルの前) か right(タイトルの後)。title_date_format で $t を使用した場合この設定は無視される",
      select_keys: %w(left right),
      select_summaries: %w(タイトルの前 タイトルの後)
    },
    {
      name: "title_date_target",
      type: :select,
      value: "general_lastup",
      help: "enable_add_date_to_title で付与する日付の種類。\ngeneral_lastup(最新話掲載日),last_update(更新日),new_arrivals_date(新着を確認した日),convert(変換した日)",
      select_keys: %w(general_lastup last_update new_arrivals_date convert),
      select_summaries: %w(最新話掲載日 更新日 新着を確認した日 変換した日)
    },
    {
      name: "enable_ruby_youon_to_big",
      type: :boolean,
      value: false,
      help: "ルビの拗音(ぁ、ぃ等)を商業書籍のように大きくする"
    },
    {
      name: "enable_pack_blank_line",
      type: :boolean,
      value: true,
      help: "縦書きで読みやすいように空行を減らす"
    },
    {
      name: "enable_kana_ni_to_kanji_ni",
      type: :boolean,
      value: true,
      help: "漢字の二と間違えてカタカナのニを使っていそうなのを、漢字に直す"
    },
    {
      name: "enable_insert_word_separator",
      type: :boolean,
      value: false,
      help: "単語選択がしやすいように単語単位の区切りデータを挿入する(Kindle専用)"
    },
    {
      name: "enable_insert_char_separator",
      type: :boolean,
      value: false,
      help: "文字選択がしやすいように1文字ずつ区切りデータを挿入する(Kindle専用。enable_insert_word_separator が有効な場合無この設定は無視される)"
    },
    {
      name: "enable_strip_decoration_tag",
      type: :boolean,
      value: false,
      help: "HTMLの装飾系タグを削除する(主にArcadiaの作品に影響)"
    },
    {
      name: "enable_double_dash_to_image",
      type: :boolean,
      value: false,
      help: "2倍ダッシュ(――)を画像に差し替える。Kindleのデフォルトフォントみたいにダッシュが太くて気になる人用"
    },
    {
      name: "enable_add_end_to_title",
      type: :boolean,
      value: false,
      help: "完結済み小説のタイトルに(完結)と表示する"
    },
    {
      name: "cut_old_subtitles",
      type: :integer,
      value: 0,
      help: "1話目から指定した話数分、変換の対象外にする。" \
            "全話数分以上の数値を指定した場合、最新話だけ変換する"
    },
    {
      name: "author_comment_style",
      type: :select,
      value: "css",
      help: "作者コメント(前書き・後書き)の装飾方法を指定する。KoboやAdobe Digital Editionでは「CSSで装飾」にするとデザインが崩れるのでそれ以外を推奨。css:CSSで装飾、simple:シンプルに段落、plain:装飾しない",
      select_keys: %w(css simple plain),
      select_summaries: %w(CSSで装飾 シンプルに段落 装飾しない)
    },
  ]

  ORIGINAL_SETTINGS_KEY_INDEXES = {}.tap { |hash|
    ORIGINAL_SETTINGS.each_with_index do |s, i|
      hash[s[:name]] = i
    end
  }
end