using System;
using System.Collections.Generic;
using MarketData.Cache;
using MarketData.DataAccess;
using MarketData.MarketDataModel;
using MarketData.Utils;

// This Beta calculator is modelled after Yahoo Finance Beta calculator.  It calculates Beta using 36 (default) monthly prices starting at beginning of previous month
// and going back 36 months.  While the calculator does not match exactly to Yahoo Finance I will use this as an alternative in the event that Yahoo Finance Beta is
// no longer available.
namespace MarketData.Numerical
{
  public class BetaPrices : List<BetaPrice>
  {
    public BetaPrices()
    {
    }

// Assumes that the list is in descending date order with the Latest (Most Recent) date in the lowest index and Earliest (Most Historical) date in the highest index
    public double[] ReturnsBenchmark()
    {
      double[] returns=new double[Count-1];
      for(int index=Count-2;index>=0;index--)
      {
        if(this[index+1].PriceBenchmark.Close==0.00)returns[index]=0.00;
        else returns[index]=(this[index].PriceBenchmark.Close-this[index+1].PriceBenchmark.Close)/this[index+1].PriceBenchmark.Close;
      }
      return returns;
    }

// Assumes that the list is in descending date order with the Latest (Most Recent) date in the lowest index and Earliest (Most Historical) date in the highest index
    public double[] ReturnsSymbol()
    {
      double[] returns=new double[Count-1];
      for(int index=Count-2;index>=0;index--)
      {
        if(this[index+1].PriceSymbol.Close==0.00)returns[index]=0.00;
        else returns[index]=(this[index].PriceSymbol.Close-this[index+1].PriceSymbol.Close)/this[index+1].PriceSymbol.Close;
      }
      return returns;
    }
  }

  public class BetaPrice
  {
    public BetaPrice(String symbol,String benchmark,Price symbolPrice,Price benchmarkPrice,DateTime pricingDate)
    {
      Symbol=symbol;
      Benchmark=benchmark;
      PricingDate=pricingDate;
      PriceSymbol=symbolPrice;
      PriceBenchmark=benchmarkPrice;
    }
    public DateTime PricingDate{get;set;}
    public String Symbol{get;set;}
    public String Benchmark{get;set;}
    public Price PriceSymbol{get;set;}
    public Price PriceBenchmark{get;set;}
  }

  public class BetaGenerator
  {
    private BetaGenerator()
    {
    }
    public static double Beta(String symbol,int months = 36, bool verbose=true)
    {
      return Beta(symbol, PricingDA.GetLatestDate(symbol), months, verbose);
    }


    public static double Beta(String symbol,DateTime asof,int months=36,bool verbose=true)
    {
      try
      {
        String benchmark = "SPY";
        DateGenerator dateGenerator = new DateGenerator();

        BetaPrices betaPrices = new BetaPrices();
        DateTime startDate = dateGenerator.GetPrevMonthStart(asof);
        DateTime minPricingDate = PricingDA.GetEarliestDate(symbol);
        Dictionary<DateTime, Price> symbolPricesByDate = new Dictionary<DateTime, Price>();
        Dictionary<DateTime, Price> benchmarkPricesByDate = new Dictionary<DateTime, Price>();
        List<DateTime> historicalDates = new List<DateTime>();

        while (historicalDates.Count < (months + 1))
        {
          historicalDates.Add(startDate);
          startDate = dateGenerator.GetPrevMonthStart(startDate);
        }
        DateTime earliestDate = historicalDates[historicalDates.Count - 1];

        Prices symbolPrices = GBPriceCache.GetInstance().GetPrices(symbol, asof, earliestDate);
        Prices benchmarkPrices = GBPriceCache.GetInstance().GetPrices(benchmark, asof, earliestDate); 

        foreach (Price price in symbolPrices) symbolPricesByDate.Add(price.Date, price);
        foreach (Price price in benchmarkPrices) benchmarkPricesByDate.Add(price.Date, price);

        startDate = dateGenerator.GetPrevMonthStart(asof);

        while (betaPrices.Count < (months + 1))
        {
          BetaPrice betaPrice = GetPrice(symbol, benchmark, startDate, symbolPricesByDate, benchmarkPricesByDate);
          if (null == betaPrice)
          {
            if(verbose)
            {
              MDTrace.WriteLine(LogLevel.DEBUG,String.Format("INFO:Beta({0},{1}).. Cannot calculate Beta.  Missing price on {2}",symbol,Utility.DateTimeToStringMMHDDHYYYY(asof),Utility.DateTimeToStringMMHDDHYYYY(startDate)));
            }
            return double.NaN;
          }
          betaPrices.Add(betaPrice);
          startDate = dateGenerator.GetPrevMonthStart(startDate);
          if (startDate < minPricingDate) break;
        }

        double[] returnsSymbol = betaPrices.ReturnsSymbol();
        double[] returnsBenchmark = betaPrices.ReturnsBenchmark();
        double beta = Numerics.Beta(ref returnsSymbol, ref returnsBenchmark);
        return beta;
      }
      catch (Exception exception)
      {
        MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Exception:{0}",exception.ToString()));
        return double.NaN;
      }
    }


    /// <summary>
    /// Fetch the first price of the month for both the asset and the benchmark, allowing both to walk forward in the month by up to 10 days until we get a good price
    /// </summary>
    /// <param name="symbol"></param>
    /// <param name="benchmark"></param>
    /// <param name="requestedDate"></param>
    /// <param name="symbolPricesByDate"></param>
    /// <param name="benchmarkPricesByDate"></param>
    /// <returns></returns>
    private static BetaPrice GetPrice(String symbol, String benchmark, DateTime requestedDate, Dictionary<DateTime, Price> symbolPricesByDate, Dictionary<DateTime, Price> benchmarkPricesByDate)
    {
      try
      {
        int maxAdvanceDays = 10;
        Price symbolPrice = default;
        Price benchmarkPrice = default;

        symbolPrice = GetPriceWithinDays(requestedDate, maxAdvanceDays, symbolPricesByDate);
        benchmarkPrice = GetPriceWithinDays(requestedDate, maxAdvanceDays, benchmarkPricesByDate);

        if (null == symbolPrice || null == benchmarkPrice) return null;

        symbolPrice.Date = requestedDate.Date;
        benchmarkPrice.Date = requestedDate.Date;

        return new BetaPrice(symbol, benchmark, symbolPrice, benchmarkPrice, requestedDate);
      }
      catch (Exception exception)
      {
        MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Exception:{0}", exception.ToString()));
        return null;
      }
    }

    private static Price GetPriceWithinDays(DateTime requestedDate, int maxDays, Dictionary<DateTime,Price> pricesByDate)
    {
      DateGenerator dateGenerator = new DateGenerator();

      List<DateTime> futureDates = dateGenerator.GenerateFutureDates(requestedDate, maxDays);
      foreach(DateTime futureDate in futureDates)
      {
        if(pricesByDate.ContainsKey(futureDate))
        {
          return pricesByDate[futureDate];
        }
      }
      return default;
    }

// assuming that the list is in descending date order



    /// <summary>
    /// Fetch the first price of the month for both the asset and the benchmark, allowing both to walk forward in the month by up to 10 days until we get a good price
    /// </summary>
    /// <param name="symbol"></param>
    /// <param name="benchmark"></param>
    /// <param name="requestedDate"></param>
    /// <param name="symbolPricesByDate"></param>
    /// <param name="benchmarkPricesByDate"></param>
    /// <returns></returns>
    //private static BetaPrice GetPrice(String symbol, String benchmark, DateTime requestedDate, Dictionary<DateTime, Price> symbolPricesByDate, Dictionary<DateTime, Price> benchmarkPricesByDate)
    //{
    //  try
    //  {
    //    int maxAdvanceDays = 10;
    //    Price symbolPrice = default;
    //    Price benchmarkPrice = default;

    //    for (int advanceDays = 0; advanceDays < maxAdvanceDays; advanceDays++)
    //    {
    //      if (!symbolPricesByDate.ContainsKey(requestedDate))
    //      { 
    //        requestedDate = requestedDate.AddDays(1); 
    //        continue; 
    //      }
    //      symbolPrice = symbolPricesByDate[requestedDate];

    //      if (!benchmarkPricesByDate.ContainsKey(requestedDate))
    //      { 
    //        requestedDate = requestedDate.AddDays(1);
    //        continue; 
    //      }
    //      benchmarkPrice = benchmarkPricesByDate[requestedDate];
    //      if(null != symbolPrice && null != benchmarkPrice)break;
    //    }

    //    if (null == symbolPrice || null == benchmarkPrice) return null;

    //    symbolPrice.Date = requestedDate.Date;
    //    benchmarkPrice.Date = requestedDate.Date;
    //    return new BetaPrice(symbol, benchmark, symbolPrice, benchmarkPrice, requestedDate);
    //  }
    //  catch (Exception exception)
    //  {
    //    MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Exception:{0}", exception.ToString()));
    //    return null;
    //  }
    //}


    //public static double Beta(String symbol,DateTime asof,int months=36)
    //{
    //  try
    //  {
    //    String benchmark = "SPY";
    //    DateGenerator dateGenerator = new DateGenerator();

    //    BetaPrices betaPrices = new BetaPrices();
    //    DateTime startDate = dateGenerator.GetPrevMonthStart(asof);
    //    DateTime minPricingDate = PricingDA.GetEarliestDate(symbol);
    //    Dictionary<DateTime, Price> symbolPricesByDate = new Dictionary<DateTime, Price>();
    //    Dictionary<DateTime, Price> benchmarkPricesByDate = new Dictionary<DateTime, Price>();
    //    List<DateTime> historicalDates = new List<DateTime>();

    //    while (historicalDates.Count < (months + 1))
    //    {
    //      historicalDates.Add(startDate);
    //      startDate = dateGenerator.GetPrevMonthStart(startDate);
    //    }
    //    DateTime earliestDate = historicalDates[historicalDates.Count - 1];

    //    Prices symbolPrices = PricingDA.GetPrices(symbol, asof, earliestDate);
    //    Prices benchmarkPrices = PricingDA.GetPrices(benchmark, asof, earliestDate);

    //    foreach (Price price in symbolPrices) symbolPricesByDate.Add(price.Date, price);
    //    foreach (Price price in benchmarkPrices) benchmarkPricesByDate.Add(price.Date, price);

    //    startDate = dateGenerator.GetPrevMonthStart(asof);

    //    while (betaPrices.Count < (months + 1))
    //    {
    //      BetaPrice betaPrice = GetPrice(symbol, benchmark, startDate, symbolPricesByDate, benchmarkPricesByDate);
    //      if (null == betaPrice)
    //      {
    //        MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Beta({0},{1}).. Cannot calculate Beta.  Missing price on {2}",symbol,Utility.DateTimeToStringMMHDDHYYYY(asof),Utility.DateTimeToStringMMHDDHYYYY(startDate)));
    //        return double.NaN;
    //      }
    //      betaPrices.Add(betaPrice);
    //      startDate = dateGenerator.GetPrevMonthStart(startDate);
    //      if (startDate < minPricingDate) break;
    //    }

    //    double[] returnsSymbol = betaPrices.ReturnsSymbol();
    //    double[] returnsBenchmark = betaPrices.ReturnsBenchmark();
    //    double beta = Numerics.Beta(ref returnsSymbol, ref returnsBenchmark);
    //    return beta;
    //  }
    //  catch (Exception exception)
    //  {
    //    MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Exception:{0}",exception.ToString()));
    //    return double.NaN;
    //  }
    //}


  }
}

