/**
 * Copyright (c) 2021 Dominic Masters
 * 
 * This software is released under the MIT License.
 * https://opensource.org/licenses/MIT
 */

#include "winner.h"

void pokerWinnerHandGetFull(
  pokerdealer_t *dealer, pokerplayer_t *player, card_t *cards
) {
  uint8_t i;

  // Add the dealer hand
  for(i = 0; i < dealer->cardsFacing; i++) {
    cards[i] = dealer->cards[i];
  }

  // Add the player hand
  for(i = 0; i < player->cardCount; i++) {
    cards[i+dealer->cardsFacing] = player->cards[i];
  }

  // Sort by card value
  cardHandSort(cards, dealer->cardsFacing + player->cardCount);
}

void pokerWinnerPlayerGet(
  pokerdealer_t *dealer, pokerplayer_t *player, pokerplayerwinning_t *winning
) {
  uint8_t i, j, l;
  int32_t index;
  card_t card;
  uint8_t number, suit, pairCount;
  int32_t pairs[CARD_SUIT_COUNT];

  // Get the full poker hand (should be a 7 card hand, but MAY not be)
  winning->fullSize = dealer->cardsFacing + player->cardCount;
  pokerWinnerHandGetFull(dealer, player, winning->full);

  // Reset the winning status.
  winning->setSize = 0;
  
  //////////////////////// Now look for the winning set ////////////////////////

  // Royal / Straight Flush
  for(i = 0; i < winning->fullSize; i++) {
    card = winning->full[i];
    number = cardGetNumber(card);
    if(number < CARD_FIVE) continue;

    suit = cardGetSuit(card);
    winning->setSize = 1;

    // Now look for the matching cards (Reverse order to order from A to 10)
    for(j = 1; j <= 4; j++) {
      l = number == CARD_FIVE && j == 4 ? CARD_ACE : number - j;//Ace low.
      index = cardContains(winning->full, winning->fullSize, cardGet(l, suit));
      if(index == -1) break;
      winning->set[j] = winning->full[index];
      winning->setSize++;
    }

    // Check if has all necessary cards.
    if(winning->setSize < POKER_WINNING_SET_SIZE) continue;

    // Add self to array
    winning->set[0] = winning->full[i];
    winning->type = (
      number == CARD_ACE ? POKER_WINNING_TYPE_ROYAL_FLUSH : 
      POKER_WINNING_TYPE_STRAIGHT_FLUSH
    );
    return;
  }

  // Four of a kind.
  winning->setSize = 0;
  for(i = 0; i < winning->fullSize; i++) {
    card = winning->full[i];
    number = cardGetNumber(card);
    pairCount = cardCountPairs(winning->full, winning->fullSize, number, pairs);
    if(pairCount < CARD_SUIT_COUNT) continue;

    winning->setSize = pairCount;
    for(j = 0; j < pairCount; j++) winning->set[j] = winning->full[pairs[j]];
    winning->type = POKER_WINNING_TYPE_FOUR_OF_A_KIND;
    _pokerWinnerFillRemaining(winning);
    return;
  }

  // Full House
  winning->setSize = 0;
  for(i = 0; i < winning->fullSize; i++) {
    // Check we haven't already added this card.
    card = winning->full[i];
    if(cardContains(winning->set, winning->setSize, card) != -1) continue;

    number = cardGetNumber(card);
    pairCount = cardCountPairs(winning->full, winning->fullSize, number, pairs);

    // Did we find either two pair or three pair?
    if(pairCount != 2 && pairCount != 3) continue;
    if(winning->setSize == 3) pairCount = 2;//Clamp to 5 max.

    // Copy found pairs.
    for(j = 0; j < pairCount; j++) {
      winning->set[winning->setSize + j] = winning->full[pairs[j]];
    }
    winning->setSize += pairCount;

    // Winned?
    if(winning->setSize != POKER_WINNING_SET_SIZE) continue;
    winning->type = POKER_WINNING_TYPE_FULL_HOUSE;
    printf("Full House\n");
    return;
  }

  // Flush (5 same suit)
  for(i = 0; i < winning->fullSize; i++) {
    card = winning->full[i];
    suit = cardGetSuit(card);
    winning->setSize = 1;
    for(j = i+1; j < winning->fullSize; j++) {
      if(cardGetSuit(winning->full[j]) != suit) continue;
      winning->set[winning->setSize] = winning->full[j];
      winning->setSize++;
      if(winning->setSize == POKER_WINNING_SET_SIZE) break;
    }
    if(winning->setSize < POKER_WINNING_SET_SIZE) continue;
    winning->set[0] = winning->full[0];
    winning->type = POKER_WINNING_TYPE_FLUSH;
    return;
  }

  // Straight (sequence any suit)
  winning->setSize = 0;
  for(i = 0; i < winning->fullSize; i++) {
    card = winning->full[i];
    number = cardGetNumber(card);
    if(number < CARD_FIVE) continue;
    winning->setSize = 1;
    
    for(j = 1; j <= 4; j++) {
      l = number == CARD_FIVE && j == 4 ? CARD_ACE : number - j;//Ace low.
      index = cardContainsNumber(winning->full, winning->fullSize, l);
      if(index == -1) break;
      winning->set[j] = winning->full[index];
      winning->setSize++;
    }

    // Check if has all necessary cards.
    if(winning->setSize < POKER_WINNING_SET_SIZE) continue;
    winning->set[0] = winning->full[i];
    winning->type = POKER_WINNING_TYPE_STRAIGHT;
    return;
  }

  // Three of a kind
  winning->setSize = 0;
  for(i = 0; i < winning->fullSize; i++) {
    card = winning->full[i];
    number = cardGetNumber(card);
    pairCount = cardCountPairs(winning->full, winning->fullSize, number, pairs);
    if(pairCount != 3) continue;

    winning->setSize = pairCount;
    for(j = 0; j < pairCount; j++) winning->set[j] = winning->full[pairs[j]];
    winning->type = POKER_WINNING_TYPE_THREE_OF_A_KIND;
    _pokerWinnerFillRemaining(winning);
    return;
  }

  // Two Pair
  winning->setSize = 0;
  for(i = 0; i < winning->fullSize; i++) {
    card = winning->full[i];// Check we haven't already added this card.
    if(cardContains(winning->set, winning->setSize, card) != -1) {
      continue;
    }

    number = cardGetNumber(card);
    pairCount = cardCountPairs(winning->full, winning->fullSize, number, pairs);
    if(pairCount != 2) continue;
    
    for(j = 0; j < pairCount; j++) {
      winning->set[j] = winning->full[winning->setSize + pairs[j]];
    }
    // arrayCopy(sizeof(int32_t), pairs, pairCount, winning->set+winning->setSize);
    winning->setSize += pairCount;
    if(winning->setSize != 4) continue;

    winning->type = POKER_WINNING_TYPE_TWO_PAIR;
    _pokerWinnerFillRemaining(winning);
    return;
  }

  // Pair
  if(winning->setSize == 2) {
    winning->type = POKER_WINNING_TYPE_PAIR;
    _pokerWinnerFillRemaining(winning);
    return;
  }

  // High card
  winning->setSize = 0;
  _pokerWinnerFillRemaining(winning);
  winning->type = POKER_WINNNIG_TYPE_HIGH_CARD;

  return;
}

void _pokerWinnerFillRemaining(pokerplayerwinning_t *winning) {
  uint8_t i, highest, current;
  card_t highestCard, currentCard;

  // Set the kicker
  winning->kicker = 0xFF;

  // Fill the remaining cards
  while(winning->setSize < POKER_WINNING_SET_SIZE) {
    highest = 0xFF;

    for(i = 0; i < winning->fullSize; i++) {
      currentCard = winning->full[i];
      if(cardContains(winning->set, winning->setSize, currentCard) != -1) {
        continue;
      }

      if(highest == 0xFF) {
        highestCard = currentCard;
        highest = cardGetNumber(highestCard);
      } else {
        current = cardGetNumber(currentCard);
        if(current != CARD_ACE && current < highest) continue;
        highestCard = currentCard;
        highest = current;
      }
    }

    if(highest == 0xFF) break;
    winning->set[winning->setSize++] = highestCard;
  }
  cardHandSort(winning->set, winning->setSize);
}

card_t pokerWinnerCompare(pokerplayerwinning_t *left, pokerplayerwinning_t *right) {
  uint8_t i, number;
  card_t card;
  int32_t index;
  uint8_t countCardsSame;

  card_t highCardLeft, highCardRight;
  uint8_t highNumberLeft, highNumberRight;

  highNumberLeft = 0xFF;
  highNumberRight = 0xFF;
  countCardsSame = 0;


  for(i = 0; i < left->setSize; i++) {
    card = left->set[i];
    number = cardGetNumber(card);
    if(highNumberLeft != 0xFF && number < highNumberLeft) continue;// Quick check

    // Check if this number is within the other hand or not
    index = cardContainsNumber(right->set, right->setSize, number);
    if(index != -1) {
      // This number IS within the other hand, let's check that the EXACT card
      // is a match/isn't a match.
      index = cardContains(right->set, right->setSize, card);

      // Exact card match
      if(index != -1) {
        countCardsSame++;
        continue;
      }
      // Not exact card match.. ?
    }

    if(highNumberLeft == 0xFF||number == CARD_ACE||highNumberLeft < number) {
      highNumberLeft = number;
      highCardLeft = card;
    }
  }
  
  for(i = 0; i < right->setSize; i++) {
    card = right->set[i];
    number = cardGetNumber(card);
    if(highNumberRight != 0xFF && number < highNumberRight) continue;
    
    index = cardContainsNumber(left->set, left->setSize, number);
    if(index != -1) {
      index = cardContains(left->set, left->setSize, card);
      if(index != -1) continue;
    }

    if(highNumberRight == 0xFF||number == CARD_ACE||highNumberRight < number) {
      highNumberRight = number;
      highCardRight = card;
    }
  }


  if(countCardsSame == left->setSize) {
    for(i = 0; i < left->setSize; i++) {
      card = left->set[i];
      number = cardGetNumber(card);
      if(highNumberLeft == 0xFF||number == CARD_ACE||highNumberLeft < number) {
        highNumberLeft = number;
        highCardLeft = card;
      }
    }
    return highCardLeft;
  }

  if(highCardLeft == 0xFF) return 0xFF;
  if(highNumberLeft < highNumberRight) return 0xFF;
  return highCardLeft;//Greater or Equal to.
}

void pokerWinnerCalculate(
  pokerwinner_t *winner, pokerdealer_t *dealer, pokerplayer_t *players
) {
  uint8_t i, j, number, highNumber;
  pokerplayerwinning_t *left, *right;
  pokerplayer_t *player;
  card_t card, highCard;
  bool isWinner;

  winner->winnerCount = 0;
  highCard = 0xFF;

  // Get winning sets
  for(i = 0; i < POKER_PLAYER_COUNT; i++) {
    left = winner->winnings + i;
    left->type = POKER_WINNING_TYPE_NULL;
    player = players + i;
    if(!pokerPlayerIsInRound(player)) continue;

    // Get the players' winning state.
    pokerWinnerPlayerGet(dealer, player, left);
  }

  // Compare against each player
  for(i = 0; i < POKER_PLAYER_COUNT; i++) {
    left = winner->winnings + i;
    if(left->type == POKER_WINNING_TYPE_NULL) continue;

    isWinner = true;
    highNumber = 0xFF;

    for(j = 0; j < POKER_PLAYER_COUNT; j++) {
      if(i == j) continue;
      right = winner->winnings + j;
      if(right->type == POKER_WINNING_TYPE_NULL) continue;

      // Am I the better hand / Is it the better hand?
      if(left->type < right->type) continue;
      if(left->type > right->type) {
        isWinner = false;
        break;
      }

      // Equal, compare hands.
      card = pokerWinnerCompare(left, right);
      if(card == 0xFF) {
        isWinner = false;
        break;
      }

      // Determine high card.
      number = cardGetNumber(card);
      if(highNumber == 0xFF || number == CARD_ACE || number > highNumber) {
        highCard = card;
        highNumber = number;
      }
    }

    if(!isWinner) continue;
    left->kicker = highCard;
    winner->winners[winner->winnerCount++] = i;
  }
}

card_t pokerWinnerGetBestCard(card_t *cards, uint8_t cardCount) {
  uint8_t i, number, bestNumber;
  card_t card, bestCard;

  bestNumber = 0xFF;

  for(i = 0; i < cardCount; i++) {
    card = cards[i];
    number = cardGetNumber(card);
    if(number == CARD_ACE) return card;
    if(bestNumber != 0xFF && number <= bestNumber) continue;

    bestCard = card;
    bestNumber = number;
  }

  return bestNumber;
}

float pokerWinnerGetTypeConfidence(uint8_t type) {
  switch(type) {
    case POKER_WINNING_TYPE_ROYAL_FLUSH:
      return POKER_WINNNIG_CONFIDENCE_ROYAL_FLUSH;
    case POKER_WINNING_TYPE_STRAIGHT_FLUSH:
      return POKER_WINNNIG_CONFIDENCE_STRAIGHT_FLUSH;
    case POKER_WINNING_TYPE_FOUR_OF_A_KIND:
      return POKER_WINNNIG_CONFIDENCE_FOUR_OF_A_KIND;
    case POKER_WINNING_TYPE_FULL_HOUSE:
      return POKER_WINNNIG_CONFIDENCE_FULL_HOUSE;
    case POKER_WINNING_TYPE_FLUSH:
      return POKER_WINNNIG_CONFIDENCE_FLUSH;
    case POKER_WINNING_TYPE_STRAIGHT:
      return POKER_WINNNIG_CONFIDENCE_STRAIGHT;
    case POKER_WINNING_TYPE_THREE_OF_A_KIND:
      return POKER_WINNNIG_CONFIDENCE_THREE_OF_A_KIND;
    case POKER_WINNING_TYPE_TWO_PAIR:
      return POKER_WINNNIG_CONFIDENCE_TWO_PAIR;
    case POKER_WINNING_TYPE_PAIR:
      return POKER_WINNNIG_CONFIDENCE_PAIR;
    default:
      return POKER_WINNNIG_CONFIDENCE_HIGH_CARD;
  }
}

float pokerWinnerGetCardWeight(card_t card) {
  uint8_t number;
  number = cardGetNumber(card);
  return ((float)number + 1)/((float)CARD_ACE + 1);
}