/*
   * @see betting_center.IBettingCenter_Spectator#goCollectTheGains(int)
   */
  @Override
  public double goCollectTheGains(int spectatorID) {
    lock.lock();
    fileLogger.log(
        CLASS_TAG, "goCollectTheGains", FunctionState.START, "spectatorID:" + spectatorID);

    while (!betManagerIsHonouringBets) waitForCondition(betManagerIsHonouringBetsCondition);
    betManagerIsHonouringBets = false;

    spectatorCollectingGain = true;
    spectatorToHonourBetID = spectatorID;
    spectatorCollectingGainConditon.signal();

    while (!betManagerHasHonourBet) waitForCondition(betManagerHasHonourBetCondition);
    betManagerHasHonourBet = false;
    fileLogger.log(CLASS_TAG, "goCollectTheGains", FunctionState.END, "spectatorID:" + spectatorID);
    lock.unlock();
    return honourBet;
  }
  /*
   * @see betting_center.IBettingCenter_Spectator#placeABet(int, int, int)
   */
  @Override
  public void placeABet(int spectatorID, int betValue, int horseToBetID) {
    lock.lock();

    fileLogger.log(
        CLASS_TAG,
        "placeABet",
        FunctionState.START,
        "Spectator:" + spectatorID + " betValue:" + betValue + " horseToBetID:" + horseToBetID);
    while (!betManagerAcceptingBets) waitForCondition(betManagerAcceptingBetsCondition);

    bet = new Bet(spectatorID, betValue, horseToBetID);
    spectatorsToBet--;
    spectatorPlaceABet = true;
    spectatorPlaceABetCondition.signal();

    fileLogger.log(
        CLASS_TAG,
        "placeABet",
        FunctionState.END,
        "Spectator:" + spectatorID + " betValue:" + betValue + " horseToBetID:" + horseToBetID);
    lock.unlock();
  }
  /*
   * @see betting_center.IBettingCenter_Manager#honourTheBets(int, java.util.TreeMap)
   */
  @Override
  public void honourTheBets(int numWinnerHorses, TreeMap<Integer, Bet> winnerSpectators) {
    lock.lock();
    fileLogger.log(
        CLASS_TAG,
        "honourTheBets",
        FunctionState.START,
        "numWinnerHorses:" + numWinnerHorses + " winnerSpectators:" + winnerSpectators);

    while (winnerSpectators.size() != 0) {

      betManagerIsHonouringBets = true;
      betManagerIsHonouringBetsCondition.signal();

      while (!spectatorCollectingGain) waitForCondition(spectatorCollectingGainConditon);
      spectatorCollectingGain = false;

      Bet bet = winnerSpectators.remove(spectatorToHonourBetID);

      // TODO: Multiply with horse odd value */
      honourBet = bet.getHorseWinOdd() * bet.getBetValue();

      betManagerHasHonourBet = true;
      betManagerHasHonourBetCondition.signal();
      fileLogger.log(
          CLASS_TAG,
          "honourTheBets",
          FunctionState.MIDDLE,
          " winnerSpectators:" + winnerSpectators);
    }
    fileLogger.log(
        CLASS_TAG,
        "honourTheBets",
        FunctionState.END,
        "numWinnerHorses:" + numWinnerHorses + " winnerSpectators:" + winnerSpectators);
    lock.unlock();
  }
/**
 * Classe representa o Centro de Apostas e por consequinte é por este monitor que passará todas as
 * acções relacionadas com apostas entre Espectadores e Gestor de Apostas.
 *
 * @author Lucas Lemos
 * @version 1.0
 */
public class BettingCenter extends Monitor
    implements IBettingCenter_Manager, IBettingCenter_Spectator {

  /** Variável para modo Debug. */
  private final FileLogger fileLogger = FileLogger.getLogger();
  /** Tag de identifação da classe. */
  private final String CLASS_TAG = getClass().getSimpleName();

  /** Lock a ser usado para as várias situações de bloqueio. */
  final Lock lock = new ReentrantLock();
  /** Condição de bloqueio à espera que o Gestor de Apostas aceite as apostas. */
  final Condition betManagerAcceptingBetsCondition = lock.newCondition();
  /** Condição de bloqueio à espera que um espetador coloque uma aposta. */
  final Condition spectatorPlaceABetCondition = lock.newCondition();
  /** Condição de bloqueio à espera que o gestor de apostas apareça para pagar as apostas. */
  final Condition betManagerIsHonouringBetsCondition = lock.newCondition();
  /** Condição de bloqueio à espera que um espetador apareça para receber o que ganhou da aposta. */
  final Condition spectatorCollectingGainConditon = lock.newCondition();
  /** Condição de bloqueio à espera que o gestor de apostas pague as apostas. */
  final Condition betManagerHasHonourBetCondition = lock.newCondition();
  /** Boolean indicando se algum espetador fez uma aposta. */
  private boolean spectatorPlaceABet;
  /** Número de espetadores que faltam apostar. */
  private int spectatorsToBet;
  /** Aposta atual a ser recolhida pelo gestor de apostas. */
  private Bet bet;
  /** Boolean indicando se gestor de apostas está aceitando apostas. */
  private boolean betManagerAcceptingBets;
  /** Boolean indicando se gestor de apostas está pagando as apostas vencedoras. */
  private boolean betManagerIsHonouringBets;
  /** Valor a ser recolhido pelo espetador da aposta ganha. */
  private double honourBet;
  /** Boolean indicando se espetador está coletando o valor da aposta ganha. */
  private boolean spectatorCollectingGain;
  /** ID do espetador a recolher o valor da aposta ganha. */
  private int spectatorToHonourBetID;
  /** Boolean indicando se gestor de apostas já pagou o valor ganho da aposta. */
  private boolean betManagerHasHonourBet;
  /** Número total de espetadores na solução. */
  private int numSpectators;

  /**
   * Construtor do Betting Center.
   *
   * @param numSpectators Número total de espetadores.
   */
  public BettingCenter(int numSpectators) {
    this.spectatorsToBet = numSpectators;
    this.numSpectators = numSpectators;
  }

  /*
   * @see betting_center.IBettingCenter_Spectator#placeABet(int, int, int)
   */
  @Override
  public void placeABet(int spectatorID, int betValue, int horseToBetID) {
    lock.lock();

    fileLogger.log(
        CLASS_TAG,
        "placeABet",
        FunctionState.START,
        "Spectator:" + spectatorID + " betValue:" + betValue + " horseToBetID:" + horseToBetID);
    while (!betManagerAcceptingBets) waitForCondition(betManagerAcceptingBetsCondition);

    bet = new Bet(spectatorID, betValue, horseToBetID);
    spectatorsToBet--;
    spectatorPlaceABet = true;
    spectatorPlaceABetCondition.signal();

    fileLogger.log(
        CLASS_TAG,
        "placeABet",
        FunctionState.END,
        "Spectator:" + spectatorID + " betValue:" + betValue + " horseToBetID:" + horseToBetID);
    lock.unlock();
  }

  /*
   * @see betting_center.IBettingCenter_Spectator#goCollectTheGains(int)
   */
  @Override
  public double goCollectTheGains(int spectatorID) {
    lock.lock();
    fileLogger.log(
        CLASS_TAG, "goCollectTheGains", FunctionState.START, "spectatorID:" + spectatorID);

    while (!betManagerIsHonouringBets) waitForCondition(betManagerIsHonouringBetsCondition);
    betManagerIsHonouringBets = false;

    spectatorCollectingGain = true;
    spectatorToHonourBetID = spectatorID;
    spectatorCollectingGainConditon.signal();

    while (!betManagerHasHonourBet) waitForCondition(betManagerHasHonourBetCondition);
    betManagerHasHonourBet = false;
    fileLogger.log(CLASS_TAG, "goCollectTheGains", FunctionState.END, "spectatorID:" + spectatorID);
    lock.unlock();
    return honourBet;
  }

  /*
   * @see betting_center.IBettingCenter_Manager#acceptTheBets()
   */
  @Override
  public Bet acceptTheBets() {
    lock.lock();

    betManagerAcceptingBets = true;
    betManagerAcceptingBetsCondition.signal();
    while (!spectatorPlaceABet) waitForCondition(spectatorPlaceABetCondition);

    betManagerAcceptingBets = false;
    spectatorPlaceABet = false;
    lock.unlock();
    return bet;
  }
  /*
   * @see betting_center.IBettingCenter_Manager#honourTheBets(int, java.util.TreeMap)
   */
  @Override
  public void honourTheBets(int numWinnerHorses, TreeMap<Integer, Bet> winnerSpectators) {
    lock.lock();
    fileLogger.log(
        CLASS_TAG,
        "honourTheBets",
        FunctionState.START,
        "numWinnerHorses:" + numWinnerHorses + " winnerSpectators:" + winnerSpectators);

    while (winnerSpectators.size() != 0) {

      betManagerIsHonouringBets = true;
      betManagerIsHonouringBetsCondition.signal();

      while (!spectatorCollectingGain) waitForCondition(spectatorCollectingGainConditon);
      spectatorCollectingGain = false;

      Bet bet = winnerSpectators.remove(spectatorToHonourBetID);

      // TODO: Multiply with horse odd value */
      honourBet = bet.getHorseWinOdd() * bet.getBetValue();

      betManagerHasHonourBet = true;
      betManagerHasHonourBetCondition.signal();
      fileLogger.log(
          CLASS_TAG,
          "honourTheBets",
          FunctionState.MIDDLE,
          " winnerSpectators:" + winnerSpectators);
    }
    fileLogger.log(
        CLASS_TAG,
        "honourTheBets",
        FunctionState.END,
        "numWinnerHorses:" + numWinnerHorses + " winnerSpectators:" + winnerSpectators);
    lock.unlock();
  }
  /*
   * @see betting_center.IBettingCenter_Manager#isFinalBetter()
   */
  @Override
  public boolean isFinalBetter() {
    boolean isFinalBetter = (spectatorsToBet == 0);
    if (isFinalBetter) spectatorsToBet = numSpectators;
    return isFinalBetter;
  }
}