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

namespace MarketData.Generator.MovingAverage
{
  public class MovingAverageGenerator
  {
    public static readonly int DayCount200=200;
    public static readonly int DayCount100=100;
    public static readonly int DayCount55=55;
    public static readonly int DayCount50=50;
    public static readonly int DayCount21=21;
    public static readonly int DayCount5=5;
    private MovingAverageGenerator()
    {
    }
    public static MovingAverages GenerateMovingAverages(String symbol,int dayCount=180)
    {
      try
      {
        Dictionary<DateTime, DMAPrice> ma200ByDate = new Dictionary<DateTime, DMAPrice>();
        Dictionary<DateTime, DMAPrice> ma100ByDate = new Dictionary<DateTime, DMAPrice>();
        Dictionary<DateTime, DMAPrice> ma55ByDate = new Dictionary<DateTime, DMAPrice>();
        Dictionary<DateTime, DMAPrice> ma21ByDate = new Dictionary<DateTime, DMAPrice>();
        Dictionary<DateTime, DMAPrice> ma5ByDate = new Dictionary<DateTime, DMAPrice>();
        if (null == symbol) return null;
        String companyName = PricingDA.GetNameForSymbol(symbol);
        DateGenerator dateGenerator = new DateGenerator();
        DateTime startDate = dateGenerator.GetPrevBusinessDay(DateTime.Now);
        Prices prices = PricingDA.GetPrices(symbol, startDate, dayCount);
        if (null == prices || 0 == prices.Count)
        {
          MDTrace.WriteLine(LogLevel.DEBUG,"No prices for symbol '" + symbol + "'");
          return null;
        }
        Price latestPrice = prices[0];
        DMAPrices ma200=  GenerateMovingAverage(prices, DayCount200);
        DMAPrices ma100 = GenerateMovingAverage(prices, DayCount100);
        DMAPrices ma55 = GenerateMovingAverage(prices, DayCount55);
        DMAPrices ma21 = GenerateMovingAverage(prices, DayCount21);
        DMAPrices ma5 = GenerateMovingAverage(prices, DayCount5);

        for (int index = 0; index < ma200.Count; index++) ma200ByDate.Add(ma200[index].Date,ma200[index]);
        for (int index = 0; index < ma100.Count; index++) ma100ByDate.Add(ma100[index].Date, ma100[index]);
        for (int index = 0; index < ma55.Count; index++) ma55ByDate.Add(ma55[index].Date, ma55[index]);
        for (int index = 0; index < ma21.Count; index++) ma21ByDate.Add(ma21[index].Date, ma21[index]);
        for (int index = 0; index < ma5.Count; index++) ma5ByDate.Add(ma5[index].Date, ma5[index]);
        MovingAverages movingAverages = new MovingAverages();
        movingAverages.ThruDate = prices[0].Date;
        movingAverages.FromDate = prices[prices.Count - 1].Date;
        for (int index = 0; index < prices.Count; index++)
        {
          Price price = prices[index];

          DMAPrice ma200Price = null;
          DMAPrice ma100Price = null;
          DMAPrice ma55Price = null;
          DMAPrice ma21Price = null;
          DMAPrice ma5Price = null;

          if (ma55ByDate.ContainsKey(price.Date)) ma55Price=ma55ByDate[price.Date];
          if (ma21ByDate.ContainsKey(price.Date)) ma21Price = ma21ByDate[price.Date];
          if (ma5ByDate.ContainsKey(price.Date)) ma5Price = ma5ByDate[price.Date];
          if(ma200ByDate.ContainsKey(price.Date))ma200Price = ma200ByDate[price.Date];
          if(ma100ByDate.ContainsKey(price.Date)) ma100Price = ma100ByDate[price.Date];

          MovingAverageElement movingAverageElement = new MovingAverageElement();
          movingAverageElement.Symbol = price.Symbol;
          movingAverageElement.Date = price.Date;
          movingAverageElement.Close = price.Close;
          movingAverageElement.High = price.High;
          movingAverageElement.Low = price.Low;
          movingAverageElement.MA200 = null==ma200Price?double.NaN:ma200Price.AVGPrice;
          movingAverageElement.MA100 = null == ma100Price ? double.NaN : ma100Price.AVGPrice;
          movingAverageElement.MA55 = null==ma55Price?double.NaN:ma55Price.AVGPrice;
          movingAverageElement.MA21 = null==ma21Price?double.NaN:ma21Price.AVGPrice;
          movingAverageElement.MA5 = null==ma5Price?double.NaN:ma5Price.AVGPrice;
          movingAverages.Add(movingAverageElement);
        }
        return movingAverages;
      }
      catch (Exception exception)
      {
        MDTrace.WriteLine(LogLevel.DEBUG,exception);
        return null;
      }
    }
    public static DMAValues GenerateMovingAverage(DMAValues values,int dayCount)
    {
      try
      {
        DMAValues dmaValues = new DMAValues();
        for (int index = 0; index < values.Count; index++)
        {
          DMAValue value = values[index];
          DMAValue dmaValue = new DMAValue();
          dmaValue.Date = value.Date;
          float[] pricesArray = values.GetValues(index, dayCount);
          if (null == pricesArray) break;
          dmaValue.MAValue = Numerics.Mean(ref pricesArray);
          dmaValue.Value = value.Value;
          if (double.IsNaN(dmaValue.MAValue)) continue;
          dmaValues.Add(dmaValue);
        }
        return dmaValues;
      }
      catch (Exception exception)
      {
        MDTrace.WriteLine(LogLevel.DEBUG,exception);
        return null;
      }
    }

    /// <summary>
    /// The EMA coverage shoudld be the same as the simple moving average.  If you have 10 elements in the SMA then you should have 10 elements in the EMA
    /// The EMA smooths the line by applying a smoothing (Beta) where Beta=1/(dayCount+1).  Foe example if you are wanting to calculate the
    /// 20 day exponential moving average over a series then Beta=2/(20+1)=.095
    /// The formula: EMA=prevEMA.AVGPrice+beta*(smaPrice.CurrentPrice - prevEMA.AVGPrice)
    /// Tom Basso uses a 9 day(Fast) and 41 day(Slow) exponential moving average crossover to determine change in trend direction.
    /// </summary>
    /// <param name="prices"></param>
    /// <param name="dayCount"></param>
    /// <returns></returns>
    public static DMAPrices GenerateExponentialMovingAverage(Prices prices,int dayCount)
    {
      try
      {
        if(null==prices||prices.Count<dayCount+1)return null;  // (i.e.) a 20 day ExponentialMovingAverage requires 21 days of data
        double beta=2.00/((double)dayCount+1.00);
        DMAPrices emaPrices=new DMAPrices();

        DMAPrices smaPrices=GenerateMovingAverage(prices,dayCount);
        if(null==smaPrices||smaPrices.Count<dayCount)return null;
        for(int index=0;index<smaPrices.Count;index++)
        {
          DMAPrice smaPrice=smaPrices[index];                    
          DMAPrice emaPrice=new DMAPrice();
          if(0==index)
          {
            emaPrice.Symbol=smaPrice.Symbol;
            emaPrice.Date=smaPrice.Date;
            emaPrice.AVGPrice=smaPrice.AVGPrice;
            emaPrice.CurrentPrice=smaPrice.CurrentPrice;
            emaPrice.MaxPrice=smaPrice.MaxPrice;
            emaPrice.MinPrice=smaPrice.MinPrice;
            emaPrices.Add(emaPrice);
          }
          else
          {
            DMAPrice prevEMA=emaPrices[emaPrices.Count-1];
            emaPrice.Symbol=smaPrice.Symbol;
            emaPrice.Date=smaPrice.Date;
            emaPrice.CurrentPrice=smaPrice.CurrentPrice;
            emaPrice.MaxPrice=smaPrice.MaxPrice;
            emaPrice.MinPrice=smaPrice.MinPrice;
            emaPrice.AVGPrice = prevEMA.AVGPrice+beta*(smaPrice.CurrentPrice - prevEMA.AVGPrice);
            emaPrices.Add(emaPrice);
          }
        }
        return emaPrices;
      }
      catch (Exception exception)
      {
        MDTrace.WriteLine(LogLevel.DEBUG,exception);
        return null;
      }
    }

/// <summary>Generates a dayCount moving average given prices.</summary>
    public static DMAPrices GenerateMovingAverage(Prices prices, int dayCount)
    {
      try
      {
        DMAPrices dmaPrices = new DMAPrices();
        for (int index = 0; index < prices.Count; index++)
        {
          Price price = prices[index];
          DMAPrice dmaPrice = new DMAPrice();
          dmaPrice.Symbol = price.Symbol;
          dmaPrice.Date = price.Date;
          float[] pricesArray = prices.GetPrices(index, dayCount);
          if (null == pricesArray) break;
          dmaPrice.AVGPrice = Numerics.Mean(ref pricesArray);
          if (double.IsNaN(dmaPrice.AVGPrice)) continue;
          dmaPrice.CurrentPrice = price.Close;
          dmaPrices.Add(dmaPrice);
        }
        return dmaPrices;
      }
      catch (Exception exception)
      {
        MDTrace.WriteLine(LogLevel.DEBUG,exception);
        return null;
      }
    }
    public static DMAPrices GenerateMovingMinsOnLow(Prices prices, int dayCount)
    {
      try
      {
        DMAPrices dmaPrices = new DMAPrices();

        for (int index = 0; index < prices.Count; index++)
        {
          Price price = prices[index];
          DMAPrice dmaPrice = new DMAPrice();
          dmaPrice.Symbol = price.Symbol;
          dmaPrice.Date = price.Date;
          float[] pricesArray = prices.GetPricesLow(index, dayCount);
          if (null == pricesArray) break;
          dmaPrice.MinPrice = Numerics.Min(ref pricesArray);
          if (double.IsNaN(dmaPrice.MinPrice)) continue;
          dmaPrice.CurrentPrice = price.Low;
          dmaPrices.Add(dmaPrice);
        }
        return dmaPrices;
      }
      catch (Exception exception)
      {
        MDTrace.WriteLine(LogLevel.DEBUG,exception.ToString());
        return null;
      }
    }
    public static DMAPrices GenerateMovingMaxOnHigh(Prices prices, int dayCount)
    {
      try
      {
        DMAPrices dmaPrices = new DMAPrices();

        for (int index = 0; index < prices.Count; index++)
        {
          Price price = prices[index];
          DMAPrice dmaPrice = new DMAPrice();
          dmaPrice.Symbol = price.Symbol;
          dmaPrice.Date = price.Date;
          float[] pricesArray = prices.GetPricesHigh(index, dayCount);
          if (null == pricesArray) break;
          dmaPrice.MaxPrice = Numerics.Max(ref pricesArray);
          if (double.IsNaN(dmaPrice.MaxPrice)) continue;
          dmaPrice.CurrentPrice = price.High;
          dmaPrices.Add(dmaPrice);
        }
        return dmaPrices;
      }
      catch (Exception exception)
      {
        MDTrace.WriteLine(LogLevel.DEBUG,exception.ToString());
        return null;
      }
    }
  }
}
