'use strict';

import { formatAmount } from "./SharePrices_Common.js";

export function createHoldingsTable(Instruments, vars) {
  function getHoldings() {
    function aggregateHoldings(i) {
      let aggUnits = 0;
      let aggBookCost = 0;
      let aggBookCostGBP = 0;
      let minPurchaseData = new Date().getTime();
      let maxPurchaseDate = 0;
      let marketIsOpen = Instruments.MarketIsOpen(i);
      let exchangeRate = Instruments.GetExchangeRate(i.Currency, 'GBP');
      let accountNames = [];
      let aggSingleDayGainGBP = 0;

      for (let hn = 0; hn < i.Holdings.length; hn++) {
        let h = i.Holdings[hn];

        if (h.SoldDate == null && h.NoUnits != 0) {
          aggUnits += h.NoUnits;
          aggBookCost += h.NoUnits * h.PurchasePricePerUnit;
          //aggBookCostGBP += h.NoUnits * h.PurchasePricePerUnit / exchangeRate;
          aggBookCostGBP += h.BookCostGBP;
          //bookCostPerAccount.addCategoryAmount(h.AccountName, (h.NoUnits * h.PurchasePricePerUnit) / exchangeRate);
          bookCostPerAccount.addCategoryAmount(h.AccountName, h.BookCostGBP);

          if (h.PurchaseDate < minPurchaseData) { minPurchaseData = h.PurchaseDate; }
          if (h.PurchaseDate > maxPurchaseDate) { maxPurchaseDate = h.PurchaseDate; }
          if (!accountNames.includes(h.AccountName)) { accountNames.push(h.AccountName); }

          if (i.CurrentPrice && exchangeRate) {
            aggSingleDayGainGBP += ((i.CurrentPrice - (h.PurchaseDate > i.SingleDayStartDate ? h.PurchasePricePerUnit : i.SingleDayPreviousClose)) * h.NoUnits) / exchangeRate;
            currentValuePerAccount.addCategoryAmount(h.AccountName, (h.NoUnits * i.CurrentPrice) / exchangeRate);
          }
        }
      }

      let aggPurchasePricePerUnit = aggBookCost / aggUnits;
      let aggCurrentValue = 0,
        aggCurrentValueGBP = 0,
        aggGainGBP = 0,
        aggSingleDayGainPercent = 0,
        aggSingleDayProfitOrLoss = 0;
      if (i.CurrentPrice) {
        aggCurrentValue = aggUnits * (i.Currency == 'GBp' ? i.CurrentPrice / 100 : i.CurrentPrice);
        if (exchangeRate) {
          aggCurrentValueGBP = (aggUnits * i.CurrentPrice) / exchangeRate;
          //aggGainGBP = ((i.CurrentPrice - aggPurchasePricePerUnit) * aggUnits) / exchangeRate;
          aggGainGBP = aggCurrentValueGBP - aggBookCostGBP;
          aggSingleDayGainPercent = ((i.CurrentPrice / i.SingleDayPreviousClose) - 1) * 100;
          aggSingleDayProfitOrLoss = aggSingleDayGainGBP < 0 ? 'loss' : 'profit';
        }
      }

      return {
        displayOrder: i.DisplayOrder,
        holdingNumber: 0,
        instrumentType: i.InstrumentType,
        instrumentName: i.InstrumentName,
        displayName: i.DisplayName,
        symbol: i.Symbol,
        currency: i.Currency,
        accountName: accountNames.length > 1 ? 'Multiple (' + accountNames.length + ')' : accountNames[0],
        accountNames: accountNames.length > 1 ? accountNames.join(', ') : '',
        purchaseDateMin: minPurchaseData,
        purchaseDateMax: maxPurchaseDate,
        noUnits: aggUnits,
        purchasePricePerUnit: aggPurchasePricePerUnit,
        bookCost: aggBookCost,
        bookCostGBP: aggBookCostGBP,
        currentValue: aggCurrentValue,
        gain: (i.Currency == 'GBp' ? (i.CurrentPrice - aggPurchasePricePerUnit) / 100 : (i.CurrentPrice - aggPurchasePricePerUnit)) * aggUnits,
        //gainPercent: (i.CurrentPrice - aggPurchasePricePerUnit) / aggPurchasePricePerUnit * 100,
        gainPercent: aggGainGBP / aggBookCostGBP * 100,
        currentValueGBP: aggCurrentValueGBP,
        gainGBP: aggGainGBP,
        singleDayGainGBP: aggSingleDayGainGBP,
        singleDayGainPercent: aggSingleDayGainPercent,
        singleDayProfitOrLoss: aggSingleDayProfitOrLoss,
        currentPrice: i.CurrentPrice,
        currentExchangeRate: exchangeRate,
        marketIsOpen: marketIsOpen
      };
    }

    //Create a structure to summarise total holdings in each currency, to be used in the pie chart
    let holdingCurrencies = {
      data: [],
      indexOfHoldingCurrency: function (symbol) { return this.data.map(function (item) { return item.symbol; }).indexOf(symbol); },
      getHoldingCurrency: function (symbol) { return this.data[this.indexOfHoldingCurrency(symbol)]; },
      addHoldingCurrency: function (symbol, amount) {
        let c = this.getHoldingCurrency(symbol);
        if (c) {
          c.total += amount;
        } else {
          this.data.push({ symbol: symbol, total: amount });
        }
      },
      getChartData: function () {
        let d = []
        for (let i = 0; i < this.data.length; i++) {
          //d.push({ label: this.data[i].symbol, data: this.data[i].total / Instruments.GetExchangeRate(this.data[i].symbol, 'GBP') });
          d.push({ label: this.data[i].symbol, data: this.data[i].total });
        }
        return d;
      }
    };
    let holdingCurrenciesCurrentValue = {
      data: [],
      indexOfHoldingCurrency: function (symbol) { return this.data.map(function (item) { return item.symbol; }).indexOf(symbol); },
      getHoldingCurrency: function (symbol) { return this.data[this.indexOfHoldingCurrency(symbol)]; },
      addHoldingCurrency: function (symbol, amount) {
        let c = this.getHoldingCurrency(symbol);
        if (c) {
          c.total += amount;
        } else {
          this.data.push({ symbol: symbol, total: amount });
        }
      },
      getChartData: function () {
        let d = []
        for (let i = 0; i < this.data.length; i++) {
          d.push({ label: this.data[i].symbol, data: this.data[i].total / Instruments.GetExchangeRate(this.data[i].symbol, 'GBP') });
        }
        return d;
      }
    };
    let bookCostPerAccount = createCategoryAggregator();
    let currentValuePerAccount = createCategoryAggregator();
    let totalHoldingsValueGBP = 0;
    let totalBookCostGBP = 0;
    let totalValueGBP = 0;
    let totalGainGBP = 0;
    let todaysGainGBP = 0;
    let maxCurrentValueGBP = 0;
    let groupHoldings = $('#groupBySymbol').prop('checked');
    let r = [];
    let w = []; //Watchlist (instruments with no current holdings)

    for (let n = 0; n < Instruments.Data.length; n++) {
      let i = Instruments.Data[n];
      //if (i.InstrumentType == 'EQUITY') {
      if (i.InstrumentType == 'EQUITY' || i.InstrumentType == 'ETF' || i.InstrumentType == 'CRYPTOCURRENCY' || i.InstrumentType == 'CURRENCY') {
        //if (i.CurrentPrice) {
        if (groupHoldings) {
          let agg = aggregateHoldings(i);
          if (agg.noUnits > 0) {
            r.push(agg);

            //holdingCurrencies.addHoldingCurrency(i.Currency, agg.noUnits * agg.purchasePricePerUnit);
            //holdingCurrencies.addHoldingCurrency(i.Currency.toUpperCase(), agg.noUnits * agg.purchasePricePerUnit / (i.Currency == "GBp" ? 100 : 1));
            holdingCurrencies.addHoldingCurrency(i.Currency.toUpperCase(), agg.bookCostGBP);
            if (i.CurrentPrice) {
              if (agg.currentValueGBP > maxCurrentValueGBP) { maxCurrentValueGBP = agg.currentValueGBP };
              //holdingCurrenciesCurrentValue.addHoldingCurrency(i.Currency, agg.noUnits * i.CurrentPrice);
              holdingCurrenciesCurrentValue.addHoldingCurrency(i.Currency.toUpperCase(), agg.noUnits * i.CurrentPrice / (i.Currency == "GBp" ? 100 : 1));
              if (agg.currentExchangeRate) {
                totalBookCostGBP += agg.bookCostGBP;
                totalValueGBP += agg.currentValueGBP;
                totalGainGBP += agg.gainGBP;
                todaysGainGBP += (agg.marketIsOpen == 0 ? 0 : agg.singleDayGainGBP);
              }
            }
          } else {
            //No current holdings for this instrument - add it to the watchlist
            if (i.WatchlistID) {
              w.push(i);
            }
          }
        } else {
          let marketIsOpen = Instruments.MarketIsOpen(i);
          let exchangeRate = Instruments.GetExchangeRate(i.Currency, 'GBP');

          let noCurrentHoldings = 0;
          for (let hn = 0; hn < i.Holdings.length; hn++) {
            let h = i.Holdings[hn];
            if (h.SoldDate == null) {
              noCurrentHoldings++;
              let holdingBookCost = h.NoUnits * h.PurchasePricePerUnit;

              let currentHoldingValue = h.NoUnits * (i.Currency == 'GBp' ? i.CurrentPrice / 100 : i.CurrentPrice);

              let holdingCurrentValueGBP = 0,
                holdingGainGBP = 0,
                singleDayGainGBP = 0,
                singleDayGainPercent = 0,
                singleDayProfitOrLoss = 0;
              if (i.CurrentPrice) {
                let currentHoldingValue = h.NoUnits * (i.Currency == 'GBp' ? i.CurrentPrice / 100 : i.CurrentPrice);

                if (exchangeRate) {
                  let holdingCurrentValueGBP = (h.NoUnits * i.CurrentPrice) / exchangeRate;
                  //let holdingGainGBP = ((i.CurrentPrice - h.PurchasePricePerUnit) * h.NoUnits) / exchangeRate;
                  let holdingGainGBP = holdingCurrentValueGBP - h.BookCostGBP;

                  let singleDayGainGBP = ((i.CurrentPrice - (h.PurchaseDate > i.SingleDayStartDate ? h.PurchasePricePerUnit : i.SingleDayPreviousClose)) * h.NoUnits) / exchangeRate;
                  let singleDayGainPercent = ((i.CurrentPrice / i.SingleDayPreviousClose) - 1) * 100;
                  let singleDayProfitOrLoss = singleDayGainGBP < 0 ? 'loss' : 'profit';

                  if (holdingCurrentValueGBP > maxCurrentValueGBP) { maxCurrentValueGBP = holdingCurrentValueGBP };
                }
              }

              r.push({
                displayOrder: i.DisplayOrder,
                holdingNumber: hn,
                instrumentType: i.InstrumentType,
                instrumentName: i.InstrumentName,
                displayName: i.DisplayName,
                symbol: i.Symbol,
                currency: i.Currency,
                accountName: h.AccountName,
                purchaseDateMin: h.PurchaseDate,
                purchaseDateMax: h.PurchaseDate,
                noUnits: h.NoUnits,
                purchasePricePerUnit: h.PurchasePricePerUnit,
                bookCost: holdingBookCost,
                currentValue: currentHoldingValue,
                gain: (i.Currency == 'GBp' ? (i.CurrentPrice - h.PurchasePricePerUnit) / 100 : (i.CurrentPrice - h.PurchasePricePerUnit)) * h.NoUnits,
                //gainPercent: (i.CurrentPrice - h.PurchasePricePerUnit) / h.PurchasePricePerUnit * 100,
                gainPercent: holdingGainGBP / h.BookCostGBP * 100,
                currentValueGBP: holdingCurrentValueGBP,
                gainGBP: holdingGainGBP,
                singleDayGainGBP: singleDayGainGBP,
                singleDayGainPercent: singleDayGainPercent,
                singleDayProfitOrLoss: singleDayProfitOrLoss,
                currentPrice: i.CurrentPrice,
                currentExchangeRate: exchangeRate,
                marketIsOpen: marketIsOpen
              });

              holdingCurrencies.addHoldingCurrency(i.Currency, h.NoUnits * h.PurchasePricePerUnit);
              if (i.CurrentPrice) {
                holdingCurrenciesCurrentValue.addHoldingCurrency(i.Currency, h.NoUnits * i.CurrentPrice);
                if (exchangeRate) {
                  currentValuePerAccount.addCategoryAmount(h.AccountName, holdingCurrentValueGBP);
                  bookCostPerAccount.addCategoryAmount(h.AccountName, holdingBookCost / exchangeRate);

                  //totalBookCostGBP += holdingBookCost / exchangeRate;
                  totalBookCostGBP += h.BookCostGBP;
                  totalValueGBP += holdingCurrentValueGBP;
                  totalGainGBP += holdingGainGBP;
                  todaysGainGBP += (marketIsOpen == 0 ? 0 : singleDayGainGBP);
                }
              }
            }
            if (noCurrentHoldings == 0) {
              //No current holdings for this instrument - add it to the watchlist
              if (i.WatchlistID) {
                w.push(i);
              }
            }
          }
        }
        //} //if (i.CurrentPrice)
      }
    }

    return {
      totalBookCostGBP: totalBookCostGBP,
      totalValueGBP: totalValueGBP,
      totalGainGBP: totalGainGBP,
      todaysGainGBP: todaysGainGBP,
      maxCurrentValueGBP: maxCurrentValueGBP,
      holdings: r,
      watchlist: w,
      holdingCurrencies: holdingCurrencies,
      holdingCurrenciesCurrentValue: holdingCurrenciesCurrentValue,
      bookCostPerAccount: bookCostPerAccount,
      currentValuePerAccount: currentValuePerAccount
    };
  }
  function createTable() {
    let t = $("#tblMainHoldings");
    if (t.length) {
      return;
    } else {
      let tbl = '<table id="tblMainHoldings" class="mainHoldings"><thead>' +
        '<tr><th>Type</th>' +
        '<th>Name</th>' +
        '<th>Symbol</th>' +
        '<th>Ccy</th>' +
        '<th>Account</th>' +
        '<th>Buy Date</th>' +
        '<th>No Units</th>' +
        '<th>Buy<br>Price</th>' +
        '<th>Current<br>Price</th>' +
        '<th>Book<br>Cost</th>' +
        '<th>Current<br>Value</th>' +
        '<th>Gain</th>' +
        '<th>Gain £</th>' +
        '<th>Gain %</th>' +
        '<th>Current<br>Value £</th>' +
        '<th>Allocation</th>' +
        '<th>Today&apos;s<br>Gain £</th>' +
        '<th>Today&apos;s<br>Gain %</th>' +
        '</tr></thead><tbody id="tbMainHoldings"></tbody><tfoot id="tfMainHoldings"><tr><td colspan="18">&nbsp;</td></tr></tfoot></table>';

      //$('#holdingsDiv').html(tbl);
      $('#holdingsTableDiv').html(tbl);

      $("#tblMainHoldings").tablesorter({ ignoreCase: false });
      $("#tblMainHoldings").trigger("sorton", Cookies.get('sortHoldings'));
      $('#tblMainHoldings').on('sortEnd', function (event) {
        // Prints the current sort order to the console
        if (event.target.config.sortList.length > 3) {
          console.info({ "Setting sortHoldings Cookie (array too long?)": event.target.config.sortList });
        }
        Cookies.set('sortHoldings', event.target.config.sortList, { 'sameSite': 'strict' });
      });
    }
  }
  function createWatchlistTable() {
    let t = $("#tblWatchlist");
    if (t.length) {
      return;
    } else {
      let tbl = '<table id="tblWatchlist" class="watchlist"><thead>' +
        '<tr><th>Type</th>' +
        '<th>Name</th>' +
        '<th>Symbol</th>' +
        '<th>Ccy</th>' +
        '<th>Current Price</th>' +
        '<th>Today&apos;s Gain</th>' +
        '<th>Today&apos;s Gain %</th>' +
        '<th>Since Last Sold<th>' +
        '</tr></thead><tbody id="tbWatchlist"></tbody></table>';

      $('#watchlistTableDiv').html(tbl);

      $("#tblWatchlist").tablesorter({ ignoreCase: false });
      $("#tblWatchlist").trigger("sorton", Cookies.get('sortWatchlist'));
      $('#tblWatchlist').on('sortEnd', function (event) {
        // Prints the current sort order to the console
        Cookies.set('sorWatchlist', event.target.config.sortList, { 'sameSite': 'strict' });
      });
    }
  }
  function addTableRow(rowID, content, tbodyID, highlightUpdates) {
    let r = $("#" + rowID);
    let newText = $(content).text(); //$(content).text();
    if (r.length) {
      let currentContent = r.text();
      if (currentContent != newText) { //Only update row if the content has changed
        $("#" + rowID).replaceWith(content);
        if (highlightUpdates) {
          $("#" + rowID).addClass("highlighted");
        }
      }
    } else {
      $("#" + tbodyID).append(content); //.addClass("highlighted");
      //$("#" + rowID).addClass("highlighted");
    }
  }
  function getPercentCell(backgroundColor, cellWidth, valuePercent, label) {
    let labelText = label ? label : (valuePercent.toFixed(1) + '%');
    let barWidth = ((valuePercent / 100) * cellWidth);
    return '<td>' +
      '<div class="pcBackground" style="background-color: ' + backgroundColor + '; width:' + barWidth.toFixed(0) + 'px;">&nbsp;</div>' +
      '<div class="pcLabel" style="width:' + cellWidth + 'px">' + labelText + '</div>' +
      '</td>';
  }
  function profitOrLoss(value) {
    return value >= 0 ? "profit" : "loss";
  }

  let t = getHoldings();
  let totalBookCostGBP = t.totalBookCostGBP;
  let totalValueGBP = t.totalValueGBP;
  let totalGainGBP = t.totalGainGBP;
  let todaysGainGBP = t.todaysGainGBP;
  let maxCurrentValueGBP = t.maxCurrentValueGBP;
  let holdings = t.holdings;
  let watchlist = t.watchlist;
  let holdingCurrencies = t.holdingCurrencies;
  let holdingCurrenciesCurrentValue = t.holdingCurrenciesCurrentValue;
  let bookCostPerAccount = t.bookCostPerAccount;
  let currentValuePerAccount = t.currentValuePerAccount;

  let maxAllocationPercent = (maxCurrentValueGBP / totalValueGBP) * 100;
  let allocationScaleFactor = 100 / maxAllocationPercent;

  let altRow = 1;

  createTable();
  for (let n = 0; n < holdings.length; n++) {
    try {
      altRow = !altRow;

      let h = holdings[n];
      let type = h.instrumentType == 'CURRENCY' ? '$' : h.instrumentType.substring(0, 1);
      //let profitOrLoss = h.currentPrice < h.purchasePricePerUnit ? 'loss' : 'profit'; //let profitOrLoss = i.CurrentPrice < h.PurchasePricePerUnit ? 'loss' : 'profit';
      let openOrClosed = h.marketIsOpen == 1 ? 'open' : 'closed';
      let allocationPercent = h.currentValueGBP / totalValueGBP * 100;
      let scaledAllocation = allocationScaleFactor * allocationPercent;

      let rowID = "holdingRow_" + h.displayOrder + "_" + h.holdingNumber;
      let row = '<tr' + (altRow ? ' class="altShareRow"' : '') + ' id="' + rowID + '">' +
        '<td class="' + openOrClosed + '">' + type + '</td>' +
        '<td class="' + openOrClosed + '">' + (h.displayName != '' ? h.displayName : h.instrumentName) + '</td>' +
        '<td><a target="_blank" href="https://uk.finance.yahoo.com/quote/' + h.symbol + '">' + h.symbol + '</a>' + '</td>' +
        '<td class="' + openOrClosed + '">' + h.currency + '</td>' +
        '<td class="' + openOrClosed + '" title="' + (h.accountNames ? h.accountNames : '') + '">' + h.accountName + '</td>' +
        '<td class="' + openOrClosed + '"' + (h.purchaseDateMin == h.purchaseDateMax ? ('>' + new Date(h.purchaseDateMin).yyyymmdd()) : (' title="' + new Date(h.purchaseDateMax).yyyymmdd() + ' - ' + new Date(h.purchaseDateMin).yyyymmdd() + '">' + new Date(h.purchaseDateMin).yyyymmdd()) + '+') + '</td>' +
        //'<td class="num ' + openOrClosed + '">' + formatAmount(h.noUnits, '', 0) + '</td>' +
        '<td class="num ' + openOrClosed + '">' + h.noUnits.autoScale() + '</td>' +
        '<td class="num ' + openOrClosed + '">' + formatAmount(h.purchasePricePerUnit, h.currency, 2) + '</td>' + //Buy Price
        '<td class="num ' + openOrClosed + '">' + (h.currentPrice ? formatAmount(h.currentPrice, h.currency, 2) : '') + '</td>' + //Current Price
        '<td class="num ' + openOrClosed + '">' + formatAmount(h.currency == 'GBp' ? h.bookCost / 100 : h.bookCost, h.currency == 'GBp' ? 'GBP' : h.currency, 0) + '</td>' + //Book Cost
        '<td class="num ' + openOrClosed + '">' + (h.currentExchangeRate ? formatAmount(h.currentValue, h.currency == 'GBp' ? 'GBP' : h.currency, 0) : '') + '</td>' + //Current Value
        '<td class="num ' + profitOrLoss(h.currentPrice - h.purchasePricePerUnit) + '">' + (h.currentPrice ? formatAmount(h.gain, h.currency == 'GBp' ? 'GBP' : h.currency, 0) : '') + '</td>' + //Gain
        '<td class="num ' + profitOrLoss(h.gainGBP) + '">' + (h.currentExchangeRate ? formatAmount(h.gainGBP, 'GBP', 0) : '') + '</td>' + //Gain £
        '<td class="num ' + profitOrLoss(h.gainGBP) + '">' + (h.currentExchangeRate ? h.gainPercent.toFixed(1) + '%' : '') + '</td>' + //Gain %
        '<td class="num ' + openOrClosed + '">' + (h.currentExchangeRate ? formatAmount(h.currentValueGBP, 'GBP', 0) : '') + '</td>' + //Current Value £
        (h.currentExchangeRate ? getPercentCell('#3f2d95', 70, scaledAllocation, allocationPercent.toFixed(1) + '%') : '') + //Allocation
        (h.currentExchangeRate ? (h.marketIsOpen == 0 ? '<td/>' : '<td class="num ' + h.singleDayProfitOrLoss + '">' + formatAmount(h.singleDayGainGBP, 'GBP', 0) + '</td>') : '') + //Today's Gain £
        (h.singleDayGainPercent ? (h.marketIsOpen == 0 ? '<td/>' : '<td class="num ' + h.singleDayProfitOrLoss + '">' + h.singleDayGainPercent.toFixed(1) + '%</td>') : '<td/>') + //Today's Gain %
        '</tr>';
      addTableRow(rowID, row, "tbMainHoldings", true);
    }
    catch (err) {
      logError("Error adding " + holdings[n].symbol + " to main holdings table: " + err.message);
    }
  }

  //Add the totals line at the bottom of the table
  //let profitOrLoss = totalValueGBP < totalBookCostGBP ? 'loss' : 'profit';
  let todaysProfitOrLoss = todaysGainGBP < 0 ? 'loss' : 'profit';
  let lSF = '';
  if (vars.fetchTiming.lastSuccessfulFetch) {
    let lastFetchAge = ((new Date()) - vars.fetchTiming.lastSuccessfulFetch);
    let staleFetch = ((new Date()) - vars.fetchTiming.lastSuccessfulFetch) > (vars.fetchTiming.fetchIntervalSingleDay * 1.5);
    lSF = (vars.fetchTiming.lastSuccessfulFetch ? ' - Last Successful Fetch: ' + (staleFetch ? '<span class="loss">' : '') + vars.fetchTiming.lastSuccessfulFetch.yyyymmddhhmmss() + (staleFetch ? '</span>' : '') : '');
  }
  let row = '<tr id="tfHoldingsTotals"><td></td><td id="holdingsLastUpdated" colspan="8">Updated: ' + new Date().yyyymmddhhmmss() + lSF + '</td>' +
    '<td class="num">' + formatAmount(totalBookCostGBP, 'GBP', 0) + '</td>' + //Book Cost
    '<td></td><td></td>' +
    '<td class="num ' + profitOrLoss(totalValueGBP - totalBookCostGBP) + '">' + formatAmount(totalGainGBP, 'GBP', 0) + '</td>' + //Gain GBP
    '<td class="num ' + profitOrLoss(totalValueGBP - totalBookCostGBP) + '">' + formatAmount((((totalValueGBP / totalBookCostGBP) - 1) * 100), '', 1) + '%</td>' + //Gain %
    '<td class="num">' + formatAmount(totalValueGBP, 'GBP', 0) + '</td>' + //Current Value
    '<td>&nbsp;</td>' + //Allocation
    '<td class="num ' + todaysProfitOrLoss + '">' + formatAmount(todaysGainGBP, 'GBP', 0) + '</td>' + //Today's Gain GBP
    //'<td class="num ' + todaysProfitOrLoss + '">' + formatAmount(((todaysGainGBP / totalBookCostGBP) * 100), '', 1) + '%</td>' + //Today's Gain %
    '<td class="num ' + todaysProfitOrLoss + '">' + formatAmount(((todaysGainGBP / (totalValueGBP - todaysGainGBP)) * 100), '', 1) + '%</td>' + //Today's Gain %
    '</tr>';
  addTableRow("tfHoldingsTotals", row, "tfMainHoldings", false);

  //Create the watchlist table
  createWatchlistTable();
  altRow = 1;
  for (let n = 0; n < watchlist.length; n++) {
    altRow = !altRow;
    let wi = watchlist[n];
    try {
      let type = wi.InstrumentType == 'CURRENCY' ? '$' : wi.InstrumentType.substring(0, 1);
      let openOrClosed = Instruments.MarketIsOpen(wi) == 1 ? 'open' : 'closed';
      let profitOrLoss = wi.CurrentPrice < wi.SingleDayPreviousClose ? 'loss' : 'profit';
      let rowID = "watchlistRow_" + wi.DisplayOrder;

      let sinceLastSold = '<td></td>';
      if (wi.LastSoldPrice) {
        let delta = (wi.CurrentPrice - wi.LastSoldPrice) / wi.LastSoldPrice * 100;
        let tooltip = new Date(wi.LastSoldDate).yyyymmdd() + ' @ ' + wi.LastSoldPrice.toFixed(2);
        sinceLastSold = '<td title="' + tooltip + '" class="num ' + (delta < 0 ? 'loss' : 'profit') + '">' + (delta).toFixed(1) + '%</td>';
      }

      //Type, Name, Symbol, Ccy, Current Price, Today's Gain, Today's Gain %
      let row = '<tr' + (altRow ? ' class="altShareRow"' : '') + ' id="' + rowID + '">' +
        '<td class="' + openOrClosed + '">' + type + '</td>' +
        '<td class="' + openOrClosed + '">' + wi.InstrumentName + '</td>' +
        '<td><a target="_blank" href="https://uk.finance.yahoo.com/quote/' + wi.Symbol + '">' + wi.Symbol + '</a>' + '</td>' +
        '<td class="' + openOrClosed + '">' + wi.Currency + '</td>' +
        '<td class="num ' + openOrClosed + '">' + (wi.CurrentPrice ? formatAmount(wi.CurrentPrice, wi.Currency, 2) : '') + '</td>' + //Current Price
        (Instruments.marketIsOpen == 0 ? '<td/>' : '<td class="num ' + profitOrLoss + '">' + (wi.CurrentPrice ? formatAmount(wi.CurrentPrice - wi.SingleDayPreviousClose, wi.Currency, 2) : '') + '</td>') + //Today's Gain
        (Instruments.marketIsOpen == 0 ? '<td/>' : '<td class="num ' + profitOrLoss + '">' + (wi.CurrentPrice ? ((wi.CurrentPrice - wi.SingleDayPreviousClose) / wi.SingleDayPreviousClose * 100).toFixed(1) : '') + '</td>') + //Today's Gain %
        //'<td>' + (new Date(wi.LastSoldDate)).yyyymmdd() + '</td>' +
        sinceLastSold +
        '</tr>';
      addTableRow(rowID, row, "tbWatchlist", true);
    }
    catch (err) {
      logError("Error adding " + wi.symbol + " to watchlist table: " + err.message);
    }
  }

  //Flash the last updated cell
  $("#holdingsLastUpdated").addClass("highlighted");
  //Remove the highlight from all rows/cells
  $("#tblMainHoldings").find(".highlighted").removeClass("highlighted", 1200);
  $("#tblWatchlist").find(".highlighted").removeClass("highlighted", 1200);

  //Tell tablesorter that the table has been updated
  var resort = true;
  $("#tblMainHoldings").trigger("update", [resort]);

  //Update the daily holdings value table in the DB
  if (totalValueGBP != vars.lastTotalHoldingsValue) {
    $.ajax({
      type: "POST",
      contentType: "application/json",
      url: "SharePrices.aspx/SetTotalHoldingsValue",
      dataType: "json",
      data: JSON.stringify({ TotalValue: totalValueGBP }),
      success: function (response) {
        vars.previousDay = response.PreviousDay;
        vars.previousClose = response.PreviousClose;
        vars.previousWeek = response.PreviousWeekDate;
        vars.previousWeekClose = response.PreviousWeekClose;

        //Update the total holdings span in the site banner
        let formattedAmount = formatAmount(totalValueGBP, "GBP", 0);
        let holdingsHTML = '<span style="font-size: small;">Total holdings:&nbsp;</span>' + '<span style="font-size: large">' + formattedAmount + '</span>&nbsp;';
        holdingsHTML += '<span style="font-size: small;"><span class="' + (vars.previousClose < totalValueGBP ? 'profit">+' : 'loss">') + formatAmount(totalValueGBP - vars.previousClose, "GBP", 0) + '</span>&nbsp;(d)';
        holdingsHTML += '&nbsp;/&nbsp;<span class="' + (vars.previousWeekClose < totalValueGBP ? 'profit">+' : 'loss">') + formatAmount(totalValueGBP - vars.previousWeekClose, "GBP", 0) + '</span>&nbsp;(w)</span>';
        $("#spnTotalHoldings").html(holdingsHTML);
        //Set the page title
        document.title = formattedAmount + ' - ' + vars.initialPageTitle;

        vars.lastTotalHoldingsValue = totalValueGBP;
      },
      failure: function (response) { console.error("SetTotalHoldingsValue error: " + JSON.stringify(response)); }
    });
  }

  //Create / update the currency allocation pie chart
  function labelFormatter(label, series) {
    return "<div class='pieLabel'>" + label + "<br/>" + Math.round(series.percent) + "%<br>" + formatAmount(series.data[0][1], 'GBP', 0) + "</div>";
  }
  let ops = {
    series: {
      downsample: { threshold: 0 },
      pie: { show: true, radius: 1, label: { show: true, radius: 0.7, formatter: labelFormatter } }
    },
    legend: { show: false },
    grid: { hoverable: true },
    tooltip: {
      show: true,
      content: "%p.0%, %s, n=%n", // show percentages, rounding to 2 decimal places
      shifts: { x: 20, y: 0 },
      defaultTheme: false
    }
  };
  let cd = holdingCurrencies.getChartData();
  if (cd.length) {
    $.plot('#divHoldingCurrenciesChart', cd, ops);
  }

  cd = holdingCurrenciesCurrentValue.getChartData();
  if (cd.length) {
    $.plot('#divHoldingCurrenciesChartCurrentValue', cd, ops);
  }

  function labelFormatter2(label, series) {
    return "<div class='pieLabel'>" + label + "<br/>" + formatAmount(series.data[0][1], 'GBP', 0) + "</div>";
  }
  ops.series.pie.label.formatter = labelFormatter2;
  cd = bookCostPerAccount.getChartData();
  if (cd.length) {
    $.plot('#divAccountBookCost', cd, ops);
  }

  cd = currentValuePerAccount.getChartData();
  if (cd.length) {
    $.plot('#divAccountValue', cd, ops);
  }
}
function createCategoryAggregator() {
  let aggregator = {
    data: [],
    indexOfCategory: function (categoryName) { return this.data.map(function (item) { return item.categoryName; }).indexOf(categoryName); },
    getCategory: function (categoryName) { return this.data[this.indexOfCategory(categoryName)]; },
    addCategoryAmount: function (categoryName, amount) {
      let c = this.getCategory(categoryName);
      if (c) {
        c.total += amount;
      } else {
        this.data.push({ categoryName: categoryName, total: amount });
      }
    },
    getChartData: function () {
      let d = []
      for (let i = 0; i < this.data.length; i++) {
        d.push({ label: this.data[i].categoryName, data: this.data[i].total });
      }
      return d;
    }
  };
  return aggregator;
}
