using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using MarketData.Utils;
using MarketData.Numerical;
//using MarketData.DataAccess;

namespace MarketData.MarketDataModel
{
  public class PricesByDate : Dictionary<DateTime, Price>
  {
    private DateTime maxDate=Utility.Epoch;
    private DateTime minDate=Utility.Epoch;
    public PricesByDate()
    {
    }
    public new void Add(DateTime key,Price price)
    {
      base.Add(key,price);
      if(key>maxDate)maxDate=key;
      else if(Utility.IsEpoch(minDate))minDate=key;
      else if(key<minDate)minDate=key;
    }
    public DateTime MaxDate
    {
      get{return maxDate;}
    }
    public DateTime MinDate
    {
      get{return minDate;}
    }
  }
  public class PriceComparerDesc : IComparer<Price>
  {
    public int Compare(Price p1, Price p2)
    {
      if (p1.Date < p2.Date) return -1;
      else if (p1.Date > p2.Date) return 1;
      return 0;
    }
  }
// Throughout the application it is assumed that these collections, when populated, be be in descending date order.
  public class Prices : List<Price>
  {
    public Prices()
    {
    }
    public Prices(Price[] prices)
    {
      foreach (Price price in prices) Add(price);
    }
    public Prices(List<Price> prices)
    {
      foreach(Price price in prices)Add(price);
    }
    public Prices(String strCSV,String symbol)
    {
      String[] csvLines = strCSV.Split('\n');
      Clear();
      for (int index = 1; index < csvLines.Length; index++)
      {
        if (csvLines[index].Length < 1) continue;
        String[] lineItems = csvLines[index].Split(',');
        Price price = new Price();
        String[] dateParts = lineItems[0].Split('-');
        try { price.Date = new DateTime(int.Parse(dateParts[0]), int.Parse(dateParts[1]), int.Parse(dateParts[2])); }
        catch (Exception /*exception*/)
        { 
          MDTrace.WriteLine(LogLevel.DEBUG,String.Format("'{0}' does not contain a date", lineItems[0])); 
          continue;
        }
        price.Symbol = symbol;
        price.Open = Double.Parse(lineItems[1]);
        price.High = Double.Parse(lineItems[2]);
        price.Low = Double.Parse(lineItems[3]);
        price.Close = Double.Parse(lineItems[4]);
        price.Volume = Int64.Parse(lineItems[5]);
        if(lineItems.Length>6)price.AdjClose=Double.Parse(lineItems[6]);
        Add(price);
      }
    }
// This Beta differs wildly with Yahoo Finance.  Yahoo finance uses 36 buckets of monthly returns which was simulated when building this Investopedia-based routine.
// I have yet to discover the cause of the difference but I leave this routine in place as a matter of further study and to make comparisons.
// I tested the bucket methodology against just using a stream of returns and they produce the same result so this routine simply uses a stream of returns.
// At any rate, the beta produced by this method does not match to Yahoo finance.
    //public double Beta(Prices bmkPrices)
    //{
    //  double beta=double.NaN;
    //  if(Count!=bmkPrices.Count)return beta;
    //  float[] cumReturnsPricesArray=GetReturns();
    //  float[] cumReturnsPricesBenchmark=bmkPrices.GetReturns();
    //  beta=Numerics.Beta(ref cumReturnsPricesArray,ref cumReturnsPricesBenchmark);
    //  return beta*10;
    //}
// Assumes that the prices are stored lowest date first
    public double MaxDrawdown()
    {
      return Numerics.MaxDrawdown(GetPrices());
    }
    public double MaxUpside()
    {
      return Numerics.MaxUpside(GetPrices());
    }
    public PricesByDate GetPricesByDate()
    {
      PricesByDate pricesByDate = new PricesByDate();
      for (int index = 0; index < Count; index++) pricesByDate.Add(this[index].Date, this[index]);
      return pricesByDate;
    }
    public Prices Top(int count)
    {
      Prices prices = new Prices();
      for (int index = 0; index < count && index<Count; index++)
      {
        prices.Add(this[index]);
      }
      return prices;
    }
    public Prices Bottom(int count)
    {
      Prices prices = new Prices();
      for (int index = Count-1; index>=0 && prices.Count<count; index--)
      {
        prices.Add(this[index]);
      }
      return prices;
    }
    public double Volatility()
    {
      float[] pricesAr = GetPrices();
      return Numerics.StdDev(ref pricesAr);
    }
    public double Min()
    {
      float[] pricesAr = GetPrices();
      return Numerics.Min(ref pricesAr);
    }
    public double MinLow()
    {
      float[] pricesAr = GetPricesLow();
      return Numerics.Min(ref pricesAr);
    }
    public double Max()
    {
      float[] pricesAr = GetPrices();
      return Numerics.Max(ref pricesAr);
    }
    public double Mean()
    {
      float[] pricesAr = GetPrices();
      return Numerics.Mean(ref pricesAr);
    }
    public double[] GetLeastSquaresFit()
    {
      double[] pricesArray = new double[Count];
      for (int index = 0; index < Count; index++)
      {
        pricesArray[index] = (float)this[index].Close;
      }
      LeastSquaresResult leastSquaresResult=Numerics.LeastSquares(pricesArray);
      return leastSquaresResult.LeastSquares;
    }
    public double[] GetVolume()
    {
      double[] volumeArray = new double[Count];
      for (int index = 0; index < Count; index++)
      {
        volumeArray[index] = (double)this[index].Volume;
      }
      return volumeArray;
    }
    public float[] GetPrices()
    {
      float[] pricesArray = new float[Count];
      for (int index = 0; index < Count; index++)
      {
        pricesArray[index] = (float)this[index].Close;
      }
      return pricesArray;
    }
    public float[] GetPricesLow()
    {
      float[] pricesArray = new float[Count];
      for (int index = 0; index < Count; index++)
      {
        pricesArray[index] = (float)this[index].Low;
      }
      return pricesArray;
    }
    public float[] GetPricesHigh()
    {
      float[] pricesArray = new float[Count];
      for (int index = 0; index < Count; index++)
      {
        pricesArray[index] = (float)this[index].High;
      }
      return pricesArray;
    }
    public float[] GetPrices(int startIndex, int count)
    {
      if (startIndex + count > Count) return null;
      float[] pricesArray=new float[count];
      for (int index = startIndex,arrayIndex=0; index < startIndex + count; index++,arrayIndex++)
      {
        pricesArray[arrayIndex] = (float)this[index].Close;
      }
      return pricesArray;
    }
    public float[] GetPricesHigh(int startIndex, int count)
    {
      if (startIndex + count > Count) return null;
      float[] pricesArray = new float[count];
      for (int index = startIndex, arrayIndex = 0; index < startIndex + count; index++, arrayIndex++)
      {
        pricesArray[arrayIndex] = (float)this[index].High;
      }
      return pricesArray;
    }
    public float[] GetPricesLow(int startIndex, int count)
    {
      if (startIndex + count > Count) return null;
      float[] pricesArray = new float[count];
      for (int index = startIndex, arrayIndex = 0; index < startIndex + count; index++, arrayIndex++)
      {
        pricesArray[arrayIndex] = (float)this[index].Low;
      }
      return pricesArray;
    }
    public float[] GetReturns()
    {
      if(Count==0||1==Count)return null;
      float[] returns = new float[Count - 1];
      for (int index = 0; index < Count - 1; index++)
      {
        Price currentPrice = this[index];
        Price prevPrice = this[index + 1];
        if (0.00 == prevPrice.Close) returns[index] = 0.00F;
        else returns[index] = (float)((currentPrice.Close - prevPrice.Close) / Math.Abs(prevPrice.Close));
      }
      return returns;
    }
    public double GetReturn1D()
    {
      if(Count<2)return double.NaN;
      Prices pricesForReturn1D=new Prices(this.Take(2).ToList());
      return  pricesForReturn1D.GetCumulativeReturn();
    }
    public float[] GetReturns(int dayCount)
    {
      if(Count-dayCount<=0)return new float[0];
      float[] returns = new float[Count - dayCount];
      for (int index = 0; index < Count - dayCount; index++)
      {
        Price currentPrice = this[index];
        Price prevPrice = this[index + dayCount];
        if (0.00 == prevPrice.Close) returns[index] = 0.00F;
        else returns[index] = (float)((currentPrice.Close - prevPrice.Close) / Math.Abs(prevPrice.Close));
      }
      return returns;
    }
    public double GetCumulativeReturn()
    {
      float[] returns=GetReturns();
      if(null==returns)return double.NaN;
      double itemReturn=0.00;
      for(int index=0;index<returns.Length;index++)itemReturn+=(double)returns[index];
      return itemReturn;
    }
    public double GetCumulativeReturn(int dayCount)
    {
      float[] returns=GetReturns(dayCount);
      double itemReturn=0.00;
      for(int index=0;index<returns.Length;index++)itemReturn+=(double)returns[index];
      return itemReturn;
    }
    public double[] GetReturnsAsDoubleArray()
    {
      double[] returns = new double[Count - 1];
      for (int index = 0; index < Count - 1; index++)
      {
        Price currentPrice = this[index];
        Price prevPrice = this[index + 1];
        if (0.00 == prevPrice.Close) returns[index] = 0.00;
        else returns[index] = (double)((currentPrice.Close - prevPrice.Close) / Math.Abs(prevPrice.Close));
      }
      return returns;
    }
    public double[] GetReturnsAsDoubleArray(int dayCount)
    {
      if (0 == Count) return null;
      double[] returns = new double[Count - dayCount];
      for (int index = 0; index < Count - dayCount; index++)
      {
        Price currentPrice = this[index];
        Price prevPrice = this[index + dayCount];
        if (0.00 == prevPrice.Close) returns[index] = 0.00F;
        else returns[index] = ((currentPrice.Close - prevPrice.Close) / Math.Abs(prevPrice.Close));
      }
      return returns;
    }
// *********************************
    //public static Prices GetMonthlyPrices(String symbol, DateTime asof, int months = 36)
    //{
    //  DateGenerator dateGenerator = new DateGenerator();
    //  Prices prices = new Prices();
    //  DateTime startDate = dateGenerator.GetCurrMonthStart(asof);
    //  DateTime minPricingDate = PricingDA.GetEarliestDate(symbol);
    //  Dictionary<DateTime, Price> symbolPricesByDate = new Dictionary<DateTime, Price>();
    //  List<DateTime> historicalDates = new List<DateTime>();
    //  while (historicalDates.Count < (months + 1))
    //  {
    //    historicalDates.Add(startDate);
    //    startDate = dateGenerator.GetPrevMonthStart(startDate);
    //  }
    //  DateTime requestStartDate = dateGenerator.DaysAddActual(asof, 5);  // advance 5 days to provide an error margin for holidays
    //  Prices symbolPrices = PricingDA.GetPrices(symbol, requestStartDate, historicalDates[historicalDates.Count - 1]);
    //  foreach (Price price in symbolPrices) symbolPricesByDate.Add(price.Date, price);
    //  startDate = dateGenerator.GetCurrMonthStart(asof);
    //  while (prices.Count < (months + 1))
    //  {
    //    Price price = GetPrice(symbol, startDate, symbolPricesByDate);
    //    if (null == price) return null;
    //    prices.Add(price);
    //    startDate = dateGenerator.GetPrevMonthStart(startDate);
    //    if (startDate < minPricingDate) break;
    //  }
    //  return prices;
    //}
    private static Price GetPrice(String symbol,DateTime requestedDate, Dictionary<DateTime, Price> symbolPricesByDate)
    {
      int maxAdvanceDays = 5;
      Price symbolPrice = null;
      for (int advanceDays = 0; advanceDays < maxAdvanceDays; advanceDays++)
      {
        if (!symbolPricesByDate.ContainsKey(requestedDate)) { requestedDate = requestedDate.AddDays(1); continue; }
        symbolPrice = symbolPricesByDate[requestedDate];
      }
      return symbolPrice;
    }
// **********************************






  }
  public class Price
  {
    private String symbol;
    private DateTime date;
    private double open;
    private double high;
    private double low;
    private double close;
    private long volume;
    private double adjClose;
    public Price()
    {
    }
    public Price Clone()
    {
      Price clonePrice=new Price();
      clonePrice.Symbol=Symbol;
      clonePrice.Date=Date;
      clonePrice.Open=Open;
      clonePrice.High=High;
      clonePrice.Low=Low;
      clonePrice.Close=Close;
      clonePrice.Volume=Volume;
      clonePrice.AdjClose=AdjClose;
      return clonePrice;
    }
    public String Symbol
    {
      get { return symbol; }
      set { symbol = value; }
    }
    public DateTime Date
    {
      get { return date; }
      set { date = value; }
    }
    public double Open
    {
      get { return open; }
      set { open = value; }
    }
    public double High
    {
      get { return high; }
      set { high = value; }
    }
    public double Low
    {
      get { return low; }
      set { low = value; }
    }
    public double Close
    {
      get { return close; }
      set { close = value; }
    }
    public long Volume
    {
      get { return volume; }
      set { volume = value; }
    }
    public double AdjClose
    {
      get { return adjClose; }
      set { adjClose = value; }
    }
    public bool IsValid
    {
      get
      {
        if(null==symbol)return false;
        if(Utility.IsEpoch(date))return false;
        if(double.IsNaN(open))return false;
        if(double.IsNaN(high))return false;
        if(double.IsNaN(low))return false;
        if(double.IsNaN(close))return false;
        if(double.IsNaN(adjClose))return false;
        return true;
      }
    }
    public static String Header
    {
      get { return "Date,Symbol,Open,High,Low,Close,Volume,Adj Close"; }   // ,M12,M26,MACD,Signal,Histogram
    }
    public override String ToString()
    {
      StringBuilder sb = new StringBuilder();
      sb.Append(symbol).Append(",");
      sb.Append(Utility.DateTimeToStringMMSDDSYYYY(Date)).Append(",");
      sb.Append(String.Format("{0:0.00}", Open)).Append(",");
      sb.Append(String.Format("{0:0.00}", High)).Append(",");
      sb.Append(String.Format("{0:0.00}", Low)).Append(",");
      sb.Append(String.Format("{0:0.00}", Close)).Append(",");
      sb.Append(Volume).Append(",");
      sb.Append(String.Format("{0:0.00}", AdjClose));
      return sb.ToString();
    }
  }
}