module Bridge

  # Class representing bridge deal
  class Deal
    include Comparable

    attr_reader :n, :e, :s, :w

    # Returns cards of given direction
    def [](direction)
      must_be_direction!(direction)
      send("#{direction.to_s.downcase}")
    end

    # Compares the deal with given deal
    def <=>(other)
      id <=> other.id
    end

    # Creates new deal object with cards given in hash of directions
    #
    # ==== Example
    #   Bridge::Deal.new(:n => ["HA", ...], :s => ["SA"], ...)
    def initialize(hands)
      hands.each do |hand, cards|
        self[hand] = cards.map { |c| Card.new(c) }
      end
    end

    # Converts given id to deal
    def self.from_id(id)
      raise ArgumentError, "invalid deal id: #{id}" unless Bridge.deal_id?(id)
      n = []; e = []; s = []; w = []; k = DEALS
      DECK.each_with_index do |card, i|
        card = Card.new(card)
        x = k * (13 - n.size) / (52 - i)
        if id < x
          n << card
        else
          id -= x
          x = k * (13 - e.size) / (52 - i)
          if id < x
            e << card
          else
            id -= x
            x = k * (13 - s.size) / (52 - i)
            if id < x
              s << card
            else
              id -= x
              x = k * (13 - w.size) / (52 - i)
              w << card
            end
          end
        end
        k = x
      end
      new(:n => n, :e => e, :s => s, :w => w)
    end

    # Converts given deal (hash) to id
    def id
      k = DEALS; id = 0; n = self.n.dup; e = self.e.dup; s = self.s.dup; w = self.w.dup
      DECK.each_with_index do |card, i|
        x = k * n.size / (52 - i)
        unless n.delete(card)
          id += x
          x = k * e.size / (52 - i)
          unless e.delete(card)
            id += x
            x = k * s.size / (52 - i)
            unless s.delete(card)
              id += x
              x = k * w.size / (52 - i)
              w.delete(card)
            end
          end
        end
        k = x
      end
      id
    end

    # Returns the direction that owns the card
    def owner(card)
      DIRECTIONS.find { |direction| self[direction].include?(card) }
    end

    # Returns a random deal id
    def self.random_id
      rand(DEALS)
    end

    # Returns a random deal
    def self.random
      from_id(random_id)
    end

    # Checks if the deal is a valid deal
    def valid?
      if DIRECTIONS.all? { |d| self[d] && self[d].size == 13 }
        cards = (n + e + s + w).uniq
        if cards.size == 52
          cards.all? { |card| Bridge.card?(card.to_s) }
        else
          false
        end
      else
        false
      end
    end

    # Returns hash with hands
    def to_hash
      # use map to be 1.8.6 compatible
      { "N" => n.map{ |c| c.to_s }, "E" => e.map{ |c| c.to_s }, "S" => s.map{ |c| c.to_s }, "W" => w.map{ |c| c.to_s } }
    end

    def inspect
      to_hash.inspect
    end

    def honour_card_points(side = nil)
      hash = DIRECTIONS.inject({}) do |h, direction|
        h[direction] = self[direction].inject(0) { |sum, card| sum += card.honour_card_points }
        h
      end
      if side
        side.to_s.upcase.split("").inject(0) { |sum, direction| sum += hash[direction] }
      else
        hash
      end
    end
    alias :hcp :honour_card_points

    def sort_by_color!(trump = nil)
      sort_by_color(trump).each do |direction, hand|
        self[direction] = hand
      end
      self
    end

    def sort_by_color(trump = nil)
      DIRECTIONS.inject({}) do |sorted, direction|
        splitted_colors = split_colors(direction)
        sorted_colors = sort_colors(splitted_colors.keys, trump)
        sorted[direction] = sorted_colors.inject([]) { |cards, color| cards << splitted_colors.delete(color) }.flatten
        sorted
      end
    end

    private

    def must_be_direction!(string)
      raise ArgumentError, "invalid direction: #{string}" unless Bridge.direction?(string.to_s.upcase)
    end

    def []=(direction, cards)
      must_be_direction!(direction)
      instance_variable_set("@#{direction.to_s.downcase}", cards)
    end

    def split_colors(direction)
      TRUMPS.inject({}) do |colors, trump|
        cards = self[direction].select { |card| card.suit == trump }
        colors[trump] = cards unless cards.empty?
        colors
      end
    end

    def sort_colors(colors, trump = nil)
      black = ["S", "C"] & colors
      red = ["H", "D"] & colors
      sorted = if black.delete(trump)
        [trump] << red.shift << black.shift << red.shift
      elsif red.delete(trump)
        [trump] << black.shift << red.shift << black.shift
      elsif black.size >= red.size
        [black.shift] << red.shift << black.shift << red.shift
      else
        [red.shift] << black.shift << red.shift << black.shift
      end
      sorted.compact
    end
  end
end