# frozen_string_literal: true

require 'cureutils/logic/base_logic'
require 'date'

#
# Date class supports the precure format
#
class DateLogic < BaseLogic
  def initialize
    super
    hashize_cure_date
    hashize_movie_date
    @in = []
    @in << nil
    @opt_date = false
    @opt_file = false
    @format = '+%F %H:%M:%S @P'
  end

  def opt_date(given_datetime)
    return if given_datetime.nil?

    @opt_date = true
    @in = []
    @in << given_datetime
  end

  def opt_file(filename)
    return if filename.nil?

    @opt_file = true
    source_input(filename)
  end

  attr_writer :format

  def print_results
    return 1 unless check_opts

    @in.each do |line|
      given_date = line ? natural_lang2time(line) : Time.now
      updated_fmt = update_fmt(given_date, @format) if given_date
      @out.puts given_date.strftime(updated_fmt) if updated_fmt
    end
    0
  end

  private

  def check_opts
    # Either option must be true, but both must NOT be same.
    return true unless @opt_date && @opt_file

    @err.puts <<-EOS
cure date: the options to specify dates for printing are mutually exclusive.
    EOS
    false
  end

  def validate_date_str(str)
    Date.parse(str)
  rescue ArgumentError => e
    @err.puts "cure date: #{e.message} '#{str.chomp}'"
    false
  end

  def natural_lang2time(time_str)
    updated_fmt = time_str.dup
    updated_fmt.gsub!(/yesterday/, '1 day ago')
    updated_fmt.gsub!(/tomorrow/, '-1 day ago')
    units = 'second|minute|hour|day|week|month|year'
    regulated_format = /(-?[0-9]+) *(#{units})s? *(ago)?/
    if updated_fmt =~ regulated_format
      diff_value = Regexp.last_match(1).to_i
      unit = Regexp.last_match(2).to_sym
      minus_flg = Regexp.last_match(3)
      return Time.now + diff_value.send(unit) if minus_flg.nil?
      return Time.now - diff_value.send(unit)
    elsif validate_date_str(updated_fmt)
      # Format like "yyyy-mm-dd", "yyyy/mm/dd" and "yyyymmdd"
      return Time.parse(updated_fmt)
    end
    false
  end

  def update_fmt(datetime, fmt)
    # Find precure related events
    date4check_event = time2date(datetime)
    found_event = events(date4check_event)
    checked_fmt = fmt
    if fmt =~ /^\+(.*)$/
      checked_fmt = Regexp.last_match(1)
    else
      @err.puts "cure date: invalid date format '#{fmt}'"
      exit 1
    end
    # Find precure related events
    checked_fmt.gsub(/@P/, found_event)
  end

  def events(date)
    event_list = [birth_date?(date),
                  created_date?(date),
                  movie_date?(date),
                  series_between(date)].compact
    event_list.flatten.join('/')
  end

  def time2date(time_obj)
    Date.parse(time_obj.strftime('%Y-%m-%d'))
  end

  def birth_date?(date)
    date_str = date.strftime('%-m/%-d')
    result = @birth_date[date_str.to_sym]
    return if result.nil?
    result.map { |v| "#{v[:human_name]}(#{v[:precure_name]})誕生日" }
  end

  def created_date?(date)
    date_str = date.strftime('%Y-%m-%d')
    result = @created_date[date_str.to_sym]
    return if result.nil?
    result.map { |v| v[:precure_name] + '登場日' }
  end

  def movie_date?(date)
    date_str = date.strftime('%Y-%m-%d')
    result = @movie_started_date[date_str.to_sym]
    # format
    "#{result[:title]}映画公開日" unless result.nil?
  end

  def series_between(date)
    event_name = nil
    Rubicure::Series.config.each do |_k, v|
      if v[:started_date] == date
        event_name = "#{v[:title]}初放映"
        break
      end
      if v[:ended_date] == date
        event_name = "#{v[:title]}最終回"
        break
      end
      # Nobody expects the future. Precure series may be abolished today.
      v[:ended_date] = Date.today + 1 if v[:ended_date].nil?
      if v[:started_date] < date && date < v[:ended_date]
        event_name = "#{v[:title]}放映期間"
        break
      end
    end
    event_name
  end

  def hashize_cure_date
    @birth_date = {}
    @created_date = {}
    Precure.all_girls.each do |precure|
      # Create birthday table
      birthday_key = precure.birthday.to_s.to_sym
      @birth_date =
        create_array_on_hash(@birth_date, birthday_key, precure)
      # Create table for the first appeared date .
      created_date_key = precure.created_date.strftime('%Y-%m-%d').to_sym
      @created_date =
        create_array_on_hash(@created_date, created_date_key, precure)
    end
  end

  def create_array_on_hash(arr_hash, key, value)
    arr_hash[key] = [] unless arr_hash.key?(key)
    arr_hash[key] << value
    arr_hash
  end

  def hashize_movie_date
    @movie_started_date = {}
    Rubicure::Movie.config.map do |_k, v|
      next if v[:started_date].nil?
      @movie_started_date[v[:started_date].strftime('%Y-%m-%d').to_sym] = v
    end
  end
end
