VALUES = [:two, :three, :four, :five, :six, :seven, :eight, :nine, :ten, :jack, :queen, :king, :ace]
SUITS = [:heart, :diamond, :spade, :club]

STRING_TO_SUIT_MAP = { 'H' => :heart,
                       'D' => :diamond,
                       'S' => :spade,
                       'C' => :club }

STRING_TO_VALUE_MAP = {'A' => :ace,
                       'K' => :king,
                       'Q' => :queen,
                       'J' => :jack,
                       '10' => :ten,
                       'T' => :ten,
                       '9' => :nine,
                       '8' => :eight,
                       '7' => :seven,
                       '6' => :six,
                       '5' => :five,
                       '4' => :four,
                       '3' => :three,
                       '2' => :two}

class Card
  include Comparable

  attr_reader :suit, :value

  def initialize(suit, value)
    @suit = STRING_TO_SUIT_MAP[suit]
    @value = STRING_TO_VALUE_MAP[value]
  end

  def succ_value
    if value == :ace
      :two
    else
      VALUES[VALUES.index(value) + 1]
    end
  end

  def succ? other
    succ_value == other.value
  end

  def <=> other
    VALUES.index(value) <=> VALUES.index(other.value)
  end

  def === other
    suit == other.suit and value == other.value
  end

  def ace?
    value == :ace
  end
end

class Hand
  include Comparable

  attr_reader :cards

  def initialize(*cards)
    if cards.first.is_a? String
      card_strings = cards.first.split(' ')
      @cards = card_strings.map { |str| str.split('') }.map { |value, suit| Card.new(suit, value) }
    else
      @cards = cards
    end
  end

  def flush?
    suits.uniq.one?
  end

  def straight?
    if ace?
      all_successors? cards.sort or all_successors? aces_low
    else
      all_successors? cards.sort
    end
  end

  def straight_flush?
    straight? and flush?
  end

  def royal_flush?
    flush? and
    values.include?(:ten) and
    values.include?(:jack) and
    values.include?(:queen) and
    values.include?(:king) and
    values.include?(:ace)
  end

  def four_of_a_kind?
    same_of_value?(4)
  end

  def three_of_a_kind?
    same_of_value?(3)
  end

  def full_house?
    same_of_value?(3) and same_of_value?(2)
  end

  def two_pair?
    collapsed == 2 and same_of_value?(2)
  end

  def pair?
    same_of_value?(2)
  end

  def values
    cards.map(&:value)
  end

  def suits
    cards.map(&:suit)
  end

  def aces_low
    sorted = cards.sort
    while sorted.last.ace?
      ace = sorted.pop
      sorted.insert(0, ace)
    end
    sorted
  end

  def ace?
    cards.any? { |card| card.ace? }
  end

  def high_card
    cards.sort.last
  end

  def full_house_high_value
    cards.group_by { |card| card.value }.detect { |k, v| v.count == 3 }.first
  end

  def full_house_low_value
    cards.group_by { |card| card.value }.detect { |k, v| v.count == 2 }.first
  end

  def high_match_value
    cards.group_by { |card| card.value }.select { |k, v| v.count > 1 }.keys.max { |a, b| VALUES.index(a) <=> VALUES.index(b) }
  end

  def low_match_value
    cards.group_by { |card| card.value }.select { |k, v| v.count > 1 }.keys.min { |a, b| VALUES.index(a) <=> VALUES.index(b) }
  end

  def kicker
    removed_value(high_match_value).sort.last
  end

  def <=> other
    poker_score <=> other.poker_score
  end

  def poker_score
    if royal_flush?
      900000
    elsif straight_flush?
      800000 + high_card_score
    elsif four_of_a_kind?
      700000 + high_match_score * 100 + kicker_score
    elsif full_house?
      600000 + full_house_high_score * 1000 + full_house_low_score * 10
    elsif flush?
      500000 + high_card_score
    elsif straight?
      400000 + high_card_score
    elsif three_of_a_kind?
      300000 + high_match_score * 100 + kicker_score
    elsif two_pair?
      200000 + high_match_score * 1000 + low_match_score * 10 + kicker_score
    elsif pair?
      100000 + high_match_score * 100 + kicker_score
    else
      high_card_score
    end
  end

  private

  def kicker_score
    VALUES.index(kicker.value)
  end

  def high_card_score
    VALUES.index(high_card.value)
  end

  def high_match_score
    VALUES.index(high_match_value)
  end

  def low_match_score
    VALUES.index(low_match_value)
  end

  def full_house_high_score
    VALUES.index(full_house_high_value)
  end

  def full_house_low_score
    VALUES.index(full_house_low_value)
  end

  def removed_value(value)
    cards.reject { |card| card.value == value }
  end

  def collapsed
    cards.size - values.uniq.size
  end

  def same_of_value(n)
    cards.detect { |card| cards.count { |c| c.value == card.value } == n }.value
  end

  def same_of_value?(n)
    cards.any? { |card| cards.count { |c| c.value == card.value } == n }
  end

  def all_successors?(cards)
    cards.all? {|card| card === cards.last || card.succ?(cards[cards.index(card) + 1]) }
  end
end

def games
  game_lines = File.readlines("../input/p054_poker.txt")
  game_lines.map { |game_line| [game_line[0..13], game_line[15..28]] }.map { |one, two| [Hand.new(one), Hand.new(two)] }
end

def solution
  games.map { |one, two| one > two }.count(true)
end
