using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MarketData.Utils;
using MarketData.DataAccess;
using MarketData.Generator;

namespace MarketData.MarketDataModel
{
// ******************************************************************************************************************************
  public class Position
  {
    public String Symbol { get; set; }
    public double Shares{get;set;}
    public double Exposure { get; set; }
    public double MarketValue { get; set; }
  }
// *******************************************************************************************************************************
  public class PortfolioTrades : List<PortfolioTrade>
  {
    public PortfolioTrades()
    {
    }
    public PortfolioTrades(List<PortfolioTrade> trades)
    {
      if(null==trades)return;
      for (int index = 0; index < trades.Count; index++) Add(trades[index]);
    }
    public double GetWeightAdjustedDividendYield()
    {
      PortfolioTrades portfolioTrades=new PortfolioTrades((from PortfolioTrade portfolioTrade in this where portfolioTrade.IsOpen select portfolioTrade).ToList());
      double totalExposure=portfolioTrades.Sum(x=>x.Exposure());
      double weightAdjustedYield=double.NaN;
      foreach(PortfolioTrade portfolioTrade in portfolioTrades)
      {
        Price price=new Price();
        price.Close=portfolioTrade.Price;
        price.Date=DateTime.Now;
        double dividendYield=DividendHistoryGenerator.GetDividendYield(portfolioTrade.Symbol,price);
        if(double.IsNaN(dividendYield))continue;
        if(double.IsNaN(weightAdjustedYield))weightAdjustedYield=0.00;
        weightAdjustedYield+=dividendYield*((portfolioTrade.Exposure()/totalExposure));
      }
      return weightAdjustedYield;
    }
    public List<String> Symbols
    {
      get
      {
        List<String> symbols=new List<String>();
        foreach(PortfolioTrade portfolioTrade in this) symbols.Add(portfolioTrade.Symbol);
        symbols=symbols.Distinct().ToList();
        symbols.Sort();
        return symbols;
      }
    }
    public List<Position> GetPositions(DateTime asOf)
    {
      List<Position> positions = new List<Position>();
      List<String> symbols = Symbols;
      foreach (String symbol in symbols) positions.Add(GetPosition(symbol, asOf));
      return positions;
    }
    public Position GetPosition(String symbol,DateTime asof)
    {
      List<PortfolioTrade> portfolioTrades = new List<PortfolioTrade>();
      for (int index = 0; index < Count; index++)
      {
        PortfolioTrade portfolioTrade = this[index];
        if (!portfolioTrade.Symbol.Equals(symbol)) continue;
        if (portfolioTrade.IsOpen && portfolioTrade.TradeDate<=asof) { portfolioTrades.Add(portfolioTrade); continue; }
        if (portfolioTrade.IsClosed && portfolioTrade.SellDate > asof) { portfolioTrades.Add(portfolioTrade); continue; }
      }
      if (0 == portfolioTrades.Count) return null;
      Position position = new Position();
      position.Symbol = symbol;
      position.Shares = portfolioTrades.Sum(x => x.Shares);
      position.Exposure = portfolioTrades.Sum(x=>x.Shares*x.Price);
      return position;
    }
    public double Exposure(){return this.Sum(x=>x.Exposure());}
    public List<String> Accounts
    {
      get
      {
        Dictionary<String, String> uniqueAccounts = new Dictionary<String, String>();
        List<String> accounts = new List<String>();
        foreach (PortfolioTrade portfolioTrade in this)
        {
          if (!uniqueAccounts.ContainsKey(portfolioTrade.Account)) uniqueAccounts.Add(portfolioTrade.Account, portfolioTrade.Account);
        }
        accounts = new List<String>(uniqueAccounts.Values);
        accounts.Sort();
        return accounts;
      }
    }
    public DateTime GetMinTradeDate()
    {
      return this.Min(x => x.TradeDate);
    }

		public DateTime GetMinTradeDate(String symbol)
		{
			DateTime minDate=Utility.Epoch;
			symbol=symbol.ToUpper();
      minDate = (from portfolioTrade in this where portfolioTrade.Symbol.Equals(symbol) select portfolioTrade.TradeDate).Min();
      return minDate;
		}

// We just want the trades (open or closed) on or before the specified date.   This will be used to run a cumulative gain/loss and return
    public PortfolioTrades GetTradesOnOrBefore(DateTime date)
    {
      PortfolioTrades tradesOnOrBefore = new PortfolioTrades();
      foreach (PortfolioTrade portfolioTrade in this)
      {
        if (date >= portfolioTrade.TradeDate) tradesOnOrBefore.Add(portfolioTrade);
      }
      return tradesOnOrBefore;
    }
// This will remove trades that are status='CLOSED' and SELL_DATE=closingDate
// The user of this fucntion would be making the inference that a trade that closed on closingDate should not be seen as part of the holdings.
// For example.  The assumption would be that trades that closed on closingDate would not be a part of the portfolio on closingDate
    public PortfolioTrades RemoveClosedTradesWhereClosedOn(DateTime closingDate)
    {
      PortfolioTrades portfolioTrades;
      List<PortfolioTrade> removeTrades = (from PortfolioTrade portfolioTrade in this where portfolioTrade.Status.Equals("CLOSED") && portfolioTrade.SellDate.Date.Equals(closingDate.Date) select portfolioTrade).ToList();
      if (null != removeTrades && 0 != removeTrades.Count) portfolioTrades = new PortfolioTrades(this.Except(removeTrades).ToList());
      else portfolioTrades = this;
      return portfolioTrades;
    }
// Get trades that are open "asof" this date.  This does not mean trades that were purchased on this date.  This means trades that were in an open status on this date.
    public PortfolioTrades GetOpenTradesOn(DateTime date)
    {
      PortfolioTrades openTrades = new PortfolioTrades();
      foreach (PortfolioTrade portfolioTrade in this)
      {
        if (Utility.Epoch.Equals(portfolioTrade.SellDate))  // No sell date so trade is open
        {
          if (date >= portfolioTrade.TradeDate) openTrades.Add(portfolioTrade);
        }
        else  // sell date is not epoch so see if date is in between tradedate and selldate
        {    
          if (date >= portfolioTrade.TradeDate && date < portfolioTrade.SellDate) openTrades.Add(portfolioTrade);  // assume that if the sell date is equal to date then the position is closed
        }
      }
      return openTrades;
    }
// Get trades that were sold ON THIS DATE
    public PortfolioTrades GetClosedTradesOn(DateTime date)
    {
      PortfolioTrades closedTrades=new PortfolioTrades();
      foreach(PortfolioTrade portfolioTrade in this)
      {
        if(!Utility.Epoch.Equals(portfolioTrade.SellDate) && portfolioTrade.SellDate.Equals(date))  
        {
          closedTrades.Add(portfolioTrade); 
        }
      }
      return closedTrades;
    }
    public static List<DateTime> GetOpenTradeDates()
    {
      List<DateTime> openTradeDates = new List<DateTime>();
      try
      {
        PortfolioTrades portfolioTrades = PortfolioDA.GetTrades();
        DateGenerator dateGenerator = new DateGenerator();
        DateTime minTradeDate = PortfolioDA.GetMinTradeDate();
        DateTime maxTradeDate = PricingDA.GetLatestDate();    // use the latest date for which we have pricing information
        List<DateTime> historicalDates = PricingDA.GetPricingDates(minTradeDate);
        foreach (DateTime historicalDate in historicalDates)
        {
          PortfolioTrades trades = portfolioTrades.GetOpenTradesOn(historicalDate);
          if (null == trades || 0 == trades.Count) continue;
          openTradeDates.Add(historicalDate);
        }
        return openTradeDates;
      }
      catch (Exception exception)
      {
        MDTrace.WriteLine(LogLevel.DEBUG, exception);
        return openTradeDates;
      }
      finally
      {
      }
    }
    public bool HasOpenPositions(String symbol)
    {
      int openTrades=(from PortfolioTrade portfolioTrade in this where portfolioTrade.Symbol.Equals(symbol) && portfolioTrade.IsOpen select portfolioTrade).Count();
      return openTrades>0?true:false;
    }
    public bool HasOpenPositionsOn(String symbol,DateTime dateTime)
    {
      PortfolioTrades portfolioTrades=GetOpenTradesOn(dateTime);
      int numTrades=(from PortfolioTrade portfolioTrade in portfolioTrades where portfolioTrade.Symbol.Equals(symbol) select portfolioTrade).Count();
      return numTrades>0?true:false;
    }
// This method relies on the fact that BreakoutTrades method in PortfolioDA.GetTradesSymbol() creates pairs of BUY and SELL legs with paired legs sharing the same TradeId.
// The open trades will show up with count==1 when we group them by TradeId
    public PortfolioTrades GetOpenTrades()
    {
      if(0==Count)return new PortfolioTrades();
      PortfolioTrades openTrades=new PortfolioTrades((from PortfolioTrade trade in this select trade).GroupBy(x=>x.TradeId).Where(grouping=>grouping.Count()==1).Select(grouping=>grouping.FirstOrDefault()).ToList());
      return openTrades;
    }
    public PortfolioTrades FilterAccount(String account)
    {
      PortfolioTrades portfolioTrades = new PortfolioTrades();
      foreach (PortfolioTrade portfolioTrade in this)
      {
        if (portfolioTrade.Account.Equals(account)) portfolioTrades.Add(portfolioTrade);
      }
      return portfolioTrades;
    }
    public PortfolioTrades FilterAccount(List<String> accounts)
    {
      PortfolioTrades portfolioTrades = new PortfolioTrades();
      foreach (PortfolioTrade portfolioTrade in this)
      {
				if(accounts.Any(x=>x.Equals(portfolioTrade.Account)))portfolioTrades.Add(portfolioTrade);
      }
      return portfolioTrades;
    }
    public PortfolioTrades FilterSymbol(String symbol)
    {
      PortfolioTrades portfolioTrades = new PortfolioTrades();
      foreach (PortfolioTrade portfolioTrade in this)
      {
        if (portfolioTrade.Symbol.Equals(symbol)) portfolioTrades.Add(portfolioTrade);
      }
      return portfolioTrades;
    }
    public bool CheckPricingOn(DateTime date)
    {
      return PricingDA.CheckPricingOn(Symbols, date);
    }

// Returns a simple gain/loss which does not include any dividend distributions.
// portfolio trades should be a collection of distinct symbols
    public double? GetGainLoss(Price price)
    {
      double? gainLoss=null;
      List<String> symbols=(from PortfolioTrade portfolioTrade in this select portfolioTrade.Symbol).Distinct().ToList();
      PortfolioTrades openTrades=this.GetOpenTradesOn(DateTime.Now);
      if(1!=symbols.Count) return null;
      foreach(PortfolioTrade portfolioTrade in this)
      {
        if(null==gainLoss)
        {
          gainLoss=GetMarketValue(price,portfolioTrade)-GetExposure(price,portfolioTrade);
        }
        else
        {
          gainLoss+=(GetMarketValue(price,portfolioTrade)-GetExposure(price,portfolioTrade));
        }
      }
      return gainLoss;
    }
    private static double? GetMarketValue(Price price,PortfolioTrade portfolioTrade)
    {
      if(null==price) return null;
      if(price.Date<portfolioTrade.TradeDate) return null;
      if(!portfolioTrade.SellDate.Equals(Utility.Epoch)&&price.Date>portfolioTrade.SellDate) return null;
      return portfolioTrade.Shares*price.Close;
    }
    private static double? GetExposure(Price price,PortfolioTrade portfolioTrade)
    {
      if(null==price) return null;
      if(price.Date<portfolioTrade.TradeDate) return null;
      if(!portfolioTrade.SellDate.Equals(Utility.Epoch)&&price.Date>portfolioTrade.SellDate) return null;
      return portfolioTrade.Shares*portfolioTrade.Price;
    }

  // Collections

    public NVPCollections ToNVPCollections()
    {
      NVPCollections nvpCollections=new NVPCollections();
      foreach(PortfolioTrade portfolioTrade in this)
      {
        nvpCollections.Add(portfolioTrade.ToNVPCollection());
      }
      return nvpCollections;
    }
    public static PortfolioTrades FromNVPCollections(NVPCollections nvpCollections)
    {
      PortfolioTrades portfolioTrades=new PortfolioTrades();
      foreach(NVPCollection nvpCollection in nvpCollections)
      {
        portfolioTrades.Add(PortfolioTrade.FromNVPCollection(nvpCollection));
      }
      return portfolioTrades;
    }
  }
}