require "csv"
require "zip"

require_relative "dataset"

module Datasets
  class PostalCodeJapan < Dataset
    class Record < Struct.new(:organization_code,
                              :old_postal_code,
                              :postal_code,
                              :prefecture_reading,
                              :city_reading,
                              :address_reading,
                              :prefecture,
                              :city,
                              :address,
                              :have_multiple_postal_codes,
                              :have_address_number_per_koaza,
                              :have_chome,
                              :postal_code_is_shared,
                              :changed,
                              :change_reason)
      alias_method :have_multiple_postal_codes?,
                   :have_multiple_postal_codes
      alias_method :have_address_number_per_koaza?,
                   :have_address_number_per_koaza
      alias_method :have_chome?,
                   :have_chome
      alias_method :postal_code_is_shared?,
                   :postal_code_is_shared
      alias_method :changed?,
                   :changed
    end

    VALID_READINGS = [
      :lowercase,
      :uppercase,
      :romaji,
    ]
    def initialize(reading: :lowercase)
      super()
      @reading = reading
      unless VALID_READINGS.include?(@reading)
        message = +":reading must be one of ["
        message << VALID_READINGS.collect(&:inspect).join(", ")
        message << "]: #{@reading.inspect}"
        raise ArgumentError, message
      end
      @metadata.id = "postal-code-japan-#{@reading}"
      @metadata.name = "Postal code in Japan (#{@reading})"
      @metadata.url = "https://www.post.japanpost.jp/zipcode/download.html"
      @metadata.licenses = ["CC0-1.0"]
      @metadata.description = "Postal code in Japan (reading: #{@reading})"
    end

    def each(&block)
      return to_enum(__method__) unless block_given?

      open_data do |input|
        utf8_data = input.read.encode(Encoding::UTF_8, Encoding::CP932)
        options = {
          quote_char: nil,
          strip: %Q["],
        }
        if @reading == :romaji
          CSV.parse(utf8_data, **options) do |row|
            yield(Record.new(nil,
                             nil,
                             row[0],
                             row[4],
                             row[5],
                             row[6],
                             row[1],
                             row[2],
                             row[3],
                             false,
                             false,
                             false,
                             false,
                             false,
                             nil))
          end
        else
          CSV.parse(utf8_data, **options) do |row|
            yield(Record.new(row[0],
                             row[1].rstrip,
                             row[2],
                             row[3],
                             row[4],
                             row[5],
                             row[6],
                             row[7],
                             row[8],
                             (row[9] == "1"),
                             (row[10] == "1"),
                             (row[11] == "1"),
                             (row[12] == "1"),
                             (row[13] != "0"),
                             convert_change_reason(row[14])))
          end
        end
      end
    end

    private
    def open_data
      data_url = +"https://www.post.japanpost.jp/zipcode/dl"
      case @reading
      when :lowercase
        data_url << "/kogaki/zip/ken_all.zip"
      when :uppercase
        data_url << "/oogaki/zip/ken_all.zip"
      when :romaji
        data_url << "/roman/KEN_ALL_ROME.zip"
      end
      data_path = cache_dir_path + "#{@reading}-ken-all.zip"
      download(data_path, data_url)

      Zip::File.open(data_path.to_s) do |zip_file|
        zip_file.each do |entry|
          next unless entry.file?
          entry.get_input_stream do |input|
            yield(input)
          end
        end
      end
    end

    def convert_change_reason(reason)
      case reason
      when "0"
        nil
      when "1"
        :new
      when "2"
        :japanese_addressing_system
      when "3"
        :land_readjustment
      when "4"
        :postal_district_adjustment
      when "5"
        :correction
      when "6"
        :deletion
      else
        :unknown
      end
    end
  end
end
