<commit_before>(function() {
  const pwnies = {
    user: getUserInfo(),
    tick: 0,
    toc: 10,
    closedTracks: [],
    tracksSent: []
  };
  const WAGERS_CHANNEL = 'v2-wagers';
  const MESSAGE_TYPE_TRACK_RESULT = 'trackResult';
  const MESSAGE_TYPE_WAGERS = 'wagers';
  const threeSeconds = 1000 * 3;
  const isMobile = window.location.href.toLowerCase().indexOf('//m.twinspires.com') > -1; // eslint-disable-line
  // const sevenSeconds = 1000 * 7;
  // const thirtySeconds = 1000 * 30;
  // const oneMinute = 1000 * 30;

  init();

  function init() {
    if (window.pwnies) { // eslint-disable-line
      console.warn('Pownies script already loaded. I ain\'t gonna do shit.'); // eslint-disable-line

      return;
    }

    window.pwnies = true; // eslint-disable-line

    clearConsole()
      .then(function() {
        return injectElements();
      })
      .then(function() {
        return initializeControlData();
      })
      .then(function() {
        return initializePoolTypes();
      })
      .then(function(poolTypes) {
        pwnies.poolTypes = poolTypes;

        return initalizeTracks();
      })
      .then(function() {
        return initializePubNub();
      })
      .then(function() {
        console.log(`Finished init.`, pwnies); // eslint-disable-line

        window.pwnies = pwnies; // eslint-disable-line

        return;
      })
      .then(function() {
        return initializeWagerChecks();
      })
      ;
  }

  function clearConsole() {
    return new Promise(function(resolve) {
      console.clear();

      return resolve();
    });
  }

  function injectElements() {
    const scripts = [
      '//cdn.pubnub.com/pubnub-3.7.18.min.js',
      '//cdnjs.cloudflare.com/ajax/libs/moment.js/2.11.1/moment.min.js',
      '//cdnjs.cloudflare.com/ajax/libs/lodash.js/4.0.0/lodash.min.js'
    ];

    return Promise
      .all(getScriptLoadPromises(scripts)) // Load scripts
      .then(function() { // Load moment timezone
        const momentWithTZ = '//cdnjs.cloudflare.com/ajax/libs/moment-timezone/0.5.0/moment-timezone-with-data-2010-2020.min.js';

        return Promise.all(getScriptLoadPromises(momentWithTZ));
      })
      .then(function() { // Do everything else
        injectCss();
        injectSyncToggle();

        return injectIFrame();
      })
      ;

    function injectCss() {
      const css = `
        <style id='css-sync'>
          .noshow {display:none;height:0;width:0;}
          #syncButton {position:fixed;bottom:22px;right:25px;z-index:99999;height:33px;width:105px;
            border-radius:3px;border:1px solid rgba(255,255,255, .4);background-color:#3986CA;color:#fff}
          #syncButton:disabled {background-color: #ccc; color: #777}
          #syncButton div {height:14px;width:14px;border-radius:14px;background-color:#ddd;margin-right:10px;float:right;}
          #syncButton div.on {background-color:#4BBA4B;}
          ${isMobile ? `#syncButton {top:111px;right: 18px;}` : ``}
        </style>
      `;

      $('body').append(css); // eslint-disable-line
    }

    function injectSyncToggle() {
      const button = $('<button id="syncButton" disabled><span>Sync: --</span><div class=""></div></button>'); // eslint-disable-line

      button.on('click', function() {
        toggleSync();
      });

      $('body').prepend(button); // eslint-disable-line
    }

    function injectIFrame() {
      const ifm = document.createElement(`iframe`); // eslint-disable-line

      console.debug(`Injecting iframe for wager creds...`); // eslint-disable-line

      ifm.id = `iWager`;
      ifm.src = `/php/rtb/?print=true&cb=${(new Date()).getTime()}`;
      ifm.className = `noshow`;
      document.body.appendChild(ifm); // eslint-disable-line

      return new Promise(function(resolve) {
        setTimeout(function() { // eslint-disable-line
          pwnies.wagerCreds = $('#iWager')[0].contentWindow.Cdi.AppConfig.WS; // eslint-disable-line

          console.debug(`... iframe creds found.`); // eslint-disable-line

          return resolve();
        }, 1000);
      });
    }

    function getScriptLoadPromises(scripts) {
      scripts = typeof scripts === 'string' ? [scripts] : scripts; // eslint-disable-line

      return scripts.map(function(src) {
        return new Promise(function(resolve, reject) {
          const s = document.createElement('script'); // eslint-disable-line

          document.getElementsByTagName('head')[0].appendChild(s); // eslint-disable-line

          s.onload = function() {
            return resolve();
          };
          s.onerror = function() {
            return reject();
          };
          s.src = src;
        });
      });
    }
  }

  function initializeControlData() {
    const uc = Cdi.AppConfig.WS; // eslint-disable-line
    const wc = pwnies.wagerCreds;
    const baseUrl = `https://${isMobile ? `m` : `www`}.twinspires.com`;

    pwnies.wagerCreds.wagersUrl = `${baseUrl}/php/fw/php_BRIS_BatchAPI/2.3/Rtb/GetData?` // eslint-disable-line
        + `&username=${wc.USERNAME}`
        + `&password=${wc.PASSWORD}`
        + `&ip=${wc.CDI_CLIENT_IP}`
        + `&affid=${wc.CDI_SAID}`
        + `&affiliateId=${wc.CDI_SAID}`
        + `&account=${pwnies.user.accountNum}`
        + `&authKey=${pwnies.user.authKey}`
        + `&output=json`
        + `&limit=200`
        ;

    pwnies.wagerCreds.poolTypesUrl = `${baseUrl}/php/fw/php_BRIS_BatchAPI/2.3/Tote/PoolTypes?` // eslint-disable-line
      + `&username=${wc.USERNAME}`
      + `&password=${wc.PASSWORD}`
      + `&ip=${wc.CDI_CLIENT_IP}`
      + `&affid=${wc.CDI_SAID}`
      + `&affiliateId=${wc.CDI_SAID}`
      + `&output=json`
      ;

    pwnies.wagerCreds.trackListUrl = `${baseUrl}/php/fw/php_BRIS_BatchAPI/2.3/Cdi/TrackList?multisource=1&vidType=FLV` // eslint-disable-line
      + `&username=${uc.USERNAME}`
      + `&password=${<PASSWORD>}`
      + `&ip=${uc.CDI_CLIENT_IP}`
      + `&affid=${uc.CDI_SAID}`
      + `&output=json`
      ;

    if (!isMobile) {
      pwnies.wagerCreds.resultUrl = `${baseUrl}/secure-bin/results_tracks.cgi?`
        + `&race=all`
        ;
    }
    else {
      pwnies.wagerCreds.resultUrl = `${baseUrl}/php/fw/php_BRIS_BatchAPI/2.3/Tote/Results?` // eslint-disable-line
        + `&username=${uc.USERNAME}`
        + `&password=${<PASSWORD>}`
        + `&ip=${uc.CDI_CLIENT_IP}`
        + `&affid=${uc.CDI_SAID}`
        + `&output=json`
        ;
    }

    return pwnies;
  }

  function initializePoolTypes() {
    return new Promise(function(resolve) {
      console.debug('Fetching pool types...'); // eslint-disable-line

      $.getJSON(pwnies.wagerCreds.poolTypesUrl, function(data) {
        console.debug('... pool types received.'); // eslint-disable-line

        return resolve(data.PoolTypes);
      });
    });
  }

  function initalizeTracks() {
    return new Promise(function(resolve) {
      console.debug(`Fetching track list...`); // eslint-disable-line

      $.getJSON(pwnies.wagerCreds.trackListUrl, function(data) {
        console.debug(`... track list received.`); // eslint-disable-line

        pwnies.tracks = data.Tracks;

        return resolve();
      });
    });
  }

  function initializePubNub() {
    return new Promise(function(resolve) {
      console.debug(`Initing PubNub...`); // eslint-disable-line

      return resolve(PUBNUB.init({ // eslint-disable-line
        'publish_key': '{{pubsub_publish_key}}',
        'subscribe_key': '{{pubsub_subscribe_key}}',
        ssl: window.location.protocol.indexOf('s') > -1 // eslint-disable-line
      }));
    })
    .then(function(pubNubInstance) {
      console.debug(`PubNub initialized.`); // eslint-disable-line

      pwnies.PubNub = pubNubInstance;

      $(window).bind('unload',function() { // eslint-disable-line
        pwnies.isSyncing = false; // eslint-disable-line

        pwnies.PubNub.state({
          channel: WAGERS_CHANNEL,
          state: getUserInfoSlim(),
          callback: function() {},
          error: function() {}
        });
      });

      return new Promise(function(resolve) {
        pwnies.PubNub.subscribe({
          channel: WAGERS_CHANNEL,
          message: function() {},
          state: getUserInfoSlim(),
          connect: function() {
            console.debug(`Successfully subscribed to ${WAGERS_CHANNEL} channel.`); // eslint-disable-line

            return resolve(pwnies.PubNub);
          }
        });
      });
    })
    ;
  }

  function initializeWagerChecks() {
    console.debug(`Initializing wager check interval...`); // eslint-disable-line

    wagerCheckingStart();
  }

  function toggleSync() {
    pwnies.isSyncing = !pwnies.isSyncing;

    if (pwnies.isSyncing) {
      wagerCheckingStart();
    }
    else {
      wagerCheckingStop();
    }
  }

  function wagerCheckingStop() {
    pwnies.isSyncing = false;
    pwnies.tick = 0;
    pwnies.closedTracks = [];
    pwnies.tracksSent = [];

    if (!pwnies.timeouts) {
      pwnies.timeouts = {};
    }

    if (pwnies.timeouts.wc) {
      clearTimeout(pwnies.timeouts.wc);
    }

    setSyncState(pwnies.isSyncing);
    updateSyncButton(pwnies.isSyncing);
  }

  function wagerCheckingStart() {
    pwnies.isSyncing = true;

    setSyncState(pwnies.isSyncing);
    updateSyncButton(pwnies.isSyncing);

    processWagers(true);
  }

  function processWagers(forceSendAllWagers) {
    const wagersBefore = pwnies.wagers || [];
    const tracksBefore = (pwnies.tracksSent || []).concat();
    let tWagers = [];

    getWagersPromise()
      .then(function(wagers) {
        wagers.sort(function(a, b) {
          return b.timestamp - a.timestamp;
        });

        pwnies.wagers = wagers.concat();
        tWagers = wagers.concat();

        console.debug(`Wagers data retrieved...`, tWagers); // eslint-disable-line

        return wagers;
      })
      .then(function() {
        return getUniqueTracksPromise(pwnies.wagers);
      })
      .then(function(uniqueTracks) {
        return getTracksMtpPromise(uniqueTracks);
      })
      .then(function(tracks) {
        if (!forceSendAllWagers) {
          tracks = removeStaticTracks(tracks);
        }

        return getAllTrackResultsPromise(tracks, tWagers);
      })
      .then(function(allTrackResults) {
        return pushDiffTrackResultsPromise(tracksBefore, allTrackResults, forceSendAllWagers);
      })
      .then(function() {
        return pushDiffWagers(wagersBefore, tWagers, forceSendAllWagers);
      })
      .then(function() {
        // Go do it all again
        if (!pwnies.timeouts) {
          pwnies.timeouts = {};
        }

        if (pwnies.timeouts.wc) {
          clearTimeout(pwnies.timeouts.wc);
        }

        if (pwnies.isSyncing) {
          pwnies.tick++;

          const sendAllWagers = pwnies.tick % pwnies.toc === 0;

          pwnies.timeouts.wc = setTimeout(function() { // eslint-disable-line
            processWagers(sendAllWagers);
          }, threeSeconds);
        }
      })
      ;
  }

  function getWagersPromise() {
    return new Promise(function(resolve) {
      const cb = `&cb=${(new Date()).getTime()}`;

      $.getJSON(pwnies.wagerCreds.wagersUrl + cb, function(data) {
        return resolve(slimWagers((data.Wagers || [])));
      });
    });
  }

  function getUniqueTracksPromise(wagers) {
    return new Promise(function(resolve) {
      const uniqTracks = [];
      const tracks = wagers.map(function(w) {
        return pwnies.tracks.find(function(t) {
          return t.EventCode === w.eventCode;
        });
      });

      tracks.forEach(function(t) {
        const uTrack = uniqTracks.find(function(u) {
          return t.BrisCode === u.BrisCode;
        });

        if (!uTrack) {
          uniqTracks.push(t);
        }
      });

      return resolve(uniqTracks);
    });
  }

  function getAllTrackResultsPromise(tracks, wagers) {
    // const slimTracks = slimDownTracksToOnlyThoseWithWagers(tracks.concat(), wagers);
    let promises;

    if (isMobile && false) {
      // TODO: Work out mobile race results
      // promises = tracks.map(getTrackResultsMobilePromise);
    }
    else {
      promises = tracks.map(getTrackResultsDesktopPromise);
    }

    return Promise.all(promises);
  }

  function getTrackResultsDesktopPromise(track) {
    const lookups = {
      'thoroughbred': 1,
      'harness': 2
    };
    const url = `${pwnies.wagerCreds.resultUrl}&track=${track.BrisCode}&type=${lookups[track.TrackType.toLowerCase()]}`;

    return new Promise(function(resolve) {
      console.debug(`Fetching ${track.BrisCode} track results...`, url); // eslint-disable-line

      $.get(url, function onSuccess(html) { // eslint-disable-line
        const data = parseResultsToJson(html) || {};

        data.track = {
          BrisCode: track.BrisCode,
          DisplayName: track.DisplayName,
          nextRace: track.nextRace
        };

        return resolve(data);
      });
    });
  }

  function getTracksMtpPromise(tracks) {
    const wc = pwnies.wagerCreds;

    return new Promise(function(resolve) {
      const url = `/php/fw/php_BRIS_BatchAPI/2.3/Tote/CurrentRace?`
        + `username=${wc.USERNAME}&password=${<PASSWORD>}`
        + `&ip=${wc.CDI_CLIENT_IP}&affid=${wc.CDI_SAID}&output=json`;

      $.get(url, function(data) {
        const mtps = data.CurrentRace;

        return resolve(tracks.map(function(t) {
          const mtp = mtps.find(function(m) {
            return t.BrisCode.toLowerCase() === m.BrisCode.toLowerCase();
          });

          mtp.postTimestamp = moment.tz(mtp.PostTime, 'America/New_York').toDate().getTime(); // eslint-disable-line
          mtp.firstPostTimestamp = moment.tz(mtp.FirstPostTime, 'America/New_York').toDate().getTime(); // eslint-disable-line

          t.nextRace = mtp;

          return t;
        }));
      });
    });
  }

  function pushDiffTrackResultsPromise(tracksBefore, tracksAfter, forceSendAllTracks) {
    return new Promise(function(resolve) {
      if (tracksAfter.length === 0) {
        console.debug(`No tracks to send. Doing nothing.`); // eslint-disable-line

        return resolve();
      }

      if (!forceSendAllTracks && !areTracksDifferent(tracksBefore, tracksAfter)) { // eslint-disable-line
        console.debug(`No changes to tracks. Nothing to do.`); // eslint-disable-line

        return resolve();
      }

      tracksAfter.forEach(function(trackResult) {
        const data = {};

        console.debug(`Sending track results...`, trackResult); // eslint-disable-line

        data[MESSAGE_TYPE_TRACK_RESULT] = trackResult;
        pwnies.PubNub.publish({
          channel: WAGERS_CHANNEL,
          message: data,
          callback: resolve
        });
      });

      pwnies.tracksSent = tracksAfter;
    });
  }

  function pushDiffWagers(wagersBefore, wagersAfter, forceSendAllWagers) {
    return new Promise(function(resolve) {
      const data = {};
      let diffWagers = [];

      wagersBefore = wagersBefore.splice(0, 30); // Limit to 30 most recent
      wagersAfter = wagersAfter.splice(0, 30); // Limit to 30 most recent

      pwnies.PubNub.state({
        channel: WAGERS_CHANNEL,
        state: getUserInfoSlim(),
        callback: function() {},
        error: function() {}
      });

      if (!forceSendAllWagers && _.isEqual(wagersBefore, wagersAfter)) { // eslint-disable-line
        console.debug(`No changes to wagers. Nothing to do.`); // eslint-disable-line

        return resolve();
      }

      if (forceSendAllWagers) {
        diffWagers = wagersAfter;

        console.debug(`Sending bulk wagers...`, diffWagers); // eslint-disable-line
      }
      else {
        wagersAfter.forEach(function(a) {
          const foundBW = wagersBefore.find(function(b) {
            return a.id === b.id;
          });

          if (!foundBW) {
            diffWagers.push(a); // Didn't find it, must be new
          }
          else if (!_.isEqual(a, foundBW)) { // eslint-disable-line
            diffWagers.push(a); // Found it, but has changed
          }
        });

        console.debug(`Sending diff wagers...`, diffWagers); // eslint-disable-line
      }

      const chunks = _.chunk(diffWagers, 5); // Break calls into manageable sizes

      chunks.forEach(function(chunk) {
        data[MESSAGE_TYPE_WAGERS] = chunk;

        pwnies.PubNub.publish({
          channel: WAGERS_CHANNEL,
          message: data,
          callback: resolve
        });
      });
    });
  }

  function setSyncState(isSyncing) {
    pwnies.PubNub.state({
      channel: WAGERS_CHANNEL,
      state: getUserInfoSlim(),
      callback: function() {
        if (isSyncing) {
          console.debug('Started syncing.') // eslint-disable-line
        }
        else {
          console.debug('Stopped syncing.') // eslint-disable-line
        }
      },
      error: function() {}
    });
  }

  function updateSyncButton(isSyncing) {
    const $btn = $('body').find('#syncButton'); // eslint-disable-line
    const $span = $btn.find('span');
    const $indicator = $btn.find('div');

    if (isSyncing) {
      $span.text('Sync: ON');
      $indicator.addClass('on');
    }
    else {
      $span.text('Sync: OFF');
      $indicator.removeClass('on');
    }

    $btn.attr('disabled', 'disabled');

    setTimeout(function() { // eslint-disable-line
      $btn.removeAttr('disabled');
    }, 750);
  }

  function slimWagers(wagers) {
    const offsetTime = 1000 * 10; // Their server times are a few seconds slow

    if (!wagers) {
      return;
    }

    return wagers.map(function(w) {
      const newWager = {};

      newWager.timestamp = moment.tz(w.placedDate, 'America/Los_Angeles').toDate().getTime() - offsetTime; // eslint-disable-line
      newWager.id = w.serialNumber;
      newWager.user = _.pick(pwnies.user, ['email', 'firstName', 'lastName', 'accountNum']); // eslint-disable-line
      newWager.betAmount = parseFloat(w.totalCost, 10);
      newWager.payoutAmount = parseFloat(w.payoutAmount, 10);
      newWager.type = pwnies.poolTypes.find(function(pt) {
        return pt.Code === w.poolType;
      });
      newWager.selections = w.runnersList;
      newWager.race = {id: w.race};
      newWager.track = pwnies.tracks.find(function(t) {
        return t.EventCode === w.eventCode;
      });
      newWager.eventCode = newWager.track.EventCode;
      newWager.status = w.status;
      newWager.refundAmount = w.refundAmount;

      return newWager;
    });
  }

  function slimDownTracksToOnlyThoseWithWagers(fullTracks, wagers) {
    const slimTracks = fullTracks
      .filter(function(t) {
        return !!t.races.find(function(r) {
          return !!wagers.find(function(w) {
            return t.BrisCode === w.BrisCode && r.id === w.race.id;
          });
        });
      })
      .map(function(t) {
        t.races = t.races.filter(function(r) {
          return !!wagers.find(function(w) {
            return w.BrisCode === t.BrisCode && w.race.id === r.id;
          });
        });

        return t;
      })
      ;

    debugger;

    return slimTracks;
  }

  function areTracksDifferent(b, a) {
    if ((!a && b) || (a && !b)) {
      return true;
    }

    if (a.length !== b.length) {
      console.debug(`Differences were found in tracks lengths.`, {expected: a.length, a: a, actual: b.length, b: b}); // eslint-disable-line

      return true;
    }

    const aSorted = _.orderBy(a, function(t) {
      return t.BrisCode;
    });

    const bSorted = _.orderBy(b, function(t) {
      return t.BrisCode;
    });

    const isDiff = aSorted.find(function(at, aIndex) {
      const bt = bSorted[aIndex];

      if (at.races.length !== bt.races.length) {
        console.debug(`Differences were found in races length.`, {expected: at.races.length, actual: bt.races.length, racesA: at.races, racesB: bt.races}); // eslint-disable-line

        return true;
      }
      else if (!_.isEqual(at.nextRace, bt.nextRace)) {
        console.debug(`Differences were found in nextRaces.`, {expected: at.nextRace, actual: bt.nextRace}); // eslint-disable-line

        return true;
      }

      return false;
    });

    return !!isDiff;
  }

  function removeStaticTracks(tracks) {
    return tracks.filter(function(t) {
      if (t.nextRace.Status.toLowerCase() === 'closed') {
        console.debug(`Removing track ${t.BrisCode} from send list because it has closed.`); // eslint-disable-line

        return false;
      }

      if (t.nextRace.Status.toLowerCase() === 'open' && t.nextRace.Mtp === 99) {
        console.debug(`Removing track ${t.BrisCode} from send list because it is not open yet.`); // eslint-disable-line

        return false;
      }

      return true;
    });
  }

  function parseResultsToJson(html) {
    if ($('.data_hl').length) { // eslint-disable-line
      return null; // No results data
    }

    const data = {races: []};
    const $wpsTbls = $(html).find('.toteboard:even'); // eslint-disable-line
    const $exTbls = $(html).find('.toteboard:odd'); // eslint-disable-line

    $(html).find('th[colspan="4"] a,script').remove(); // eslint-disable-line

    const raceIds = [];
    const regex = /Race (\d+)/ig;

    $(html).find('th[colspan="4"]').each(function(index, th) { // eslint-disable-line
      const matches = $(th).text().match(regex); // eslint-disable-line

      raceIds.push(parseInt(matches[0].replace('Race ', ''), 0));
    });

    $wpsTbls.each(function(i, tbl) {
      const $tbl = $(tbl); // eslint-disable-line
      const $xbl = $($exTbls[i]); // eslint-disable-line
      const race = {
        id: raceIds[i],
        wps: [],
        exotics: []
      };

      const $rows = $tbl.find('tr:gt(1)');

      if ($rows.length === 0) {
        return null; // No results data
      }

      $rows.each(function(j, row) {
        const $row = $(row); // eslint-disable-line
        const payout = {
          place: j + 1
        };

        const winAmountDisplay = $row.find('td:eq(1)').text().replace(/^[\.]$/ig, '');
        const placeAmountDisplay = $row.find('td:eq(2)').text().replace(/^[\.]$/ig, '');
        const showAmountDisplay = $row.find('td:eq(3)').text().replace(/^[\.]$/ig, '');

        payout.horse = $row.find('td:eq(0)').text();
        payout.winAmount = parseFloat(winAmountDisplay.replace(/[^0-9\.]+/ig, ''), 0) || 0;
        payout.placeAmount = parseFloat(placeAmountDisplay.replace(/[^0-9\.]+/ig, ''), 0) || 0;
        payout.showAmount = parseFloat(showAmountDisplay.replace(/[^0-9\.]+/ig, ''), 0) || 0;

        race.wps.push(payout);
      });

      const $xRows = $xbl.find('tr:gt(1)');

      $xRows.each(function(k, row) {
        const $row = $(row); // eslint-disable-line

        const payout = {};

        const wagerType = $row.find('td:eq(0)').text().trim();
        const winAmountDisplay = $row.find('td:eq(2)').text().trim();

        payout.type = wagerType.replace(/[\$0-9\. ]/ig, '');
        payout.denomination = parseFloat(wagerType.replace(/[^0-9\.]/ig, ''), 0) || 0;
        payout.winCombo = $row.find('td:eq(1)').text().split('/');
        payout.winAmount = parseFloat(winAmountDisplay.replace(/[^0-9\.]/ig, ''), 0) || 0;

        race.exotics.push(payout);
      });

      data.races.push(race);
    });

    return data;
  }

  function getUserInfo() {
    const obj = JSON.parse(sessionStorage.getItem('GlobalData')); // eslint-disable-line

    if (obj) {
      const key = Object.keys(obj).find(function(key) {
        return key.indexOf('classic') > -1;
      });
      const user = obj[key];

      return {
        email: user.email,
        firstName: user.firstName,
        lastName: user.lastName,
        accountNum: user.accountNum,
        authKey: user.authKey,
        isSyncing: pwnies && pwnies.isSyncing
      };
    }

    return {};
  }

  function getUserInfoSlim() {
    const data = getUserInfo();

    return {
      email: data.email,
      firstName: data.firstName,
      lastName: data.lastName,
      isSyncing: data.isSyncing
    };
  }
})();
<commit_msg>Fixed sync button color bug on disable<commit_after>(function() {
  const pwnies = {
    user: getUserInfo(),
    tick: 0,
    toc: 10,
    closedTracks: [],
    tracksSent: []
  };
  const WAGERS_CHANNEL = 'v2-wagers';
  const MESSAGE_TYPE_TRACK_RESULT = 'trackResult';
  const MESSAGE_TYPE_WAGERS = 'wagers';
  const threeSeconds = 1000 * 3;
  const isMobile = window.location.href.toLowerCase().indexOf('//m.twinspires.com') > -1; // eslint-disable-line
  // const sevenSeconds = 1000 * 7;
  // const thirtySeconds = 1000 * 30;
  // const oneMinute = 1000 * 30;

  init();

  function init() {
    if (window.pwnies) { // eslint-disable-line
      console.warn('Pownies script already loaded. I ain\'t gonna do shit.'); // eslint-disable-line

      return;
    }

    window.pwnies = true; // eslint-disable-line

    clearConsole()
      .then(function() {
        return injectElements();
      })
      .then(function() {
        return initializeControlData();
      })
      .then(function() {
        return initializePoolTypes();
      })
      .then(function(poolTypes) {
        pwnies.poolTypes = poolTypes;

        return initalizeTracks();
      })
      .then(function() {
        return initializePubNub();
      })
      .then(function() {
        console.log(`Finished init.`, pwnies); // eslint-disable-line

        window.pwnies = pwnies; // eslint-disable-line

        return;
      })
      .then(function() {
        return initializeWagerChecks();
      })
      ;
  }

  function clearConsole() {
    return new Promise(function(resolve) {
      console.clear();

      return resolve();
    });
  }

  function injectElements() {
    const scripts = [
      '//cdn.pubnub.com/pubnub-3.7.18.min.js',
      '//cdnjs.cloudflare.com/ajax/libs/moment.js/2.11.1/moment.min.js',
      '//cdnjs.cloudflare.com/ajax/libs/lodash.js/4.0.0/lodash.min.js'
    ];

    return Promise
      .all(getScriptLoadPromises(scripts)) // Load scripts
      .then(function() { // Load moment timezone
        const momentWithTZ = '//cdnjs.cloudflare.com/ajax/libs/moment-timezone/0.5.0/moment-timezone-with-data-2010-2020.min.js';

        return Promise.all(getScriptLoadPromises(momentWithTZ));
      })
      .then(function() { // Do everything else
        injectCss();
        injectSyncToggle();

        return injectIFrame();
      })
      ;

    function injectCss() {
      const css = `
        <style id='css-sync'>
          .noshow {display:none;height:0;width:0;}
          #syncButton {position:fixed;bottom:22px;right:25px;z-index:99999;height:33px;width:105px;
            border-radius:3px;border:1px solid rgba(255,255,255, .4);background-color:#5196D4;color:#fff}
          #syncButton:disabled {background-color: #ccc; color: #777}
          #syncButton div {height:14px;width:14px;border-radius:14px;background-color:#ddd;margin-right:10px;float:right;}
          #syncButton div.on {background-color:#4BBA4B;}
          ${isMobile ? `#syncButton {top:111px;right: 18px;}` : ``}
        </style>
      `;

      $('body').append(css); // eslint-disable-line
    }

    function injectSyncToggle() {
      const button = $('<button id="syncButton" disabled><span>Sync: --</span><div class=""></div></button>'); // eslint-disable-line

      button.on('click', function() {
        toggleSync();
      });

      $('body').prepend(button); // eslint-disable-line
    }

    function injectIFrame() {
      const ifm = document.createElement(`iframe`); // eslint-disable-line

      console.debug(`Injecting iframe for wager creds...`); // eslint-disable-line

      ifm.id = `iWager`;
      ifm.src = `/php/rtb/?print=true&cb=${(new Date()).getTime()}`;
      ifm.className = `noshow`;
      document.body.appendChild(ifm); // eslint-disable-line

      return new Promise(function(resolve) {
        setTimeout(function() { // eslint-disable-line
          pwnies.wagerCreds = $('#iWager')[0].contentWindow.Cdi.AppConfig.WS; // eslint-disable-line

          console.debug(`... iframe creds found.`); // eslint-disable-line

          return resolve();
        }, 1000);
      });
    }

    function getScriptLoadPromises(scripts) {
      scripts = typeof scripts === 'string' ? [scripts] : scripts; // eslint-disable-line

      return scripts.map(function(src) {
        return new Promise(function(resolve, reject) {
          const s = document.createElement('script'); // eslint-disable-line

          document.getElementsByTagName('head')[0].appendChild(s); // eslint-disable-line

          s.onload = function() {
            return resolve();
          };
          s.onerror = function() {
            return reject();
          };
          s.src = src;
        });
      });
    }
  }

  function initializeControlData() {
    const uc = Cdi.AppConfig.WS; // eslint-disable-line
    const wc = pwnies.wagerCreds;
    const baseUrl = `https://${isMobile ? `m` : `www`}.twinspires.com`;

    pwnies.wagerCreds.wagersUrl = `${baseUrl}/php/fw/php_BRIS_BatchAPI/2.3/Rtb/GetData?` // eslint-disable-line
        + `&username=${wc.USERNAME}`
        + `&password=${<PASSWORD>}`
        + `&ip=${wc.CDI_CLIENT_IP}`
        + `&affid=${wc.CDI_SAID}`
        + `&affiliateId=${wc.CDI_SAID}`
        + `&account=${pwnies.user.accountNum}`
        + `&authKey=${pwnies.user.authKey}`
        + `&output=json`
        + `&limit=200`
        ;

    pwnies.wagerCreds.poolTypesUrl = `${baseUrl}/php/fw/php_BRIS_BatchAPI/2.3/Tote/PoolTypes?` // eslint-disable-line
      + `&username=${wc.USERNAME}`
      + `&password=${<PASSWORD>}`
      + `&ip=${wc.CDI_CLIENT_IP}`
      + `&affid=${wc.CDI_SAID}`
      + `&affiliateId=${wc.CDI_SAID}`
      + `&output=json`
      ;

    pwnies.wagerCreds.trackListUrl = `${baseUrl}/php/fw/php_BRIS_BatchAPI/2.3/Cdi/TrackList?multisource=1&vidType=FLV` // eslint-disable-line
      + `&username=${uc.USERNAME}`
      + `&password=${<PASSWORD>}`
      + `&ip=${uc.CDI_CLIENT_IP}`
      + `&affid=${uc.CDI_SAID}`
      + `&output=json`
      ;

    if (!isMobile) {
      pwnies.wagerCreds.resultUrl = `${baseUrl}/secure-bin/results_tracks.cgi?`
        + `&race=all`
        ;
    }
    else {
      pwnies.wagerCreds.resultUrl = `${baseUrl}/php/fw/php_BRIS_BatchAPI/2.3/Tote/Results?` // eslint-disable-line
        + `&username=${uc.USERNAME}`
        + `&password=${<PASSWORD>}`
        + `&ip=${uc.CDI_CLIENT_IP}`
        + `&affid=${uc.CDI_SAID}`
        + `&output=json`
        ;
    }

    return pwnies;
  }

  function initializePoolTypes() {
    return new Promise(function(resolve) {
      console.debug('Fetching pool types...'); // eslint-disable-line

      $.getJSON(pwnies.wagerCreds.poolTypesUrl, function(data) {
        console.debug('... pool types received.'); // eslint-disable-line

        return resolve(data.PoolTypes);
      });
    });
  }

  function initalizeTracks() {
    return new Promise(function(resolve) {
      console.debug(`Fetching track list...`); // eslint-disable-line

      $.getJSON(pwnies.wagerCreds.trackListUrl, function(data) {
        console.debug(`... track list received.`); // eslint-disable-line

        pwnies.tracks = data.Tracks;

        return resolve();
      });
    });
  }

  function initializePubNub() {
    return new Promise(function(resolve) {
      console.debug(`Initing PubNub...`); // eslint-disable-line

      return resolve(PUBNUB.init({ // eslint-disable-line
        'publish_key': '{{pubsub_publish_key}}',
        'subscribe_key': '{{pubsub_subscribe_key}}',
        ssl: window.location.protocol.indexOf('s') > -1 // eslint-disable-line
      }));
    })
    .then(function(pubNubInstance) {
      console.debug(`PubNub initialized.`); // eslint-disable-line

      pwnies.PubNub = pubNubInstance;

      $(window).bind('unload',function() { // eslint-disable-line
        pwnies.isSyncing = false; // eslint-disable-line

        pwnies.PubNub.state({
          channel: WAGERS_CHANNEL,
          state: getUserInfoSlim(),
          callback: function() {},
          error: function() {}
        });
      });

      return new Promise(function(resolve) {
        pwnies.PubNub.subscribe({
          channel: WAGERS_CHANNEL,
          message: function() {},
          state: getUserInfoSlim(),
          connect: function() {
            console.debug(`Successfully subscribed to ${WAGERS_CHANNEL} channel.`); // eslint-disable-line

            return resolve(pwnies.PubNub);
          }
        });
      });
    })
    ;
  }

  function initializeWagerChecks() {
    console.debug(`Initializing wager check interval...`); // eslint-disable-line

    wagerCheckingStart();
  }

  function toggleSync() {
    pwnies.isSyncing = !pwnies.isSyncing;

    if (pwnies.isSyncing) {
      wagerCheckingStart();
    }
    else {
      wagerCheckingStop();
    }
  }

  function wagerCheckingStop() {
    pwnies.isSyncing = false;
    pwnies.tick = 0;
    pwnies.closedTracks = [];
    pwnies.tracksSent = [];

    if (!pwnies.timeouts) {
      pwnies.timeouts = {};
    }

    if (pwnies.timeouts.wc) {
      clearTimeout(pwnies.timeouts.wc);
    }

    setSyncState(pwnies.isSyncing);
    updateSyncButton(pwnies.isSyncing);
  }

  function wagerCheckingStart() {
    pwnies.isSyncing = true;

    setSyncState(pwnies.isSyncing);
    updateSyncButton(pwnies.isSyncing);

    processWagers(true);
  }

  function processWagers(forceSendAllWagers) {
    const wagersBefore = pwnies.wagers || [];
    const tracksBefore = (pwnies.tracksSent || []).concat();
    let tWagers = [];

    getWagersPromise()
      .then(function(wagers) {
        wagers.sort(function(a, b) {
          return b.timestamp - a.timestamp;
        });

        pwnies.wagers = wagers.concat();
        tWagers = wagers.concat();

        console.debug(`Wagers data retrieved...`, tWagers); // eslint-disable-line

        return wagers;
      })
      .then(function() {
        return getUniqueTracksPromise(pwnies.wagers);
      })
      .then(function(uniqueTracks) {
        return getTracksMtpPromise(uniqueTracks);
      })
      .then(function(tracks) {
        if (!forceSendAllWagers) {
          tracks = removeStaticTracks(tracks);
        }

        return getAllTrackResultsPromise(tracks, tWagers);
      })
      .then(function(allTrackResults) {
        return pushDiffTrackResultsPromise(tracksBefore, allTrackResults, forceSendAllWagers);
      })
      .then(function() {
        return pushDiffWagers(wagersBefore, tWagers, forceSendAllWagers);
      })
      .then(function() {
        // Go do it all again
        if (!pwnies.timeouts) {
          pwnies.timeouts = {};
        }

        if (pwnies.timeouts.wc) {
          clearTimeout(pwnies.timeouts.wc);
        }

        if (pwnies.isSyncing) {
          pwnies.tick++;

          const sendAllWagers = pwnies.tick % pwnies.toc === 0;

          pwnies.timeouts.wc = setTimeout(function() { // eslint-disable-line
            processWagers(sendAllWagers);
          }, threeSeconds);
        }
      })
      ;
  }

  function getWagersPromise() {
    return new Promise(function(resolve) {
      const cb = `&cb=${(new Date()).getTime()}`;

      $.getJSON(pwnies.wagerCreds.wagersUrl + cb, function(data) {
        return resolve(slimWagers((data.Wagers || [])));
      });
    });
  }

  function getUniqueTracksPromise(wagers) {
    return new Promise(function(resolve) {
      const uniqTracks = [];
      const tracks = wagers.map(function(w) {
        return pwnies.tracks.find(function(t) {
          return t.EventCode === w.eventCode;
        });
      });

      tracks.forEach(function(t) {
        const uTrack = uniqTracks.find(function(u) {
          return t.BrisCode === u.BrisCode;
        });

        if (!uTrack) {
          uniqTracks.push(t);
        }
      });

      return resolve(uniqTracks);
    });
  }

  function getAllTrackResultsPromise(tracks, wagers) {
    // const slimTracks = slimDownTracksToOnlyThoseWithWagers(tracks.concat(), wagers);
    let promises;

    if (isMobile && false) {
      // TODO: Work out mobile race results
      // promises = tracks.map(getTrackResultsMobilePromise);
    }
    else {
      promises = tracks.map(getTrackResultsDesktopPromise);
    }

    return Promise.all(promises);
  }

  function getTrackResultsDesktopPromise(track) {
    const lookups = {
      'thoroughbred': 1,
      'harness': 2
    };
    const url = `${pwnies.wagerCreds.resultUrl}&track=${track.BrisCode}&type=${lookups[track.TrackType.toLowerCase()]}`;

    return new Promise(function(resolve) {
      console.debug(`Fetching ${track.BrisCode} track results...`, url); // eslint-disable-line

      $.get(url, function onSuccess(html) { // eslint-disable-line
        const data = parseResultsToJson(html) || {};

        data.track = {
          BrisCode: track.BrisCode,
          DisplayName: track.DisplayName,
          nextRace: track.nextRace
        };

        return resolve(data);
      });
    });
  }

  function getTracksMtpPromise(tracks) {
    const wc = pwnies.wagerCreds;

    return new Promise(function(resolve) {
      const url = `/php/fw/php_BRIS_BatchAPI/2.3/Tote/CurrentRace?`
        + `username=${wc.USERNAME}&password=${<PASSWORD>}`
        + `&ip=${wc.CDI_CLIENT_IP}&affid=${wc.CDI_SAID}&output=json`;

      $.get(url, function(data) {
        const mtps = data.CurrentRace;

        return resolve(tracks.map(function(t) {
          const mtp = mtps.find(function(m) {
            return t.BrisCode.toLowerCase() === m.BrisCode.toLowerCase();
          });

          mtp.postTimestamp = moment.tz(mtp.PostTime, 'America/New_York').toDate().getTime(); // eslint-disable-line
          mtp.firstPostTimestamp = moment.tz(mtp.FirstPostTime, 'America/New_York').toDate().getTime(); // eslint-disable-line

          t.nextRace = mtp;

          return t;
        }));
      });
    });
  }

  function pushDiffTrackResultsPromise(tracksBefore, tracksAfter, forceSendAllTracks) {
    return new Promise(function(resolve) {
      if (tracksAfter.length === 0) {
        console.debug(`No tracks to send. Doing nothing.`); // eslint-disable-line

        return resolve();
      }

      if (!forceSendAllTracks && !areTracksDifferent(tracksBefore, tracksAfter)) { // eslint-disable-line
        console.debug(`No changes to tracks. Nothing to do.`); // eslint-disable-line

        return resolve();
      }

      tracksAfter.forEach(function(trackResult) {
        const data = {};

        console.debug(`Sending track results...`, trackResult); // eslint-disable-line

        data[MESSAGE_TYPE_TRACK_RESULT] = trackResult;
        pwnies.PubNub.publish({
          channel: WAGERS_CHANNEL,
          message: data,
          callback: resolve
        });
      });

      pwnies.tracksSent = tracksAfter;
    });
  }

  function pushDiffWagers(wagersBefore, wagersAfter, forceSendAllWagers) {
    return new Promise(function(resolve) {
      const data = {};
      let diffWagers = [];

      wagersBefore = wagersBefore.splice(0, 30); // Limit to 30 most recent
      wagersAfter = wagersAfter.splice(0, 30); // Limit to 30 most recent

      pwnies.PubNub.state({
        channel: WAGERS_CHANNEL,
        state: getUserInfoSlim(),
        callback: function() {},
        error: function() {}
      });

      if (!forceSendAllWagers && _.isEqual(wagersBefore, wagersAfter)) { // eslint-disable-line
        console.debug(`No changes to wagers. Nothing to do.`); // eslint-disable-line

        return resolve();
      }

      if (forceSendAllWagers) {
        diffWagers = wagersAfter;

        console.debug(`Sending bulk wagers...`, diffWagers); // eslint-disable-line
      }
      else {
        wagersAfter.forEach(function(a) {
          const foundBW = wagersBefore.find(function(b) {
            return a.id === b.id;
          });

          if (!foundBW) {
            diffWagers.push(a); // Didn't find it, must be new
          }
          else if (!_.isEqual(a, foundBW)) { // eslint-disable-line
            diffWagers.push(a); // Found it, but has changed
          }
        });

        console.debug(`Sending diff wagers...`, diffWagers); // eslint-disable-line
      }

      const chunks = _.chunk(diffWagers, 5); // Break calls into manageable sizes

      chunks.forEach(function(chunk) {
        data[MESSAGE_TYPE_WAGERS] = chunk;

        pwnies.PubNub.publish({
          channel: WAGERS_CHANNEL,
          message: data,
          callback: resolve
        });
      });
    });
  }

  function setSyncState(isSyncing) {
    pwnies.PubNub.state({
      channel: WAGERS_CHANNEL,
      state: getUserInfoSlim(),
      callback: function() {
        if (isSyncing) {
          console.debug('Started syncing.') // eslint-disable-line
        }
        else {
          console.debug('Stopped syncing.') // eslint-disable-line
        }
      },
      error: function() {}
    });
  }

  function updateSyncButton(isSyncing) {
    const $btn = $('body').find('#syncButton'); // eslint-disable-line
    const $span = $btn.find('span');
    const $indicator = $btn.find('div');

    if (isSyncing) {
      $span.text('Sync: ON');
      $indicator.addClass('on');
    }
    else {
      $span.text('Sync: OFF');
      $indicator.removeClass('on');
    }

    $btn.attr('disabled', 'disabled');

    setTimeout(function() { // eslint-disable-line
      $btn.removeAttr('disabled');
    }, 750);
  }

  function slimWagers(wagers) {
    const offsetTime = 1000 * 10; // Their server times are a few seconds slow

    if (!wagers) {
      return;
    }

    return wagers.map(function(w) {
      const newWager = {};

      newWager.timestamp = moment.tz(w.placedDate, 'America/Los_Angeles').toDate().getTime() - offsetTime; // eslint-disable-line
      newWager.id = w.serialNumber;
      newWager.user = _.pick(pwnies.user, ['email', 'firstName', 'lastName', 'accountNum']); // eslint-disable-line
      newWager.betAmount = parseFloat(w.totalCost, 10);
      newWager.payoutAmount = parseFloat(w.payoutAmount, 10);
      newWager.type = pwnies.poolTypes.find(function(pt) {
        return pt.Code === w.poolType;
      });
      newWager.selections = w.runnersList;
      newWager.race = {id: w.race};
      newWager.track = pwnies.tracks.find(function(t) {
        return t.EventCode === w.eventCode;
      });
      newWager.eventCode = newWager.track.EventCode;
      newWager.status = w.status;
      newWager.refundAmount = w.refundAmount;

      return newWager;
    });
  }

  function slimDownTracksToOnlyThoseWithWagers(fullTracks, wagers) {
    const slimTracks = fullTracks
      .filter(function(t) {
        return !!t.races.find(function(r) {
          return !!wagers.find(function(w) {
            return t.BrisCode === w.BrisCode && r.id === w.race.id;
          });
        });
      })
      .map(function(t) {
        t.races = t.races.filter(function(r) {
          return !!wagers.find(function(w) {
            return w.BrisCode === t.BrisCode && w.race.id === r.id;
          });
        });

        return t;
      })
      ;

    debugger;

    return slimTracks;
  }

  function areTracksDifferent(b, a) {
    if ((!a && b) || (a && !b)) {
      return true;
    }

    if (a.length !== b.length) {
      console.debug(`Differences were found in tracks lengths.`, {expected: a.length, a: a, actual: b.length, b: b}); // eslint-disable-line

      return true;
    }

    const aSorted = _.orderBy(a, function(t) {
      return t.BrisCode;
    });

    const bSorted = _.orderBy(b, function(t) {
      return t.BrisCode;
    });

    const isDiff = aSorted.find(function(at, aIndex) {
      const bt = bSorted[aIndex];

      if (at.races.length !== bt.races.length) {
        console.debug(`Differences were found in races length.`, {expected: at.races.length, actual: bt.races.length, racesA: at.races, racesB: bt.races}); // eslint-disable-line

        return true;
      }
      else if (!_.isEqual(at.nextRace, bt.nextRace)) {
        console.debug(`Differences were found in nextRaces.`, {expected: at.nextRace, actual: bt.nextRace}); // eslint-disable-line

        return true;
      }

      return false;
    });

    return !!isDiff;
  }

  function removeStaticTracks(tracks) {
    return tracks.filter(function(t) {
      if (t.nextRace.Status.toLowerCase() === 'closed') {
        console.debug(`Removing track ${t.BrisCode} from send list because it has closed.`); // eslint-disable-line

        return false;
      }

      if (t.nextRace.Status.toLowerCase() === 'open' && t.nextRace.Mtp === 99) {
        console.debug(`Removing track ${t.BrisCode} from send list because it is not open yet.`); // eslint-disable-line

        return false;
      }

      return true;
    });
  }

  function parseResultsToJson(html) {
    if ($('.data_hl').length) { // eslint-disable-line
      return null; // No results data
    }

    const data = {races: []};
    const $wpsTbls = $(html).find('.toteboard:even'); // eslint-disable-line
    const $exTbls = $(html).find('.toteboard:odd'); // eslint-disable-line

    $(html).find('th[colspan="4"] a,script').remove(); // eslint-disable-line

    const raceIds = [];
    const regex = /Race (\d+)/ig;

    $(html).find('th[colspan="4"]').each(function(index, th) { // eslint-disable-line
      const matches = $(th).text().match(regex); // eslint-disable-line

      raceIds.push(parseInt(matches[0].replace('Race ', ''), 0));
    });

    $wpsTbls.each(function(i, tbl) {
      const $tbl = $(tbl); // eslint-disable-line
      const $xbl = $($exTbls[i]); // eslint-disable-line
      const race = {
        id: raceIds[i],
        wps: [],
        exotics: []
      };

      const $rows = $tbl.find('tr:gt(1)');

      if ($rows.length === 0) {
        return null; // No results data
      }

      $rows.each(function(j, row) {
        const $row = $(row); // eslint-disable-line
        const payout = {
          place: j + 1
        };

        const winAmountDisplay = $row.find('td:eq(1)').text().replace(/^[\.]$/ig, '');
        const placeAmountDisplay = $row.find('td:eq(2)').text().replace(/^[\.]$/ig, '');
        const showAmountDisplay = $row.find('td:eq(3)').text().replace(/^[\.]$/ig, '');

        payout.horse = $row.find('td:eq(0)').text();
        payout.winAmount = parseFloat(winAmountDisplay.replace(/[^0-9\.]+/ig, ''), 0) || 0;
        payout.placeAmount = parseFloat(placeAmountDisplay.replace(/[^0-9\.]+/ig, ''), 0) || 0;
        payout.showAmount = parseFloat(showAmountDisplay.replace(/[^0-9\.]+/ig, ''), 0) || 0;

        race.wps.push(payout);
      });

      const $xRows = $xbl.find('tr:gt(1)');

      $xRows.each(function(k, row) {
        const $row = $(row); // eslint-disable-line

        const payout = {};

        const wagerType = $row.find('td:eq(0)').text().trim();
        const winAmountDisplay = $row.find('td:eq(2)').text().trim();

        payout.type = wagerType.replace(/[\$0-9\. ]/ig, '');
        payout.denomination = parseFloat(wagerType.replace(/[^0-9\.]/ig, ''), 0) || 0;
        payout.winCombo = $row.find('td:eq(1)').text().split('/');
        payout.winAmount = parseFloat(winAmountDisplay.replace(/[^0-9\.]/ig, ''), 0) || 0;

        race.exotics.push(payout);
      });

      data.races.push(race);
    });

    return data;
  }

  function getUserInfo() {
    const obj = JSON.parse(sessionStorage.getItem('GlobalData')); // eslint-disable-line

    if (obj) {
      const key = Object.keys(obj).find(function(key) {
        return key.indexOf('classic') > -1;
      });
      const user = obj[key];

      return {
        email: user.email,
        firstName: user.firstName,
        lastName: user.lastName,
        accountNum: user.accountNum,
        authKey: user.authKey,
        isSyncing: pwnies && pwnies.isSyncing
      };
    }

    return {};
  }

  function getUserInfoSlim() {
    const data = getUserInfo();

    return {
      email: data.email,
      firstName: data.firstName,
      lastName: data.lastName,
      isSyncing: data.isSyncing
    };
  }
})();

{'fraction_non_alphanumeric': 0.15841955159564503, 'fraction_numerical': 0.00788975164976997, 'mean_word_length': 2.359535631862368, 'pattern_counts': {'":': 0, '<': 27, '<?xml version=': 0, '>': 33, 'https://': 2, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1685980', 'n_tokens_mistral': 16188, 'n_tokens_neox': 15605, 'n_words': 2560}