# frozen_string_literal: true

#
# Copyright 2013 whiteleaf. All rights reserved.
#

#
# 小説の状態を監視・検査する
#
class Inspector
  INSPECT_LOG_NAME = "調査ログ.txt"
  LINE_LENGTH_THRESHOLD = 400
  BRACKETS_RETURN_COUNT_THRESHOLD = 7
  END_TOUTEN_COUNT_THRESHOLD = 50

  ERROR = 1
  WARNING = 2
  INFO = 4
  ALL = ERROR | WARNING | INFO

  KLASS_TAG = { ERROR => "エラー", WARNING =>  "警告", INFO => "INFO" }

  IGNORE_INDENT_CHAR = "(（「『〈《≪【〔―・※［〝\n"
  AUTO_INDENT_THRESHOLD_RATIO = 0.5   # 括弧等を除く全ての行のうちこの割合以上字下げされてなければ強制字下げする

  attr_writer :messages, :subtitle

  def self.read_messages(setting)
    inspect_log = File.join(setting.archive_path, INSPECT_LOG_NAME)
    if File.exist?(inspect_log)
      File.read(inspect_log)
    else
      nil
    end
  end

  def initialize(setting)
    @setting = setting
    @messages = []
    @error = false
    @warning = false
    @info = false
    @subtitle = ""
  end

  def empty?
    @messages.empty?
  end

  def error?
    @error
  end

  def warning?
    @warning
  end

  def info?
    @info
  end

  def display_summary(target = $stdout)
    target.print "小説状態の調査結果を #{Inspector::INSPECT_LOG_NAME} に出力しました（"
    target.print KLASS_TAG.values.map { |klass_type|
      num = @messages.count { |msg| msg =~ /^\[#{klass_type}\]/ }
      "#{klass_type}：#{num}件"
    }.join("、")
    target.puts "）"
  end

  def display(klass = ALL, target = $stdout)
    target.puts @messages.map { |msg|
      if msg =~ /^\[(.+)\]/
        key = KLASS_TAG.key($1)
        if key && (klass & key) != 0
          next msg
        end
      end
      nil
    }.compact.join("\n\n")
  end

  def save(path = nil)
    path = File.join(@setting.archive_path, INSPECT_LOG_NAME) if path.nil?
    open(path, "w") do |fp|
      fp.puts "--- ログ出力 #{Time.now} ---"
      display(ALL, fp)
    end
  end

  def log(message)
    @messages << message
  end

  def info(message)
    log("[#{KLASS_TAG[INFO]}] #{message}")
    @info = true
  end

  def warning(message)
    log("[#{KLASS_TAG[WARNING]}] #{message}")
    @warning = true
  end

  def error(message)
    log("[#{KLASS_TAG[ERROR]}] #{message}")
    @error = true
  end

  def omit_message(strings)
    navigation = "in #{@subtitle}" if @subtitle.to_s.length > 0
    "≫≫≫ 該当箇所 #{navigation}\n..." + strings[0...36].gsub("\n", "\\n") + "..."
  end

  #
  # 連結したかぎ括弧が正常かどうか
  #
  def validate_joined_inner_brackets(raw_strings, joined_strings, brackets)
    error_result = false
    case
    # 連結前の文章の改行具合を調べて、改行が閾値を超えた場合意図的な改行とみなす
    when raw_strings.count("\n") >= BRACKETS_RETURN_COUNT_THRESHOLD
      warning("改行が規定の回数を超えて検出されました。" +
              "作者による意図的な改行とみなし、連結を中止しました。\n" +
              omit_message(raw_strings))
      error_result = true
    # 連結した文章があまりにも長い場合、特殊な用途で使われている可能性がある
    when joined_strings.length >= LINE_LENGTH_THRESHOLD
      warning("連結結果が長過ぎます。連結を中止しました。" +
              "特殊な用途(手紙形式)等でかぎ括弧が使われている可能性があります。\n" +
            omit_message(raw_strings))
      error_result = true
    end
    error_result
  end

  #
  # かぎ括弧のとじ開きの異常部分を調査
  #
  def inspect_invalid_openclose_brackets(data, brackets, stack)
    brackets.each do |bracket|
      buffer = data.dup
      while buffer =~ /#{bracket}/
        match_before = $`.dup
        match_after = $'.dup
        before = ConverterBase.rebuild_brackets(match_before, stack)
        after = ConverterBase.rebuild_brackets(match_after, stack)
        error("かぎ括弧(#{bracket})が正しく閉じていません。\n" +
              omit_message((before[-15..-1] || before) + bracket + after))
        buffer = match_before
      end
    end
  end

  #
  # 行末読点の状況を調べる
  #
  def inspect_end_touten_conditions(data)
    return if @setting.enable_auto_join_line
    num = 0
    data.scan(/、\n　/) do
      num += 1
    end
    if num > 0
      msg = +"#{num}個の行末読点を発見しました。"
      if num >= END_TOUTEN_COUNT_THRESHOLD
        msg << "作者による手動改行により改行が多くなっています。" + \
               "setting.ini の enable_auto_join_line を true にすることをお薦めします。"
      end
      info(msg)
    end
  end

  #
  # カギ括弧内の改行状況を調べる
  #
  def countup_return_in_brackets(data)
    return if @setting.enable_auto_join_in_brackets
    max = 0
    brackets_num = 0
    brackets_num_over_threshould = 0
    total = 0
    ConverterBase::OPENCLOSE_REGEXPS.each do |openclose|
      data.scan(openclose) do |match|
        cnt = match[0].count("\n")
        brackets_num += 1
        total += cnt
        next if cnt < BRACKETS_RETURN_COUNT_THRESHOLD
        brackets_num_over_threshould += 1
        if cnt > max
          max = cnt
        end
      end
    end
    info("カギ括弧内の改行状況:\n" +
         "検出したカギ括弧数: #{brackets_num}、そのうち#{BRACKETS_RETURN_COUNT_THRESHOLD}個以上改行を含む数: #{brackets_num_over_threshould}\n" + 
         "1つのカギ括弧内で最大の改行数: #{max}、全カギ括弧内での改行合計: #{total}")
  end

  #
  # 行頭字下げをするべきか調べる
  #
  def inspect_indent(data)
    target_line_count = 0
    dont_indent_line_count = 0
    data.scan(/^[^#{IGNORE_INDENT_CHAR}]/).tap { |a|
      target_line_count = a.size
    }.each { |line|
      head = line[0]
      if head != " " && head != "　"
        dont_indent_line_count += 1
      end
    }
    ratio = dont_indent_line_count / target_line_count.to_f
    return ratio > AUTO_INDENT_THRESHOLD_RATIO
  end
end
