sagas/round.js

import {
  takeEvery, put, call, select, takeLeading, takeLatest,
} from 'redux-saga/effects';
/**
 * @namespace sagas/round
 */
import Constants, { GAME_TYPE } from '../constants';
import { requestGet } from '../utils/request';
import {
  delay,
  roundSetupTimers,
  transformStandingsPerTeam,
  transformStatisticsPerTeamLast5,
  filterSelectedOdds,
} from '../utils/common';

import {
  GET_ROUND_SELECTIONS,
  REPEAT_CALL_SUCCESS,
  FETCH_ROUND_SUCCESS,
  SET_ROUND_LOADER,
  REPEAT_CALL,
  GET_ROUND_SUCCESS,
  FETCH_ROUND,
  GET_ROUND,
  SHOW_ERROR_MODAL,
  SET_NUM_OF_BETS,
  GET_PLACED_BETS,
  GET_TURBO_ROUND_SELECTIONS,
  WAKE_UP_APP,
  FORCE_BALANCE_UPDATE,
  GET_BALANCE,
  SET_LAST_BET_ROUND,
  CLEAR_SELECTIONS,
  CLOSE_BETSLIP,
  FETCH_STATISTICS,
  SET_STATISTICS,
  LIVE_FINISHED,
  CLEAR_STATISTICS,
  SET_FILTERED_SELECTED_ODDS,
} from '../reducers';

import {
  getUser,
  getCurrentPage,
  getLostFocus,
  getPlacebetBackupLogin,
} from '../store/common';
import { getNumberOfBets } from '../store/tickets';
import {
  getRepeatRoundCall, getNextRound, getInitialBetsLoaded,
  getNoBettingTimeCountdown, getRoundEnd,
  getSelectedLeague, getStatistics, getGameType, getTurboRoundId, getRoundId,
} from '../store/bets';
import {
  getLastBetRound, getSelectedOdds,
} from '../store/placebet';

/**
 * Repeat Round API every time when receive RESULTS_NOT_READY code or old data.
 * Repeat this 5 time maximum.
 *
 * @memberof sagas/round
 * @param {object} action
 * @property {object} action
 * @property {object} action.payload
 * @property {boolean} action.payload.showNoBetTime
 *
 * @yields {select} getRepeatRoundCall
 * @yields {put} REPEAT_CALL_SUCCESS
 * @yields {call} delay 1000
 * @yields {put} GET_ROUND
 * @yields {put} SHOW_ERROR_MODAL
 * @returns {void}
 */
function* repeatCall(action) {
  const { payload } = action;
  try {
    let repeatRoundCall = yield select(getRepeatRoundCall);
    repeatRoundCall += 1;
    payload.repeatRoundCall = repeatRoundCall;

    yield put({
      type: REPEAT_CALL_SUCCESS,
      payload,
    });
    if (repeatRoundCall < 5) {
      yield call(delay, 1000);
      yield put({
        type: GET_ROUND,
      });
    } else {
      // showGeneralError();
    }
  } catch (error) {
    yield put({
      type: SHOW_ERROR_MODAL,
      payload: {
        activeModal: error.modal || Constants.MODALS.GENERAL_ERROR,
        message: error.message,
      },
    });
  }
}

/**
 * Call when Live start, setup new data from round call
 *
 * @memberof sagas/round
 * @param {object} action
 * @property {object} action
 * @property {object} action.payload
 * @property {boolean} action.payload.isNoBetTime
 * @property {number} action.payload.leftTime
 *
 * @yields {call} delay leftTime
 * @yields {select} getNextRound
 * @yields {select} getCurrentPage
 * @yields {select} getNumberOfBets
 * @yields {put} FETCH_ROUND_SUCCESS
 * @yields {put} SET_NUM_OF_BETS
 * @yields {put} SHOW_ERROR_MODAL
 * @returns {void}
 */
function* resetFetchingResults(action) {
  try {
    if (action.payload.isNoBetTime) {
      // One second before to setup live
      yield call(delay, (action.payload.leftTime - 1) * 1000);
      const placebetBackupLogin = yield select(getPlacebetBackupLogin);
      const nextRound = yield select(getNextRound);
      const currentPage = yield select(getCurrentPage);
      const numberOfBets = placebetBackupLogin ? 0 : yield select(getNumberOfBets);

      yield put({
        type: FETCH_ROUND_SUCCESS,
        payload: {
          roundId: nextRound && nextRound.roundId,
          roundEnd: nextRound && nextRound.roundEnd,
          currentPage:
            currentPage === Constants.PAGES.BETS || currentPage === Constants.PAGES.BETSLIP
              ? Constants.PAGES.LIVE
              : currentPage,
          haveLiveMatches: true,
        },
      });

      yield put({
        type: SET_NUM_OF_BETS,
        payload: {
          numberOfBetsLive: numberOfBets,
        },
      });
    }
  } catch (error) {
    yield put({
      type: SHOW_ERROR_MODAL,
      payload: {
        activeModal: error.modal || Constants.MODALS.GENERAL_ERROR,
        message: error.message,
      },
    });
  }
}
/**
 * @memberof sagas/round
 * @typedef "getRound/response.data"
 * @type {object}
 * @property {Round} round Round for betting
 * @property {Live} roundResults Round in play (LIVE)
 */
/**
 * @memberof sagas/round
 * @typedef Round
 * @type {object}
 * @property {number} roundId Round ID
 * @property {Array} competitions Sorted IDs of leagues
 * @property {Array} seasons Sorted IDs of Seasons
 * @property {Array} weeks Sorted IDs of weeks
 * @property {Time} startDate Round start time
 * @property {Time} endDate Round end time
 * @property {Array} matches Names of competitors in matches per leagues
 * @property {string} selections IDs of all selections included in round separated with "-"
 * @property {Array} odds Odds per league (Matches separated with "|" and selections with "-")
 * @property {Time} noBetDate Not in use
 * @property {object} stake Not in use
 * @property {number} bonusOddsThreshold Config parameter for SystemMenager library
 * @property {string} bonusPercentages Config parameter for SystemMenager library
 * @property {number} winCap Maximum winning payout
 */

/**
 * @memberof sagas/round
 * @typedef Live
 * @type {object}
 * @property {number} roundId Live round ID
 * @property {Array} competitions Sorted IDs of leagues
 * @property {Array} seasons Sorted IDs of Seasons
 * @property {Array} weeks Sorted IDs of weeks
 * @property {Time} liveStartDate Live round start time
 * @property {Time} liveEndDate Live round end time
 * @property {Array} matches Names of competitors in matches per leagues
 * @property {Array} timeline Parsed timeline per league.
 * (minute:competitorIndex which get +1 score separated with "-")
 */
/**
 * Call in the time of No Betting Time to get data for next round and to get timeline for Live
 *
 * @memberof sagas/round
 * @param {object} action
 * @property {object} action
 * @async
 * @yields {select} getInitialBetsLoaded
 * @yields {put} SET_ROUND_LOADER
 * @yields {call} /game/round
 * @yields {put} REPEAT_CALL
 * @yields {select} getNoBettingTimeCountdown
 * @yields {select} getLiveMatchDuration
 * @yields {put} REPEAT_CALL
 * @yields {put} GET_PLACED_BETS
 * @yields {select} getCurrentPage
 * @yields {put} GET_ROUND_SUCCESS
 * @yields {put} FETCH_ROUND
 * @yields {put} SHOW_ERROR_MODAL
 */
function* getRound(action) {
  let response;
  try {
    const lostFocus = yield select(getLostFocus);
    if (lostFocus) {
      const now = new Date();
      const dif = now - lostFocus;
      const mins = parseInt(dif / (1000 * 60));
      if (mins >= 15) {
        return;
      }
    }

    const initialBetsLoaded = yield select(getInitialBetsLoaded);

    yield put({
      type: SET_ROUND_LOADER,
      payload: {
        roundLoading: true,
      },
    });

    response = yield call(requestGet, Constants.API_URLS.ROUND, true);
    if (response.status === -1) {
      if (response.error.code === Constants.ERROR.RESULTS_NOT_READY
        || response.error.code === Constants.ERROR.NEW_ROUND_NOT_READY) {
        yield put({
          type: REPEAT_CALL,
          payload: {
            showNoBetTime: !initialBetsLoaded,
          },
        });
      } else {
        throw new Error(response.error.message);
      }
    } else {
      const noBettingTimeCountdown = yield select(getNoBettingTimeCountdown);
      const liveMatchDuration = response?.data?.roundResults?.liveRoundDuration;

      const {
        oldData, haveLiveMatches, isNoBetTime, liveStart, roundEnd, leftTime,
      } = roundSetupTimers(
        response.data.round.endDate,
        response.data.roundResults.liveStartDate,
        liveMatchDuration,
        noBettingTimeCountdown,
      );
      if (oldData) {
        yield put({
          type: REPEAT_CALL,
          payload: {
            showNoBetTime: !initialBetsLoaded,
          },
        });
        return;
      }

      if (!initialBetsLoaded) {
        yield put({
          type: GET_PLACED_BETS,
          payload: {
            haveLiveMatches,
            isNoBetTime,
            liveId: response.data.roundResults.roundId,
            roundId: response.data.round.roundId,
            turbo: false,
          },
        });
      }

      let currentPage = yield select(getCurrentPage);

      if (currentPage !== Constants.PAGES.MY_ACCOUNT_MENU) {
        currentPage = haveLiveMatches
          ? Constants.PAGES.LIVE
          : yield select(getCurrentPage);
      }

      // go to live page if user come in the middle of the live

      // check for open bets
      const lastBetRound = yield select(getLastBetRound);
      if (lastBetRound && parseInt(lastBetRound) !== parseInt(response.data.round.roundId)) {
        if (parseInt(lastBetRound) === parseInt(response.data?.roundResults?.roundId)) {
          const liveEndDate = new Date(response.data.roundResults.liveEndDate);
          const currentDate = new Date();

          const timeDiff = parseInt((liveEndDate - currentDate) / 1000);

          const waitTime = timeDiff < 0 ? 0 : timeDiff + Math.floor(Math.random() * 5) + 5;
          yield put({
            type: FORCE_BALANCE_UPDATE,
            payload: waitTime,
          });
        } else {
          yield put({
            type: FORCE_BALANCE_UPDATE,
            payload: 0,
          });
        }
      }

      const liveEndDate = new Date(response.data.roundResults.liveEndDate);
      const currentDate = new Date();

      const timeDiff = parseInt((liveEndDate - currentDate) / 1000);

      let isThereLiveATM = haveLiveMatches;

      if (timeDiff > 0) {
        isThereLiveATM = true;
      }

      if (haveLiveMatches) {
        currentPage = Constants.PAGES.MY_LIVE;
      }

      const selectedOdds = yield select(getSelectedOdds);
      const roundId = response.data?.round?.roundId;
      const filteredSelectedOdds = filterSelectedOdds(selectedOdds, roundId);

      yield put({
        type: SET_FILTERED_SELECTED_ODDS,
        payload: {
          selectedOdds: filteredSelectedOdds,
        },
      });

      yield put({
        type: GET_ROUND_SUCCESS,
        payload: {
          haveLiveMatches: isThereLiveATM,
          round: response.data.round,
          roundResults: response.data.roundResults,
          winCap: response.data.round.winCap,
          initialBetsLoaded,
          isNoBetTime,
          liveStart,
          roundEnd,
          bonusOddsThreshold: response.data.round.bonusOddsThreshold,
          bonusPercentages: response.data.round.bonusPercentages.split('-'),
          currentPage,
          placebetBackupLogin: action.payload?.placebetBackupLogin || false,
          isSelectLeague: action.payload?.leagueSelection || 0,
        },
      });

      if (!action.payload?.leagueSelection || action.payload?.haveGameTypeChanged) {
        yield put({
          type: CLEAR_SELECTIONS,
        });
        yield put({
          type: CLOSE_BETSLIP,
        });
      }

      yield put({
        type: FETCH_ROUND,
        payload: {
          isNoBetTime,
          leftTime,
        },
      });
    }
  } catch (error) {
    yield put({
      type: SHOW_ERROR_MODAL,
      payload: {
        activeModal: error.modal || Constants.MODALS.GENERAL_ERROR,
        message: error.message,
      },
    });
  }
}

/**
 * Get placed bets for MY_LIVE page
 *
 * @memberof sagas/round
 * @async
 * @param {object} action
 * @property {object} action
 * @property {object} action.payload
 * @property {boolean} action.payload.haveLiveMatches
 * @property {boolean} action.payload.isNoBetTime
 * @property {number} action.payload.liveId
 * @property {number} action.payload.roundId
 *
 * @yields {select} getUser
 * @yields {put} GET_ROUND_SELECTIONS
 * @returns {void}
 */
function* getPlacedBets(action) {
  const user = yield select(getUser);
  if (user && action.payload.roundId) {
    if (action.payload.haveLiveMatches || action.payload.isNoBetTime) {
      yield put({
        type: action.payload.turbo ? GET_TURBO_ROUND_SELECTIONS : GET_ROUND_SELECTIONS,
        payload: {
          roundId: action.payload.liveId,
          live: true,
        },
      });
    } else {
      yield put({
        type: action.payload.turbo ? GET_TURBO_ROUND_SELECTIONS : GET_ROUND_SELECTIONS,
        payload: {
          roundId: action.payload.roundId,
        },
      });
    }
  }
}

/**
 * Check if app needs to be awoken
 *
 * @memberof sagas/round
 * @async
 * @returns {void}
 */
function* checkIfWakeUpNeeded() {
  const roundEnd = yield select(getRoundEnd);
  if (roundEnd && roundEnd < new Date()) {
    yield put({
      type: GET_ROUND,
      payload: {},
    });
  }
}

/**
 * Call balance update after action.payload seconds
 *
 * @memberof sagas/round
 * @param {object} action
 * @async
 * @returns {void}
 */
function* forceUpdateBalance(action) {
  if (action.payload) {
    yield call(delay, 1000 * action.payload);
  }
  yield put({
    type: GET_BALANCE,
    payload: {},
  });
  yield put({
    type: SET_LAST_BET_ROUND,
    payload: null,
  });
}

/**
 * Fetch league statistics
 *
 * @memberof sagas/round
 * @param {object} action
 * @async
 * @returns {void}
 */
function* fetchStatistics(action) {
  try {
    const apiUrl = action.payload.gameType === 'Turbo' ? Constants.API_URLS.STATISTICS_TURBO
      : Constants.API_URLS.STATISTICS;

    const { leagueId, roundId } = action.payload;
    const response = yield call(requestGet, apiUrl + leagueId, true);
    if (response.status === -1) {
      throw new Error(response.error.message);
    } else {
      const { standings, statistics } = response.data;

      const newStandings = transformStandingsPerTeam(standings[0].value);
      const last5Matches = transformStatisticsPerTeamLast5(statistics);
      yield put({
        type: SET_STATISTICS,
        payload: {
          roundId,
          leagueId,
          statistics: { standings: newStandings, last5: last5Matches },
          clearOther: action.payload.clearOther,
        },
      });
    }
  } catch (error) {
    yield put({
      type: SHOW_ERROR_MODAL,
      payload: {
        activeModal: error.modal || Constants.MODALS.GENERAL_ERROR,
        message: error.message,
      },
    });
  }
}

/**
 * Fetch league statistics
 *
 * @memberof sagas/round
 * @async
 * @returns {void}
 */
function* tryUpdateStatistics() {
  const currentPage = yield select(getCurrentPage);
  if (currentPage === Constants.PAGES.BETS) {
    const gameType = yield select(getGameType);
    const currentLeague = yield select(getSelectedLeague);
    const currentLeagueId = currentLeague.id;
    const currentStatistics = yield select(getStatistics);
    const roundId = gameType === GAME_TYPE.CLASSIC
      ? yield select(getRoundId) : yield select(getTurboRoundId);

    const shouldGetNewStatistics = currentStatistics[roundId]
      && currentStatistics[roundId][currentLeagueId] !== undefined;

    if (shouldGetNewStatistics) {
      yield put({
        type: FETCH_STATISTICS,
        payload: {
          gameType,
          roundId,
          leagueId: currentLeagueId,
          clearOther: true,
        },
      });
    } else {
      yield put({ type: CLEAR_STATISTICS });
    }
  } else {
    yield put({ type: CLEAR_STATISTICS });
  }
}

/**
 * @memberof sagas/round
 * @async
 * @returns {void}
 */
export default function* roundSaga() {
  yield takeEvery(REPEAT_CALL, repeatCall);
  yield takeEvery(FETCH_ROUND, resetFetchingResults);
  yield takeEvery(GET_ROUND, getRound);
  yield takeEvery(GET_PLACED_BETS, getPlacedBets);
  yield takeEvery(WAKE_UP_APP, checkIfWakeUpNeeded);
  yield takeLatest(FORCE_BALANCE_UPDATE, forceUpdateBalance);
  yield takeLeading(FETCH_STATISTICS, fetchStatistics);
  yield takeLatest(LIVE_FINISHED, tryUpdateStatistics);
}