class Puzzle
  def initialize(input = nil)
    @input = input || File.readlines("input").map(&:strip)
  end

  def calc_part_1
    passports = parse_passport_data(@input)
    passports.select{ |p| passport_presence_validation(p) }.size
  end

  def calc_part_2
    passports = parse_passport_data(@input)
    passports.select{ |p| passport_presence_validation(p) && passport_content_validation(p) }.size
  end

  private

  def parse_passport_data(input)
    input.map{ |i| i == "" ? "###" : i}.join(" ").split("###").map(&:strip).map{ |p| parse_passport(p) }
  end

  def parse_passport(input)
    input.split(" ").map do |attr|
      attr.split(":")
    end.to_h
  end

  def passport_presence_validation(passport)
    ["byr", "iyr", "eyr", "hgt", "hcl", "ecl", "pid"].all?{ |el| !passport[el].nil? }
  end

  def passport_content_validation(passport)
    [
      (1920..2002).include?(passport["byr"].to_i), # byr (Birth Year) - four digits; at least 1920 and at most 2002.
      (2010..2020).include?(passport["iyr"].to_i), # iyr (Issue Year) - four digits; at least 2010 and at most 2020.
      (2020..2030).include?(passport["eyr"].to_i), # eyr (Expiration Year) - four digits; at least 2020 and at most 2030.
      height_validation(passport["hgt"]),
      hair_color_validation(passport["hcl"]),
      eye_color_validation(passport["ecl"]),
      passport_id_validation(passport["pid"])
    ].all?
  end

  # hgt (Height) - a number followed by either cm or in:
  # If cm, the number must be at least 150 and at most 193.
  # If in, the number must be at least 59 and at most 76.
  def height_validation(height)
    if height.include?("cm")
      (150..193).include?(height.gsub("cm", "").to_i)
    elsif height.include?("in")
      (59..76).include?(height.gsub("in", "").to_i)
    else
      false
    end
  end

  # hcl (Hair Color) - a # followed by exactly six characters 0-9 or a-f.
  def hair_color_validation(hair_color)
    hair_color[0] == "#" && hair_color.size == 7 &&
      hair_color.split("").drop(1).all?{ |l| %w(0 1 2 3 4 5 6 7 8 9 a b c d e f).include?(l) }
  end

  # ecl (Eye Color) - exactly one of: amb blu brn gry grn hzl oth.
  def eye_color_validation(eye_color)
    %w(amb blu brn gry grn hzl oth).include?(eye_color)
  end

  # pid (Passport ID) - a nine-digit number, including leading zeroes.
  def passport_id_validation(passport_id)
    passport_id.size == 9 && passport_id.split("").all?{ |d| %w(0 1 2 3 4 5 6 7 8 9).include?(d) }
  end
end
