using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Controls;
using CommunityToolkit.Mvvm.Input;
using DynamicData;
using MarketData;
using MarketData.Cache;
using MarketData.DataAccess;
using MarketData.Generator;
using MarketData.Generator.GainLoss;
using MarketData.Generator.MovingAverage;
using MarketData.MarketDataModel;
using MarketData.MarketDataModel.GainLoss;
using MarketData.Utils;
using PortfolioManager.DataSeriesViewModels;
using PortfolioManager.Models;
using PortfolioManager.UIUtils;

namespace PortfolioManager.ViewModels
{
  public partial class GainLossViewModel : WorkspaceViewModel
  {
    private const String ALL = UIConstants.CONST_ALL;
    private enum Tasks { Accounts, SelectedSymbol, SelectedAccounts, LeastSquaresFit, UseDividends };
    private Dictionary<Tasks, Semaphore> semaphorePool = new Dictionary<Tasks, Semaphore>();
    private PortfolioTrades portfolioTrades = null;
    private ObservableCollection<String> symbols = new ObservableCollection<String>(new String[]{ALL}.ToList());
    private ObservableCollection<String> accounts = new ObservableCollection<String>(new String[]{ALL}.ToList());
    private String selectedSymbol = ALL;
    private String selectedAccount = ALL;
    private DateTime latestMarketDate;
    private bool isBusy = false;
    private bool suspendUpdate = false;
    private bool includeDividends = false;
    private String selectedCompanyName = String.Empty;
    private ITotalGainLossGenerator gainLossGenerator = null;
    private GainLossCompoundModelCollection gainLossModelCollection = null;
    private bool showAsGainLoss = true;
    private bool showActiveGainLoss = false;
    private ObservableCollection<GainLossCompoundModel> gainLossCompoundModelCollection = new ObservableCollection<GainLossCompoundModel>();
    private ObservableCollection<GainLossSummaryItem> gainLossSummaryItemCollection = new ObservableCollection<GainLossSummaryItem>();
    private IActiveGainLossGenerator activeGainLossGenerator = null;
    private TotalGainLossCollection totalGainLoss = null;  // total gain/loss
    private bool useCumulativeGainLoss = true;
    private bool showMarkers = false;
    private bool useLeastSquaresFit = true;
    private GainLossCompoundModel selectedGainLossCompoundItem = null;
    private GainLossSummaryItem selectedGainLossSummaryItem = null;

    public GainLossViewModel()
    {
      DisplayName = "GainLossView";
      semaphorePool.Add(Tasks.SelectedSymbol, new Semaphore(1, 1));
      semaphorePool.Add(Tasks.SelectedAccounts, new Semaphore(1, 1));
      semaphorePool.Add(Tasks.LeastSquaresFit, new Semaphore(1, 1));
      semaphorePool.Add(Tasks.Accounts, new Semaphore(1, 1));
      semaphorePool.Add(Tasks.UseDividends, new Semaphore(1, 1));
      PropertyChanged += OnViewModelPropertyChanged;
      Initialize();
    }

    protected override void OnDispose()
    {
      MDTrace.WriteLine(LogLevel.DEBUG,$"Dispose GainLossViewModel");
      base.OnDispose();
    }    

    public override String DisplayName
    {
      get
      {
        return "GainLossView";
      }
    }

    public override String Title
    {
      get
      {
        return DisplayName;
      }
    }

    public String PercentButtonText
    {
      get
      {
        if (!showAsGainLoss) return "Show $";
        else return "Show %";
      }
    }

    public String ActiveTotalButtonText
    {
      get
      {
        if (!showActiveGainLoss) return "Show Active G/L";
        else return "Show Total G/L";
      }
    }

    public bool ShowMarkers
    {
      get
      {
        return showMarkers;
      }
      set
      {
        showMarkers = value;
        base.OnPropertyChanged("ShowMarkers");
      }
    }

    private void OnViewModelPropertyChanged(object sender, PropertyChangedEventArgs eventArgs)
    {
      if (eventArgs.PropertyName.Equals("SelectedSymbol"))HandleSelectedSymbol();
      else if (eventArgs.PropertyName.Equals("Symbols"))HandleSymbols();
      else if (eventArgs.PropertyName.Equals("SelectedAccount"))HandleSelectedAccount();
      else if (eventArgs.PropertyName.Equals("LeastSquaresFit"))HandleLeastSquaresFit();
      else if (eventArgs.PropertyName.Equals("SelectedGainLossCompoundItem"))HandleSelectedGainLossCompoundItem();
      else if (eventArgs.PropertyName.Equals("CheckBoxUseCumulativeReturns"))HandleCheckBoxUseCumulativeReturns();
    }

    private void HandleSymbols()
    {
      SelectedSymbol = symbols[0];
    }

    private void HandleSelectedSymbol()
    {
      try
      {
        semaphorePool[Tasks.SelectedSymbol].WaitOne();
        gainLossCompoundModelCollection = null;
        gainLossSummaryItemCollection = null;
        if (suspendUpdate) return;
        suspendUpdate = true;
        MDTrace.WriteLine(LogLevel.DEBUG, $"HandleSelectedSymbol:{selectedSymbol}");
        if (String.IsNullOrEmpty(selectedSymbol) || String.IsNullOrEmpty(selectedAccount))
        {
          UpdateProperties();
          return;
        }
        MDTrace.WriteLine(LogLevel.DEBUG, String.Format("[GainLossViewModel::OnGainLossViewModelPropertyChanged]SelectedSymbol '{0}'", selectedSymbol));
        IsBusy = true;
        Task workerTask = Task.Factory.StartNew(() =>
        {
          DividendPayments dividendPayments = null;
          if (ALL.Equals(selectedSymbol))
          {
            portfolioTrades = PortfolioDA.GetTrades();
            if (includeDividends) dividendPayments = DividendPaymentDA.GetDividendPayments();
            selectedCompanyName = "";
          }
          else
          {
            portfolioTrades = PortfolioDA.GetTrades(selectedSymbol);
            if (includeDividends) dividendPayments = DividendPaymentDA.GetDividendPaymentsForSymbol(selectedSymbol);
            selectedCompanyName = PricingDA.GetNameForSymbol(selectedSymbol);
          }
          if (!ALL.Equals(selectedAccount))
          {
            portfolioTrades = portfolioTrades.FilterAccount(selectedAccount);
          }
          if (null != dividendPayments && !ALL.Equals(selectedAccount))
          {
            dividendPayments = dividendPayments.FilterAccounts(selectedAccount);
          }

          // check the TotalGainLoss generator and build appropriately                                
          InstantiateGenerators();
          LocalPriceCache.GetInstance().Refresh();
          GainLossCollection gainLoss = null;

          MDTrace.WriteLine(LogLevel.DEBUG, "GeneratingActiveGainLoss");

          // gainLoss will contain the gain/loss from active positions.  Never includes dividends .. just positions          
          gainLoss = activeGainLossGenerator.GenerateGainLoss(portfolioTrades);

          MDTrace.WriteLine(LogLevel.DEBUG, "GeneratingTotalGainLoss)");
          // Call the appropriate TotalGainLoss method depending on whether we should include dividends or not.
          if (includeDividends) totalGainLoss = gainLossGenerator.GenerateTotalGainLossWithDividends(portfolioTrades, dividendPayments);
          else totalGainLoss = gainLossGenerator.GenerateTotalGainLoss(portfolioTrades);

          TotalGainLossItem lastItem = totalGainLoss[totalGainLoss.Count - 1];
          MDTrace.WriteLine(LogLevel.DEBUG, $"Date:{lastItem.Date.ToShortDateString()} TotalGainLoss:{Utility.FormatCurrency(lastItem.TotalGainLoss, 2)}");

          gainLossModelCollection = null;
          gainLossModelCollection = new GainLossCompoundModelCollection(gainLoss, totalGainLoss);
          if (null != gainLossModelCollection)
          {
            gainLossModelCollection.Sort(new SortGainLossCompoundModelYearDescendingOrder());
            gainLossCompoundModelCollection = new ObservableCollection<GainLossCompoundModel>(gainLossModelCollection);
          }
          GainLossSummaryItemCollection gainLossSummaryItems = new GainLossSummaryItemCollection(portfolioTrades, gainLossGenerator, activeGainLossGenerator);
          gainLossSummaryItemCollection = new ObservableCollection<GainLossSummaryItem>(gainLossSummaryItems);
        });
        workerTask.ContinueWith((continuation) =>
        {
          IsBusy = false;
          UpdateProperties();
          suspendUpdate = false;
        });
      }
      finally
      {
        semaphorePool[Tasks.SelectedSymbol].Release();
      }
    }

    private void HandleSelectedAccount()
    {
      try
      {
        semaphorePool[Tasks.SelectedAccounts].WaitOne();

        Task workerTask = Task.Factory.StartNew(() =>
        {
          if (String.IsNullOrEmpty(selectedAccount)) return;
          if (ALL.Equals(selectedAccount))
          {
            portfolioTrades = PortfolioDA.GetTrades();
          }
          else
          {
            portfolioTrades = PortfolioDA.GetTradesForAccounts(new List<String>() { selectedAccount });
          }
        });
        workerTask.ContinueWith((continuation) =>
        {
          SetSymbols();
          SelectedSymbol = Symbols[0];
        });
      }
      finally
      {
        semaphorePool[Tasks.SelectedAccounts].Release();
      }
    }

    private void HandleLeastSquaresFit()
    {
      MDTrace.WriteLine(LogLevel.DEBUG, $"HandleLeastSquaresFit");
    }

    private void HandleSelectedGainLossCompoundItem()
    {
      if (null == selectedGainLossCompoundItem) return;
      IsBusy = true;
      Task workerTask = Task.Factory.StartNew(() =>
      {
        DateTime selectedDate = selectedGainLossCompoundItem.Date;
        PortfolioTrades tradesOnOrBefore = portfolioTrades.GetTradesOnOrBefore(selectedDate);
        GainLossSummaryItemCollection gainLossSummaryItems = new GainLossSummaryItemCollection(tradesOnOrBefore, gainLossGenerator, activeGainLossGenerator, selectedDate);
        gainLossSummaryItemCollection = new ObservableCollection<GainLossSummaryItem>(gainLossSummaryItems);
      });
      workerTask.ContinueWith((continuation) =>
      {
        IsBusy = false;
        base.OnPropertyChanged("GainLossSummaryItemCollection");
        UpdateSummaryProperties();
      });
    }

    private void HandleCheckBoxUseCumulativeReturns()
    {
      MDTrace.WriteLine(LogLevel.DEBUG, $"HandleCheckBoxUseCumulativeReturns");
    }

    // ********************************************************************************************************************************

    public void InstantiateGenerators()
    {
      if (null == gainLossGenerator)
      {
        if (useCumulativeGainLoss) gainLossGenerator = new GainLossGeneratorCum();
        else gainLossGenerator = new GainLossGenerator();
      }
      if (null == activeGainLossGenerator) activeGainLossGenerator = new ActiveGainLossGenerator();
    }

    private void Initialize()
    {
      IsBusy = true;
      suspendUpdate = true;
      semaphorePool[Tasks.Accounts].WaitOne();
      Task workerTask = Task.Factory.StartNew(() =>
      {
        latestMarketDate = PremarketDA.GetLatestMarketDate();
        portfolioTrades = PortfolioDA.GetTrades();
      });
      workerTask.ContinueWith((continuation) =>
      {
        SetAccounts();
        SetSymbols();
        selectedSymbol = symbols[0];
        selectedAccount = accounts[0];
        IsBusy = false;
        suspendUpdate = false;
        base.OnPropertyChanged("SelectedSymbol");
        semaphorePool[Tasks.Accounts].Release();
      });
    }

    public void SetAccounts()
    {
      if (null == portfolioTrades || 0 == portfolioTrades.Count) return;
      if (default != accounts.Where(x => x.Equals(ALL)).FirstOrDefault())
      {
        selectedAccount = accounts[0];
        IEnumerable<String> collectionToRemove = accounts.Where(x => !x.Equals(ALL)).ToList();
        accounts.Remove(collectionToRemove);
        accounts.AddRange(portfolioTrades.Accounts);
      }
      else
      {
        List<String> portfolioTradeAccounts = new List<String>(portfolioTrades.Accounts);
        portfolioTradeAccounts.Insert(0, ALL);
        accounts.AddRange(portfolioTradeAccounts);
        selectedAccount = accounts[0];
      }
    }

    public void SetSymbols()
    {
      if (null == portfolioTrades || 0 == portfolioTrades.Count) return;
      if (default != symbols.Where(x => x.Equals(ALL)).FirstOrDefault())
      {
        selectedSymbol = symbols[0];
        IEnumerable<String> collectionToRemove = symbols.Where(x => !x.Equals(ALL)).ToList();
        symbols.Remove(collectionToRemove);
        symbols.AddRange(portfolioTrades.Symbols);
      }
      else
      {
        List<String> tradedSymbols = new List<String>(portfolioTrades.Symbols);
        tradedSymbols.Insert(0, ALL);
        symbols.AddRange(tradedSymbols);
        selectedSymbol = symbols[0];
      }      
    }

    public String SelectedSymbol
    {
      get
      {
        return selectedSymbol;
      }
      set
      {
        if (null == value) return;
        selectedSymbol = value;
        base.OnPropertyChanged("SelectedSymbol");
      }
    }

    public String SelectedAccount
    {
      get
      {
        return selectedAccount;
      }
      set
      {
        if (null == value) return;
        selectedAccount = value;
        base.OnPropertyChanged("SelectedAccount");
      }
    }

    public ObservableCollection<String> Accounts
    {
      get
      {
        return accounts;
      }
    }

    public ObservableCollection<String> Symbols
    {
      get
      {
        return symbols;
      }
    }

    public String TotalGainLoss
    {
      get
      {
        if (null == totalGainLoss || 0 == totalGainLoss.Count)
        {
          return Constants.CONST_DASHES;
        }
        return Utility.FormatCurrency(totalGainLoss[totalGainLoss.Count - 1].TotalGainLoss, 2);
      }
    }

    // ************************************************************************************************************************************************

    public String SummaryDate
    {
      get
      {
        if (null == gainLossSummaryItemCollection || 0 == gainLossSummaryItemCollection.Count)
        {
          return Constants.CONST_DASHES;
        }
        return gainLossSummaryItemCollection[0].Date.ToShortDateString();
      }
    }

    public String SummaryChange
    {
      get
      {
        if (null == gainLossSummaryItemCollection)
        {
          return Constants.CONST_DASHES;
        }
        return Utility.FormatCurrency(gainLossSummaryItemCollection.Sum(x => x.Change), 2);
      }
    }

    public String SummaryGainLoss
    {
      get
      {
        if (null == gainLossSummaryItemCollection)
        {
          return Constants.CONST_DASHES;
        }
        return Utility.FormatCurrency(gainLossSummaryItemCollection.Sum(x => x.CurrentGainLoss), 2);
      }
    }

    public String SummaryPositions
    {
      get
      {
        if (null == gainLossSummaryItemCollection)
        {
          return Constants.CONST_DASHES;
        }
        return Utility.FormatNumber(gainLossSummaryItemCollection.Count(), 0, true);
      }
    }

    public bool CheckBoxIncludeDividends
    {
      get
      {
        return includeDividends;
      }
      set
      {
        includeDividends = value;
        base.OnPropertyChanged("CheckBoxIncludeDividends");
        base.OnPropertyChanged("SelectedSymbol");
      }
    }

    // ************************************************************************************************************************************************

    private void UpdateProperties()
    {
      base.OnPropertyChanged("TotalGainLoss");
      base.OnPropertyChanged("GraphTitle");
      base.OnPropertyChanged("Title");
      base.OnPropertyChanged("Data");
      base.OnPropertyChanged("MA21");
      base.OnPropertyChanged("MA55");
      base.OnPropertyChanged("MA90");
      base.OnPropertyChanged("LeastSquares");
      base.OnPropertyChanged("GainLossCompoundModelCollection");  // The compound model collection is the data behind the views left-hand grid.
      base.OnPropertyChanged("GainLossSummaryItemCollection");  // The summary item collection is the data behind the views right-hand grid. 
      base.OnPropertyChanged("Parity");
      base.OnPropertyChanged("DollarChangePercent");
      UpdateSummaryProperties();
    }

    private void UpdateSummaryProperties()
    {
      base.OnPropertyChanged("SummaryGainLoss");
      base.OnPropertyChanged("SummaryDate");
      base.OnPropertyChanged("SummaryChange");
      base.OnPropertyChanged("SummaryDate");
      base.OnPropertyChanged("SummaryPositions");
    }

    // *****************************************************************************************************************
    public bool IsBusy
    {
      get
      {
        return isBusy;
      }
      set
      {
        isBusy = value;
        base.OnPropertyChanged("IsBusy");
      }
    }

    // ********************************************* P E R S I S T E N C E *********************************************
    public override bool CanPersist()
    {
      return false;
    }

    public override SaveParameters GetSaveParameters()
    {
      return null;
    }

    public override void SetSaveParameters(SaveParameters saveParameters)
    {
    }

    // ************************************************** C H A R T  D A T A ********************************************
    public CompositeDataSource Data
    {
      get
      {
        if (null == gainLossModelCollection || 0 == gainLossModelCollection.Count) return GainLossModel.Empty();
        CompositeDataSource compositeDataSource = null;
        if (showActiveGainLoss) compositeDataSource = GainLossModel.GainLoss(gainLossModelCollection, showAsGainLoss);
        else compositeDataSource = GainLossModel.TotalGainLoss(gainLossModelCollection, showAsGainLoss);
        return compositeDataSource;
      }
    }

    public CompositeDataSource MA21
    {
      get
      {
        if (null == gainLossModelCollection || 0 == gainLossModelCollection.Count) return GainLossModel.Empty();
        CompositeDataSource compositeDataSource = null;
        DMAValues dmaValues = null;
        if (showActiveGainLoss)
        {
          if (showAsGainLoss) dmaValues = MovingAverageGenerator.GenerateMovingAverage(gainLossModelCollection.DMAValuesActiveGainLoss, 21);
          else dmaValues = MovingAverageGenerator.GenerateMovingAverage(gainLossModelCollection.DMAValuesActiveGainLossPercent, 21);
          compositeDataSource = MovingAverageModel.CreateCompositeDataSource(dmaValues);
        }
        else
        {
          if (showAsGainLoss) dmaValues = MovingAverageGenerator.GenerateMovingAverage(gainLossModelCollection.DMAValuesTotalGainLoss, 21);
          else dmaValues = MovingAverageGenerator.GenerateMovingAverage(gainLossModelCollection.DMAValuesTotalGainLossPercent, 21);
          compositeDataSource = MovingAverageModel.CreateCompositeDataSource(dmaValues);
        }
        return compositeDataSource;
      }
    }

    public CompositeDataSource MA55
    {
      get
      {
        if (null == gainLossModelCollection || 0 == gainLossModelCollection.Count) return GainLossModel.Empty();
        CompositeDataSource compositeDataSource = null;
        DMAValues dmaValues = null;
        if (showActiveGainLoss)
        {
          if (showAsGainLoss) dmaValues = MovingAverageGenerator.GenerateMovingAverage(gainLossModelCollection.DMAValuesActiveGainLoss, 55);
          else dmaValues = MovingAverageGenerator.GenerateMovingAverage(gainLossModelCollection.DMAValuesActiveGainLossPercent, 55);
          compositeDataSource = MovingAverageModel.CreateCompositeDataSource(dmaValues);
        }
        else
        {
          if (showAsGainLoss) dmaValues = MovingAverageGenerator.GenerateMovingAverage(gainLossModelCollection.DMAValuesTotalGainLoss, 55);
          else dmaValues = MovingAverageGenerator.GenerateMovingAverage(gainLossModelCollection.DMAValuesTotalGainLossPercent, 55);
          compositeDataSource = MovingAverageModel.CreateCompositeDataSource(dmaValues);
        }
        return compositeDataSource;
      }
    }

    public CompositeDataSource MA90
    {
      get
      {
        if (null == gainLossModelCollection || 0 == gainLossModelCollection.Count) return GainLossModel.Empty();
        CompositeDataSource compositeDataSource = null;
        DMAValues dmaValues = null;
        if (showActiveGainLoss)
        {
          if (showAsGainLoss) dmaValues = MovingAverageGenerator.GenerateMovingAverage(gainLossModelCollection.DMAValuesActiveGainLoss, 90);
          else dmaValues = MovingAverageGenerator.GenerateMovingAverage(gainLossModelCollection.DMAValuesActiveGainLossPercent, 90);
          compositeDataSource = MovingAverageModel.CreateCompositeDataSource(dmaValues);
        }
        else
        {
          if (showAsGainLoss) dmaValues = MovingAverageGenerator.GenerateMovingAverage(gainLossModelCollection.DMAValuesTotalGainLoss, 90);
          else dmaValues = MovingAverageGenerator.GenerateMovingAverage(gainLossModelCollection.DMAValuesTotalGainLossPercent, 90);
          compositeDataSource = MovingAverageModel.CreateCompositeDataSource(dmaValues);
        }
        return compositeDataSource;
      }
    }

    public CompositeDataSource LeastSquares
    {
      get
      {
        if (!useLeastSquaresFit || null == gainLossModelCollection || 0 == gainLossModelCollection.Count) return GainLossModel.Empty();
        if (showActiveGainLoss) return GainLossModel.LeastSquares(gainLossModelCollection, showAsGainLoss);
        else return GainLossModel.TotalLeastSquares(gainLossModelCollection, showAsGainLoss);
      }
    }


    // *********************************************  C H A R T   T I T L E ****************************************
    public String GraphTitle
    {
      get
      {
        if (null == gainLossModelCollection || 0 == gainLossModelCollection.Count)
        {
          return "GainLossView";
        }
        StringBuilder sb = new StringBuilder();
        sb.Append(showActiveGainLoss ? "Active" : "Total").Append(" ");
        sb.Append("Gain/Loss ");
        if (showAsGainLoss) sb.Append("($)");
        else sb.Append("(%)");
        String accounts = selectedAccount.Equals(ALL) ? Utility.ListToString(portfolioTrades.Accounts) : selectedAccount;
        if (!ALL.Equals(selectedSymbol)) sb.Append(" (").Append(accounts).Append(") ").Append(" - ").Append(selectedCompanyName).Append(" (").Append(selectedSymbol).Append(") ");
        else sb.Append(" (").Append(accounts).Append(") ").Append(" - ").Append(selectedCompanyName);

        String part = sb.ToString();
        if (part.Length >= 75)
        {
          sb = new StringBuilder();
          sb.Append(part.Substring(0, 75));
          sb.Append("...");
        }

        DateTime fromDate = gainLossModelCollection.Select(x => x.Date).Min();
        DateTime toDate = gainLossModelCollection.Select(x => x.Date).Max();
        sb.Append(" from ").Append(Utility.DateTimeToStringMMHDDHYYYY(fromDate));
        sb.Append(" Thru ").Append(Utility.DateTimeToStringMMHDDHYYYY(toDate));
        return sb.ToString();
      }
    }

    // *************************************************************************************************************************************************************************
    // ***************************************************************************** T A B L E  D A T A  ***********************************************************************
    // *************************************************************************************************************************************************************************
    // The compound model collection is the data behind the views left-hand grid.  This view shows the compound gain/loss since inception
    public ObservableCollection<GainLossCompoundModel> GainLossCompoundModelCollection
    {
      get
      {
        return gainLossCompoundModelCollection;
      }
    }

    // The summary model collection is the data behind the views right-hand grid.  This view shows the gain/loss for the selected date
    public ObservableCollection<GainLossSummaryItem> GainLossSummaryItemCollection
    {
      get
      {
        return gainLossSummaryItemCollection;
      }
    }

    // ****************************************************************************** G R I D   S E L E C T I O N S ***********************************************
    // The selected item in the compound model
    public GainLossCompoundModel SelectedGainLossCompoundItem
    {
      get
      {
        return selectedGainLossCompoundItem;
      }
      set
      {
        selectedGainLossCompoundItem = value;
        base.OnPropertyChanged("SelectedGainLossCompoundItem");
      }
    }

    // The selected item in the summary model
    public GainLossSummaryItem SelectedGainLossSummaryItem
    {
      get
      {
        return selectedGainLossSummaryItem;
      }
      set
      {
        selectedGainLossSummaryItem = value;
        base.OnPropertyChanged("SelectedGainLossSummaryItem");
        base.OnPropertyChanged("Parity");
        base.OnPropertyChanged("DollarChangePercent");
      }
    }

    // ********************************************* R E L A Y S ********************************************* 

    [RelayCommand]
    public async Task ToggleReturnOrPercent()
    {
      showAsGainLoss = !showAsGainLoss;
      base.OnPropertyChanged("SelectedSymbol");
      base.OnPropertyChanged("PercentButtonText");
      await Task.FromResult(true);
    }

    [RelayCommand]
    public async Task ToggleActiveOrTotal()
    {
      showActiveGainLoss = !showActiveGainLoss;
      base.OnPropertyChanged("SelectedSymbol");
      base.OnPropertyChanged("ActiveTotalButtonText");
      await Task.FromResult(true);
    }

    [RelayCommand]
    public async Task PerformRefresh()
    {
      latestMarketDate = PremarketDA.GetLatestMarketDate();
      base.OnPropertyChanged("SelectedSymbol");
      await Task.FromResult(true);
    }

    [RelayCommand]
    public async Task PerformReset()
    {
      SelectedAccount = ALL;
     await Task.FromResult(true);
    }

    // *********************************************************** M E N U  I T E M S ****************************************************
    public ObservableCollection<MenuItem> GainLossMenuItems
    {
      get
      {
        ObservableCollection<MenuItem> collection = new ObservableCollection<MenuItem>();
        collection.Add(new MenuItem() { Header = "Display Bollinger Band...", Command = BollingerBandsCommand, StaysOpenOnClick = false });
        return collection;
      }
    }

    [RelayCommand(CanExecute = nameof(CanExecuteBollingerBand))]
    public async Task BollingerBands()
    {
      if (null == selectedGainLossSummaryItem) return;
      SaveParameters saveParams = SaveParameters.Parse("Type,PortfolioManager.ViewModels.BollingerBandViewModel,SelectedSymbol," + selectedGainLossSummaryItem.Symbol + ",SelectedWatchList,{All},SelectedDayCount,180,SyncTradeToBand,FALSE");
      saveParams.Referer = this;
      WorkspaceInstantiator.Invoke(saveParams);
      await Task.FromResult(true); 
    }

    public bool CanExecuteBollingerBand()
    {
      return selectedGainLossSummaryItem == default ? false : true;
    }

    // ****************************************************** T O O L T I P S *********************************************************   
    public String DollarChangePercent
    {
      get
      {
        return "'$ Change(%)' is calculated as a percentage of dollar change in market value from the previous period to the current period.\nThis number may appear skewed if share count has changed day over day.";
      }
    }

    public String Parity
    {
      get
      {
        Profiler profiler = new Profiler();
        try
        {
          if (null == selectedGainLossSummaryItem || null == selectedGainLossSummaryItem.Symbol)
          {
            return "No row selected.";
          }
          StringBuilder sb = new StringBuilder();
          PortfolioTrades portfolioTrades = PortfolioDA.GetOpenTradesSymbol(selectedGainLossSummaryItem.Symbol);
          DateTime currentDate = PricingDA.GetLatestDate(selectedGainLossSummaryItem.Symbol);

          String companyName = PricingDA.GetNameForSymbol(selectedGainLossSummaryItem.Symbol);
          if (null != companyName) sb.Append(companyName).Append("\n");

          CompanyProfile companyProfile = CompanyProfileDA.GetCompanyProfile(selectedGainLossSummaryItem.Symbol);
          if (null != companyProfile)
          {
            sb.Append(companyProfile.Sector ?? Constants.CONST_QUESTION).Append("/").Append(companyProfile.Industry ?? Constants.CONST_QUESTION).Append("\n");
          }

          if (null != portfolioTrades && 0 != portfolioTrades.Count)
          {
            double shares = (from PortfolioTrade portfolioTrade in portfolioTrades select portfolioTrade.Shares).Sum();
            double exposure = portfolioTrades.Sum(x => x.Exposure());
            // Calculate the gain loss so that we can show the difference between our all time high percentage and where we are right now
            InstantiateGenerators();
            GainLossCollection gainLoss = activeGainLossGenerator.GenerateGainLoss(portfolioTrades);  // gainLoss contains the gain/loss from active positions.  Never includes dividends .. just positions
            GainLossItem maxGainLossItem = gainLoss.OrderByDescending(x => x.GainLossPercent).FirstOrDefault();
            GainLossItem minGainLossItem = gainLoss.OrderBy(x => x.GainLossPercent).FirstOrDefault();

            String accounts = Utility.ListToString(portfolioTrades.Accounts);
            sb.Append("You own '").Append(selectedGainLossSummaryItem.Symbol).Append("' in ").Append(portfolioTrades.Count).Append(" lot(s) (").Append(Utility.FormatNumber(shares, 0, true)).Append(" shares) ").Append("\n");
            sb.Append("Exposure: ").Append(Utility.FormatCurrency(exposure));
            sb.Append("\n").Append("Accounts: ").Append(accounts);

            ParityElement parityElement = ParityGenerator.GenerateBreakEven(selectedGainLossSummaryItem.Symbol);
            if (null != parityElement)
            {
              sb.Append("\n").Append(parityElement.ToString());
              sb.Append("\n").Append("All Time Gain/Loss: ").Append(maxGainLossItem.GainLossPercent < 0 ? "" : "+").Append(Utility.FormatPercent(maxGainLossItem.GainLossPercent / 100));
              sb.Append(" (").Append(minGainLossItem.GainLossPercent < 0 ? "" : "+").Append(Utility.FormatPercent(minGainLossItem.GainLossPercent / 100)).Append(")");
            }
          }
          else sb.Append("You don't hold any shares of '").Append(selectedGainLossSummaryItem.Symbol).Append("'");
          sb.Append(".");
          DateGenerator dateGenerator = new DateGenerator();
          DateTime priorDate = dateGenerator.FindPrevBusinessDay(currentDate);
          Price p1 = PricingDA.GetPrice(selectedGainLossSummaryItem.Symbol, currentDate);
          Price p2 = PricingDA.GetPrice(selectedGainLossSummaryItem.Symbol, priorDate);
          if (null == p2 && null != p1)
          {
            priorDate = dateGenerator.FindPrevBusinessDay(priorDate);
            p2 = PricingDA.GetPrice(selectedGainLossSummaryItem.Symbol, priorDate);
          }
          if (null == p1 || null == p2) return sb.ToString();
          sb.Append("\n");
          double change = (p1.Close - p2.Close) / p2.Close;
          sb.Append(String.Format("Latest Price {0} {1} ({2}{3})", Utility.DateTimeToStringMMSDDSYYYY(p1.Date), Utility.FormatCurrency(p1.Close), change < 0 ? "-" : "+", Utility.FormatPercent(Math.Abs(change))));
          if (companyProfile.FreezePricing) sb.Append(" ").Append("**Frozen**");
          sb.Append("\n").Append(String.Format("Source: {0}", p1.SourceAsString()));
          return sb.ToString();
        }
        finally
        {
          MDTrace.WriteLine(LogLevel.DEBUG, $"[Parity]Done, total took {profiler.End()}(ms)");
        }
      }
    }
  }
}