"""  """

from functools import reduce
from itertools import combinations
import logging
from operator import __or__

from clovers_tiles_heart_spikes.cards import CARDS_IN_COLOR_COUNT

logging.basicConfig(level=logging.INFO)
LOGGER = logging.getLogger(__name__)

def generate_hands_from_deck(deck_card_count, hand_card_count):
    """ Choose `hand_card_count` from `deck_card_count`
    Args:
        deck_card_count: int - Total number of cards in deck
        hand_card_count: int - Cards in the hand to construct
    Returns:
        Generator of all combinations of (card value, card index)
        within choose `hand_card_count` from `deck_card_count`
    """
    deck = [(0b1 << i, i) for i in range(deck_card_count)]
    return combinations(deck, hand_card_count)

def normalize_hand(hand, suit_counts, suit_sums, suit_max):
    """ Sort and update suits to reduce duplicates
        where the only difference is suits. E.g.
        QcQs 2dTcTs == QdQh 2cTdTh
        ## 134,459 distinct hands
    """
    suit_values = sorted(suit_counts.values(), reverse=True)
    double_suited = suit_values[0] == suit_values[1] == 2
    cards = sorted(hand, key=lambda c: (
        suit_counts[c // CARDS_IN_COLOR_COUNT],
        suit_sums[c // CARDS_IN_COLOR_COUNT],
        suit_max[c // CARDS_IN_COLOR_COUNT],
        c // CARDS_IN_COLOR_COUNT if double_suited and suit_counts[c // CARDS_IN_COLOR_COUNT] else 0,
        c % CARDS_IN_COLOR_COUNT), reverse=True)
    suit_count = 0
    suit_last = cards[0]//CARDS_IN_COLOR_COUNT
    test = []
    normalized = []
    for c in cards:
        rank = c % CARDS_IN_COLOR_COUNT
        suit = c // CARDS_IN_COLOR_COUNT
        if suit_last != suit:
            suit_count += 1

        if suit_count * CARDS_IN_COLOR_COUNT + rank >= 52: #3c3d2c2d2h
            raise ValueError('hey')

        test.append(suit_count * CARDS_IN_COLOR_COUNT + rank)
        normalized.append(0b1 << (suit_count * CARDS_IN_COLOR_COUNT + rank))
        suit_last = suit

    #print(hand, cards, test)

    return reduce(__or__, normalized), test

def card_display(card_index):
    values = { 8: 'T', 9: 'J', 10: 'Q',  11: 'K', 12: 'A' }
    suits = { 0: u'♣', 1: u'♦', 2: u'♥', 3: u'♠'}
    suits = { 0: u'c', 1: u'd', 2: u'h', 3: u's'}
    rank = card_index % CARDS_IN_COLOR_COUNT
    suit = card_index // CARDS_IN_COLOR_COUNT
    return (str(rank + 2) if rank < 8 else values[rank]) + suits[suit]


def generate_cards_from_hands():
    """ Foreach hand, generate metrics for ranking
    Returns:
        Dictionary<int, (int)>:
            Key - integer representing hand
            Value - tuple (
                [list of card indexes]
            )
    """
    hands_seen = set()
    hand_ranks = []
    hand_count = 0
    for card_list in generate_hands_from_deck(52, 5):
        if hand_count and hand_count % 250000 == 0:
            LOGGER.info(f'..cached {hand_count} hands..')

        hand_count += 1
        cards, indexes = zip(*card_list)
        hand = reduce(__or__, cards)

        card_suits = [index // CARDS_IN_COLOR_COUNT for index in indexes]
        suit_counts = {i: 0 for i in range(4)}
        suit_sums = {i: 0 for i in range(4)}
        suit_max = {i: 0 for i in range(4)}
        for index in indexes:
            s = index // CARDS_IN_COLOR_COUNT
            r = index % CARDS_IN_COLOR_COUNT
            suit_counts[s] += 1
            suit_sums[s] += r
            suit_max[s] = max([r, suit_max[s]])

        #hand, test = normalize_hand(indexes, suit_counts, suit_sums, suit_max)
        test = indexes

        if hand in hands_seen:
            continue

        suit_counts = sorted(suit_counts.values(), reverse=True)
        suit_count = suit_counts[0]

        is_flush = all(card_suits[0] == suit for suit in card_suits)

        card_ranks = sorted([index % CARDS_IN_COLOR_COUNT for index in indexes])
        rank_counts = {i: 0 for i in range(CARDS_IN_COLOR_COUNT)}
        ranks = set()
        for rank in card_ranks:
            rank_counts[rank] += 1
            ranks.add(rank)
            if rank == 12:
                ranks.add(-1)

        ranks = sorted(ranks)
        straight_count, str8_count = 1, 1
        last_rank = ranks[0]
        for rank in ranks:
            if rank == last_rank:
                continue
            elif rank == last_rank + 1:
                str8_count += 1
            else:
                straight_count = max(straight_count, str8_count)
                str8_count = 1

            last_rank = rank

        straight_count = max(straight_count, str8_count)
        rank_by_count = {}
        for k, v in rank_counts.items():
            rank_by_count[v] = k
        
        rank_values = sorted(rank_counts.values(), reverse=True)
        is_four_of_a_kind = rank_values[0] == 4
        is_four_aces = is_four_of_a_kind and rank_by_count[4] == 12 # ACE
        is_four_aces_w_234 = is_four_aces and rank_by_count[1] < 3 # FIVE
        is_four_aces = is_four_aces and not is_four_aces_w_234

        is_four_432 = is_four_of_a_kind and rank_by_count[4] < 3 # FIVE
        is_four_432_w_a234 = is_four_432 and (
            rank_by_count[1] < 3 or # FIVE
            rank_by_count[1] == 12) # ACE
        is_four_432 = is_four_432 and not is_four_432_w_a234

        is_four_of_a_kind_fives_to_kings = is_four_of_a_kind and (
            rank_by_count[4] > 2 and # FOUR
            rank_by_count[4] < 12) # ACE

        is_full_house = rank_values[0] == 3 and rank_values[1] == 2
        is_three_of_a_kind = not is_full_house and rank_values[0] == 3
        is_two_pair = rank_values[0] == 2 and rank_values[1] == 2
        is_pair_jacks_or_better = not is_two_pair and rank_values[0] == 2 and rank_by_count[2] > 8 # TEN

        is_straight = len(set(card_ranks)) == 5 and (
            card_ranks[-1] - card_ranks[0] == 4 or
            card_ranks[-1] - card_ranks[-2] == 9)
        is_royal_flush = is_straight and is_flush and card_ranks[0] == 8 and card_ranks[-1] == 12
        is_straight_flush = not is_royal_flush and is_straight and is_flush
        is_straight = is_straight and not (is_royal_flush or is_straight_flush)
        is_flush = is_flush and not (is_royal_flush or is_straight_flush)

        hands_seen.add(hand)
        hand_ranks.append({
            'hand': hand,
            'display': ''.join([card_display(t) for t in test]),
            'is_royal_flush': 1 if is_royal_flush else 0,
            'is_straight_flush': 1 if is_straight_flush else 0,
            'is_four_aces_w_234': 1 if is_four_aces_w_234 else 0,
            'is_four_432_w_a234': 1 if is_four_432_w_a234 else 0,
            'is_four_aces': 1 if is_four_aces else 0,
            'is_four_432': 1 if is_four_432 else 0,
            'is_four_of_a_kind_fives_to_kings': 1 if is_four_of_a_kind_fives_to_kings else 0,
            'is_full_house': 1 if is_full_house else 0,
            'is_flush': 1 if is_flush else 0,
            'is_straight': 1 if is_straight else 0,
            'is_three_of_a_kind': 1 if is_three_of_a_kind else 0,
            'is_two_pair': 1 if is_two_pair else 0,
            'is_pair_jacks_or_better': 1 if is_pair_jacks_or_better else 0,
            'straight_count': straight_count,
            'suit_count': suit_count,
            '2': rank_counts[0],
            '3': rank_counts[1],
            '4': rank_counts[2],
            '5': rank_counts[3],
            '6': rank_counts[4],
            '7': rank_counts[5],
            '8': rank_counts[6],
            '9': rank_counts[7],
            'T': rank_counts[8],
            'J': rank_counts[9],
            'Q': rank_counts[10],
            'K': rank_counts[11],
            'A': rank_counts[12],
        })

    return hand_ranks
