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

#include "turn.h"

pokerturn_t pokerTurnFold(poker_t *poker, uint8_t player) {
  return (pokerturn_t){
    .chips = 0,
    .confidence = 1,
    .type = POKER_TURN_TYPE_FOLD
  };
}

pokerturn_t pokerTurnBet(poker_t *poker, uint8_t playerIndex, int32_t chips) {
  pokerturn_t turn;
  pokerplayer_t *player;
  int32_t i;
  player = poker->players + playerIndex;
  turn.confidence = 1;

  if(chips == 0) {
    turn.type = POKER_TURN_TYPE_CHECK;
    turn.chips = 0;
  } else if(player->chips <= chips) {
    turn.chips = player->chips;
    turn.type = POKER_TURN_TYPE_ALL_IN;
  } else {
    turn.chips = chips;
    turn.type = POKER_TURN_TYPE_BET;
    i = pokerBetGetCurrentCallValue(poker);

    if(chips == (i - player->currentBet)) {
      turn.type = POKER_TURN_TYPE_CALL;
    }
  }

  return turn;
}

pokerturn_t pokerTurnGetForPlayer(poker_t *poker, uint8_t playerIndex) {
  int32_t random, maxBet, bluffBet, callBet;
  float confidence, expectedGain, potOdds;
  bool isBluff;
  int32_t amount;
  pokerplayer_t *player;
  pokerplayerwinning_t winning;
  uint8_t i, cardNumber0, cardNumber1, suitNumber0, suitNumber1;
  pokerturn_t turn;

  player = poker->players + playerIndex;

  // Can the player do anything?
  if(player->state & (POKER_PLAYER_STATE_FOLDED | POKER_PLAYER_STATE_OUT)) {
    turn.type = POKER_TURN_TYPE_OUT;
    return turn;
  }

  // The following logic is heavily inspired by;
  // https://github.com/gorel/C-Poker-AI/blob/master/src/common/pokerai.c
  // But with some changes and smarts added by me. The original source code will
  // essentially just run a crap tun of simulated games and get the times that
  // they are expected to win from those games, but I'm just going to use the
  // odds of the winning hand.

  // Is this preflop?
  if(poker->communitySize == 0 && player->cardCount >= 2) {
    // Get the hand weight
    cardNumber0 = cardGetNumber(player->cards[0]);
    cardNumber1 = cardGetNumber(player->cards[1]);
    suitNumber0 = cardGetSuit(player->cards[0]);
    suitNumber1 = cardGetSuit(player->cards[1]);

    // Get delta between cards
    i = mathAbs(cardNumber0 - cardNumber1);

    // Get card weight
    confidence = (float)cardNumber0 + (float)cardNumber1;
    if(cardNumber0 == cardNumber1) {// Pairs
      confidence += 6;
    } else if(suitNumber0 == suitNumber1) {// Same suit
      confidence += 4;
    }

    // Get difference from cards for guessing flush
    if(i > 4) {
      confidence -= 4;
    } else if(i > 2) {
      confidence -= i;
    }

    // Get the confidence delta 0-1
    confidence = confidence / 30.0f;
  } else {
    // Simulate my hand being the winning hand, use that as the confidence
    pokerWinnerGetForPlayer(poker, player, &winning);
    confidence = pokerWinnerGetTypeConfidence(winning.type);
  }

  // Now we know how confident the AI is, let's put a chip value to that weight
  // How many chips to call?
  callBet = pokerPlayerGetCallBet(poker, player);

  // Do they need chips to call, or is it possible to check?
  if(callBet > 0) {
    potOdds = (float)callBet / (
      (float)callBet + (float)pokerPotGetSumOfChipsForPlayer(poker, playerIndex)
    );
  } else {
    potOdds = 1.0f / (float)pokerBetGetRemainingBetterCount(poker);
  }

  // Now determine the expected ROI
  expectedGain = confidence / potOdds;

  // Now get a random 0-100
  random = randInt32() % 100;

  // Determine the max bet that the AI is willing to make
  maxBet = (int32_t)((float)player->chips / 1.75f) - (random / 2);
  maxBet -= callBet;

  // Determine what's a good bluff bet.
  bluffBet = random * maxBet / 100 / 2;

  // Now prep the output
  isBluff = false;
  amount = 0;

  // Now the actual AI can happen. This is basically a weight to confidence
  // ratio. The higher the gains and the confidence then the more likely the AI
  // is to betting. There are also bluff chances within here.
  if(expectedGain < 0.8 && confidence < 0.8) {
    if(random < 95) {
      amount = 0;
    } else {
      amount = bluffBet;
      isBluff = true;
    }
  } else if ((expectedGain < 1.0 && confidence < 0.85) || confidence < 0.1) {
    if (random < 80) {
      amount = 0;
    } else if(random < 5) {
      amount = callBet;
      isBluff = true;
    } else {
      amount = bluffBet;
      isBluff = true;
    }
  } else if ((expectedGain < 1.3 && confidence < 0.9) || confidence < 0.5) {
    if (random < 60 || confidence < 0.5) {
      amount = callBet;
    } else {
      amount = maxBet;
    }
  } else if (confidence < 0.95 || poker->communitySize < 0x04) {
    if(random < 20) {
      amount = callBet;
    } else {
      amount = maxBet;
    }
  } else {
    amount = (player->chips - callBet) * 9 / 10;
  }

  // If this is the first round... make it a lot less likely I'll bet
  if(poker->communitySize == 0x00 && amount > callBet) {
    if(random > 5) amount = callBet;
  }

  // Did we actually bet?
  if(amount > 0) {
    printf("AI is betting %i chips, bluff: %i\n", amount, isBluff);

    // Let's not get caught in a raising loop with AI.
    if(player->timesRaised >= POKER_TURN_MAX_RAISES) {
      amount = callBet;
    }

    amount = mathMax(amount, callBet);
    turn = pokerTurnBet(poker, playerIndex, amount);
    turn.confidence = confidence;
  } else if(pokerPlayerCanCheck(poker, player)) {
    turn = pokerTurnBet(poker, playerIndex, 0);
    turn.confidence = 1;
  } else {
    turn = pokerTurnFold(poker, playerIndex);
    turn.confidence = 1 - confidence;
  }

  return turn;
}

void pokerTurnAction(poker_t *poker, uint8_t playerIndex, pokerturn_t turn) {
  pokerplayer_t *player;
  player = poker->players + playerIndex;

  switch(turn.type) {
    case POKER_TURN_TYPE_BET:
    case POKER_TURN_TYPE_CALL:
    case POKER_TURN_TYPE_ALL_IN:
      pokerBetForPlayer(poker, playerIndex, turn.chips);
      player->timesRaised++;
      break;

    case POKER_TURN_TYPE_CHECK:
      pokerBetForPlayer(poker, playerIndex, 0);
      player->timesRaised = 0;
      break;

    case POKER_TURN_TYPE_FOLD:
      player->state |= POKER_PLAYER_STATE_FOLDED;
      player->timesRaised = 0;
      break;
  }
  
  player->state |= POKER_PLAYER_STATE_HAS_BET_THIS_ROUND;
}