utils/parser.js

/* eslint-disable no-param-reassign */
/**
 * @namespace utils/parsers
 */
import { newObjectRef, getSelectionName, calulatePlacedSelection } from './common';
import Constants, { COUNTRY_TIMEZONE, COUNTRY_OFFSET, ROUNDING_DECIMAL_VALUE } from '../constants';

/**
 * Adding index as market.id in markets array
 *
 * @memberof utils/parsers
 * @param {Array} markets
 * @param {number} correctScoreIndex
 * @returns {Array}
 */
export function parseMarkets(markets, correctScoreIndex) {
  return markets.map((m, _i) => {
    if (_i === correctScoreIndex) {
      m.correctScore = true;
    }
    m.enabled = 1; return m;
  });
}

/**
 * Parse odds
 *
 * @memberof utils/parsers
 * @param {Array} odds
 * @param {Array<string>} activeSelections
 * @returns {object}
 */
export function parseOdds(odds, activeSelections) {
  const odds4D = [];
  odds.forEach((oddsString, i) => {
    const selections = activeSelections[i].split('-');
    const oddsPerMatches = oddsString.split('|');
    const oddsPerMatch = [];
    oddsPerMatches.map((o) => {
      const matchOdds = o.split('-');
      const oddsPerSelections = {};
      matchOdds.map((odd, index) => {
        // check existation of selections[index]
        oddsPerSelections[selections[index]] = odd;
      });
      oddsPerMatch.push(oddsPerSelections);
    });
    odds4D.push(oddsPerMatch);
  });
  return odds4D;
}

/**
 * Parse matches
 *
 * @memberof utils/parsers
 * @param {string} matchesString
 * @returns {Array}
 */
export function parseMatches(matchesString) {
  const matches = matchesString.split ? matchesString.split('|') : [];
  return matches[0] !== '' ? matches : [];
}

/**
 * Get team names from match
 *
 * @memberof utils/parsers
 * @param {string} matchString
 * @returns {Array}
 */
export function parseCompetitors(matchString) {
  return matchString.split('-');
}

/**
 * Parse number as currency. For example 9,999,999
 *
 * @memberof utils/parsers
 * @function
 * @param {number} value
 * @returns {string}
 */
export const parseNum = (value) => (
  (value / 100).toFixed(0).replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1,')
);

/**
 * Round number up and add 0.000001 to avoid js errors
 *
 * @memberof utils/parsers
 * @function
 * @param {number} value
 * @returns {string}
 */
export const roundNum = (value) => (
  (Math.round(value + ROUNDING_DECIMAL_VALUE))
);

/**
 * Parse date in format DD/MM/YYYY, time in format hh:mm and seconds in format ss
 *
 * @memberof utils/parsers
 * @param {Time} dateString
 * @example 2020-04-27T09:18:02Z
 * parsed to {
 *  date: "27/04/2020"
 *  time: "09:18"
 *  seconds: "02"
 * }
 * @returns {object} Parsed date and time
 */
export function getDateTime(dateString) {
  const dateTime = dateString.split('T');
  const date = dateTime[0].split('-');
  const time = dateTime[1].replace('Z', '').split(':');
  return ({
    date: `${date[2]}/${date[1]}/${date[0]}`,
    time: `${time[0]}:${time[1]}`,
    seconds: time[2],
  });
}

/**
 * Parse date in format DD/MM/YYYY, time in format hh:mm:ss with selected country time zone
 *
 * @memberof utils/common
 * @param {Time} dateString
 * @example 2020-04-27T09:18:02Z
 * parsed to   "27/04/2020 09:18:02"
 * @returns {string} Parsed date and time
 */
export function getCountryDateTime(dateString) {
  if (dateString) {
    const timezone = COUNTRY_TIMEZONE[process.env.BUNDLE_SKIN];
    const offset = new Date().getTimezoneOffset();
    const localDateTime = new Date(dateString.replace(/z/gi, ''))
      .setMinutes(new Date(dateString).getMinutes() - offset);
    const dateTime = new Date(localDateTime).toLocaleString('en-GB', { timeZone: timezone });
    const dateTimeArr = dateTime.split(',');
    const date = dateTimeArr[0].split('/');
    const time = dateTimeArr[1].split(':');
    return ({
      date: `${date[2]}/${date[1]}/${date[0]}`,
      time: `${time[0]}:${time[1]}`,
      seconds: time[2],
    });
  }
  return '';
}

/**
 * get javascript date object in nigeria time
 *
 * @memberof utils/common
 * @param {string} country
 * @param {Time} date
 * @returns {string} Parsed date and time
 */
export function getCountryNewDate(country, date) {
  if (!country) {
    return '';
  }
  const offset = new Date().getTimezoneOffset();
  const countryOffset = offset + COUNTRY_OFFSET[country].OFFSET;
  const countryDate = date ? new Date(date) : new Date();
  return new Date(Date.UTC(countryDate.getUTCFullYear(),
    countryDate.getUTCMonth(), countryDate.getUTCDate(),
    countryDate.getUTCHours(), countryDate.getUTCMinutes()
  + countryOffset, countryDate.getUTCSeconds()));
}

/**
 * get javascript date object in country time from utc time
 *
 * @memberof utils/common
 * @param {string} countryOffset
 * @param {Time} date
 * @example 2020-04-27T09:18:02Z
 * parsed to   "27/04/2020 09:18:02"
 * @returns {string} Parsed date and time
 */
export function getCountryDateFromUTC(countryOffset, date) {
  return new Date(date)
    .setMinutes(new Date(date).getMinutes() + countryOffset);
}

/**
 * Parse League Table statistic
 *
 * @memberof utils/parsers
 * @param {string} teamsTableString
 * @returns {Array}
 */
export function parseTeamStatistic(teamsTableString) {
  const teamsArray = parseMatches(teamsTableString);
  const teams = [];
  teamsArray.map((team) => {
    const dataPerTeam = parseCompetitors(team);
    teams.push({
      name: dataPerTeam[0],
      points: dataPerTeam[1],
      lastFive: dataPerTeam[2].split(''),
    });
  });

  return teams;
}

/**
 * Parse placed bets.
 * One selection code skelet leagueId:matchIndex:selectionId:oddValue
 *
 * @memberof utils/parsers
 * @param {Array} selections
 * @param {Array} markets
 * @returns {object}
 */
export function getPlacedBetsResults(selections, markets) {
  if (selections.length === 0) return null;
  const placedSelections = {};
  selections.map((sel) => {
    const s = sel.split(':');
    const leagueId = s[0];
    const matchId = parseInt(s[1]) - 1;
    const selectionId = s[2];
    const odd = s[3];
    if (!placedSelections[leagueId]) {
      placedSelections[leagueId] = {};
    }
    if (!placedSelections[leagueId][matchId]) {
      placedSelections[leagueId][matchId] = [];
    }
    const selection = getSelectionName(selectionId, markets);
    selection.id = selectionId;
    if (placedSelections[leagueId][matchId].findIndex((e) => (
      e.selection.id === selectionId)) === -1) {
      placedSelections[leagueId][matchId].push({
        colorId: Constants.COLOR_IDS.LOSE,
        selection,
        odd,
      });
    }
  });
  return placedSelections;
}

/**
 * Adding new placed selection in object of placed selections
 *
 * @memberof utils/parsers
 * @param {object} currentSelections
 * @param {object} newPlacedSelections
 * @param {Array} markets
 * @returns {object}
 */
export function parsePlacedSelections(currentSelections, newPlacedSelections, markets) {
  const placedSelections = newPlacedSelections;
  const newPlacedBets = getPlacedBetsResults(currentSelections, markets);
  Object.keys(newPlacedBets).map((league) => {
    Object.keys(newPlacedBets[league]).map((match) => {
      if (placedSelections[league]) {
        if (placedSelections[league][match]) {
          let current = placedSelections[league][match];
          const unique = newPlacedBets[league][match].filter((e) => (
            current.findIndex((c) => c.selection.name === e.selection.name)) === -1);
          current = current.concat(unique);
          placedSelections[league][match] = current;
        } else {
          placedSelections[league][match] = newPlacedBets[league][match];
        }
      } else {
        placedSelections[league] = {
          [match]: newPlacedBets[league][match],
        };
      }
    });
  });
  return placedSelections;
}

/**
 * Parse timeline into minutes for one league
 *
 * @memberof utils/parsers
 * @param {string} timelineString
 * @returns {object}
 */
export function parseTimeline(timelineString) {
  const timelineArray = timelineString.split('|');
  const timeline = {};
  timelineArray.map((t) => {
    const minutes = t.split(':');
    timeline[minutes[0]] = minutes[1];
  });
  return timeline;
}

/**
 * Parse timeline into minutes for one league
 * 0: "0:0&3:1&0:6&1:1&1:0&0:0&1:2&1:2&2:1&1:0"
 *
 * @memberof utils/parsers
 * @param {string} timelineString
 * @returns {object}
 */
export function parseTurboTimeline(timelineString) {
  const timelineArray = timelineString.split('&');
  const timeline = [];
  timelineArray.map((t) => {
    const scores = t.split(':');
    timeline.push(scores[0]);
    timeline.push(scores[1]);
  });
  return timeline;
}

/**
 * Seting placed selection changes, scores and blink data
 *
 * @memberof utils/parsers
 * @param {string} minuteString
 * @param {number} leagueIndex
 * @param {boolean} showBlink
 * @param {object} placedSelectionsLive
 * @param {object} blink
 * @param {object} scores
 * @param {Array} blinksForRemove
 * @returns {object}
 */
export function parseTimelineResults(
  minuteString,
  leagueIndex,
  showBlink,
  placedSelectionsLive,
  blink,
  scores,
  blinksForRemove,
) {
  if (minuteString) {
    const teams = minuteString.split('-');
    for (let j = 0; j < teams.length; j += 1) {
      const teamObj = teams[j].split('^');
      const team = parseInt(teamObj[0]) - 1;
      let otherTeam;
      let score;
      const matchId = Math.floor(parseInt(team) / 2);

      if (team % 2 === 0) {
        otherTeam = team + 1;
        score = `${scores[leagueIndex][team] + 1}:${scores[leagueIndex][otherTeam]}`;
      } else {
        otherTeam = team - 1;
        score = `${scores[leagueIndex][otherTeam]}:${scores[leagueIndex][team] + 1}`;
      }
      if (placedSelectionsLive) {
        placedSelectionsLive = calulatePlacedSelection(
          placedSelectionsLive,
          leagueIndex,
          matchId,
          score,
        );
      }

      if (showBlink) {
        blink[leagueIndex][team] = true;
        blinksForRemove.push({ l: leagueIndex, t: team });
      }
      scores[leagueIndex][team] += 1;
    }
  }
  return {
    placedSelectionsLive,
    blink,
    scores,
    blinksForRemove,
  };
}

/**
 * Parse ticket bets.
 *
 * @memberof utils/parsers
 * @param {string} betData [bet_type|leagueId:matchIndex:selectionId|stake -ex:
 *  "2|2:6:133$3:1:128|20000" ],
 * combination stake [ -ex: "3|1:3:102$1:4:102$1:5:102|2:200&3:500&4:1000&T:310000" ]
 * @param {string} betDetail [match:odd:score:win/lost$another_match|minOdd-maxOdd|winnings -ex:
 *  "ABC-DEF:2.15:0-1:1$GHI-JKL:1.85:2-1:1|1.85-11.63|1:64000&2:380000" ],
 * @param {Array} markets
 * @see {@link https://docs.google.com/document/d/1NZAkDBrMDwEZvmUvc3eee3rebHHzuVYARWeHzoKwJ0E/edit#heading=h.f5gov45epmd1}
 * @returns {object}
 */
function parseBets(betData, betDetail, markets) {
  const detail = betDetail.split('|');
  let minOdd;
  let maxOdd;
  const matches = detail[0].split('$');
  if (detail[1]) {
    const odds = detail[1].split('-');
    minOdd = odds[0] ? parseFloat(odds[0]).toFixed(2) : odds[0];
    maxOdd = odds[1] ? parseFloat(odds[1]).toFixed(2) : odds[1];
  }

  let winningString;
  const winningData = {};
  if (detail[2]) {
    winningString = detail[2].split('&');
    winningString.map((win) => {
      const winData = win.split(':');
      winningData[winData[0]] = winData[1];
    });
  }

  const bData = betData.split('|');
  const type = bData[0];
  const selectionsString = bData[1];
  const selections = selectionsString.split('$');
  const stakeString = bData[2].split('&');
  const combinations = {};

  stakeString.map((stake, i) => {
    if (i < stakeString.length - 1) {
      const s = stake.split(':');
      combinations[s[0]] = s[1];
    }
  });
  const bets = [];
  matches.map((matchString, i) => {
    const match = matchString.split(':');
    const selection = selections[i].split(':');
    bets.push({
      match: match[0],
      selection: getSelectionName(selection[2], markets),
      matchId: selection[1],
      odd: match[1] ? parseFloat(match[1]).toFixed(2) : match[1],
      score: match[2],
      status: match[3],
      league: selection[0],
    });
  });
  return {
    type,
    minOdd,
    maxOdd,
    numberOfBets: matches.length,
    bets,
    combinations,
    winningData,
    winningString,
  };
}

/**
 * Parsed tickets list per date
 *
 * @memberof utils/parsers
 * @param {Array} bets
 * @param {Array} markets
 * @param {object} prevBets
 * @param {object} selectedTicket
 * @param {Array} prevDates
 * @param {boolean} updTickets
 * @returns {object}
 */
export function parseTicketData(bets, markets, prevBets, selectedTicket, prevDates, updTickets) {
  const newBets = prevBets;
  const dates = prevDates;
  let newSelectedTicket = selectedTicket;
  bets.map((bet) => {
    const dateTime = getCountryDateTime(bet.placed);
    if (!newBets[dateTime.date]) {
      newBets[dateTime.date] = [];
      dates.push(dateTime.date);
    }
    const parsedBets = parseBets(bet.betdata, bet.betdetails, markets);
    const betData = {};
    Object.assign(
      betData,
      bet,
      parsedBets,
      {
        date: dateTime.date,
        time: dateTime.time,
      },
    );
    if (newSelectedTicket && betData.id === newSelectedTicket.id) {
      newSelectedTicket = betData;
    }
    if (updTickets) {
      const index = newBets[dateTime.date].findIndex((b) => b.id === betData.id);
      if (index !== -1) {
        newBets[dateTime.date][index] = betData;
      } else {
        newBets[dateTime.date].push(betData);
      }
    } else {
      newBets[dateTime.date].push(betData);
    }
  });
  return { tickets: newObjectRef(newBets), selectedTicket: newSelectedTicket, dates };
}

/**
 * Parse selected market
 *
 * @memberof utils/parsers
 * @param {Array} market
 * @returns {object}
 */
export function parseSelectedMarket(market) {
  const selectedMarket = newObjectRef(market);
  selectedMarket.selections = market.submarkets
    ? Object.keys(market.submarkets[0].selections)
    : Object.keys(market.selections);
  return selectedMarket;
}

/**
 * Check if any selection exist in odds
 *
 * @memberof utils/parsers
 * @param {object} selections
 * @param {object} odds
 * @returns {boolean}
 */
function findSelection(selections, odds) {
  let enabled = 0;
  Object.keys(selections).map((s) => {
    if (odds[s]) {
      enabled = 1;
    }
  });
  return enabled;
}
/**
 * Set flag enable on markets
 *
 * @memberof utils/parsers
 * @param {Array} markets
 * @param {object} odds Odds of first match of selected league
 * @returns {object}
 */
export function setActiveMarkets(markets, odds) {
  markets.map((m) => {
    if (!m.submarkets) {
      m.enabled = findSelection(m.selections, odds);
    } else {
      m.enabled = 0;
      m.submarkets.map((sm) => {
        sm.enabled = findSelection(sm.selections, odds);
        m.enabled = m.enabled || sm.enabled;
      });
    }
  });
  const activeMarkets = markets.filter((m) => (m.enabled === 1));
  return ({
    markets,
    activeMarkets,
    selectedMarket: parseSelectedMarket(activeMarkets[0]),
  });
}

/**
 * Set flag enable on markets
 *
 * @memberof utils/parsers
 * @param {number} bigNum
 * @returns {string}
 */
export function parseBigNums(bigNum) {
  if (bigNum.toString().indexOf('e') === -1) {
    return bigNum;
  }
  // take everything before 'e', split with '.' and join with '' to remove decimal dot
  return `${(bigNum.toString().split('e')[0]).split('.').join('')}...`;
}