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

namespace MarketData.Generator
{
  public class DCFGenerator
  {
    private DCFGenerator()
    {
    }
// This just calculates the WACC
    public static double CalculateWACC(String symbol,DateTime asOf)
    {
      try
      {
        DCFValuation dcfValuation = new DCFValuation();
        dcfValuation.Symbol = symbol;
        Fundamental fundamental = FundamentalDA.GetFundamentalMaxDate(symbol,asOf);
        if (null == fundamental) {return double.NaN; }
        if (!CalculateCostOfEquity(dcfValuation,asOf)) return double.NaN;
        if (!CalculateCostOfDebt(dcfValuation,fundamental.TotalDebt,asOf)) return double.NaN;
        dcfValuation.MarketValueOfDebt = fundamental.TotalDebt;
        dcfValuation.MarketValueOfEquity = fundamental.MarketCap;
        dcfValuation.OutstandingShares = fundamental.SharesOutstanding;
        dcfValuation.TotalCapitalInvested = dcfValuation.MarketValueOfDebt + dcfValuation.MarketValueOfEquity;
        if (!CalculateWACC(dcfValuation)) return double.NaN;
        return dcfValuation.WACC;
      }
      catch(Exception)
      {
        return double.NaN;
      }
    }
// This calculates the full discounted cashflow
    public static DCFValuation GenerateDCFValuation(String symbol)
    {
      DCFValuation dcfValuation = new DCFValuation();
      dcfValuation.Symbol = symbol;

      Fundamental fundamental = FundamentalDA.GetFundamental(symbol);
      if (null == fundamental) { dcfValuation.Message = "Missing fundamental data."; return dcfValuation; }
      if (!CalculateCostOfEquity(dcfValuation)) return dcfValuation;
      if (!CalculateCostOfDebt(dcfValuation, fundamental.TotalDebt)) return dcfValuation;
      dcfValuation.MarketValueOfDebt = fundamental.TotalDebt;
      dcfValuation.MarketValueOfEquity = fundamental.MarketCap;
      dcfValuation.OutstandingShares = fundamental.SharesOutstanding;
      dcfValuation.TotalCapitalInvested = dcfValuation.MarketValueOfDebt + dcfValuation.MarketValueOfEquity;
      if (!CalculateWACC(dcfValuation)) return dcfValuation;
      if (!Discount(dcfValuation)) return dcfValuation;
      EnhanceValuation(dcfValuation,fundamental);  // add ROIC, EPSGrowth, EarningsGrowth
      dcfValuation.success = true;
      return dcfValuation;
    }
    public static void EnhanceValuation(DCFValuation dcfValuation,Fundamental fundamental)
    {
      TimeSeriesCollection bookValuePerShareCollection = HistoricalDA.GetTimeSeries(dcfValuation.Symbol, TimeSeriesElement.ElementType.BVPS);
      TimeSeriesCollection roicCollection = HistoricalDA.GetTimeSeries(dcfValuation.Symbol, TimeSeriesElement.ElementType.ROIC);
      TimeSeriesCollection revenueCollection = HistoricalDA.GetTimeSeries(dcfValuation.Symbol, TimeSeriesElement.ElementType.Revenue);
      TimeSeriesCollection epsCollection = HistoricalDA.GetTimeSeries(dcfValuation.Symbol, TimeSeriesElement.ElementType.EPS);
      YieldCurve yieldCurve=YieldCurveDA.GetYieldCurve();

      if (null != roicCollection && 0 != roicCollection.Count)
      {
        dcfValuation.Enhancements.ROICGrowth = Numerics.AverageReturn(roicCollection);
        dcfValuation.Enhancements.ROIC = roicCollection[0].Value/100.00;
      }
      dcfValuation.Enhancements.DebtLoad = fundamental.Revenue * 3 > fundamental.TotalDebt ? true : false;
      dcfValuation.Enhancements.EPSGrowth = Numerics.AverageReturn(epsCollection);
      dcfValuation.Enhancements.RevenueGrowth = Numerics.AverageReturn(revenueCollection);
      dcfValuation.Enhancements.BVPS = fundamental.BookValuePerShare;
      if (null == dcfValuation.CurrentPrice || double.NaN.Equals(dcfValuation.CurrentPrice.Close)) dcfValuation.Enhancements.PBVPS = double.NaN;
      else dcfValuation.Enhancements.PBVPS = dcfValuation.CurrentPrice.Close / dcfValuation.Enhancements.BVPS;
      dcfValuation.Enhancements.EPS = fundamental.EPS;
      dcfValuation.Enhancements.PE = fundamental.PE;
      dcfValuation.Enhancements.PEG = fundamental.PEG;
      dcfValuation.Enhancements.MOS = dcfValuation.StockPriceValuation * .5;
      dcfValuation.Enhancements.MOS80 = dcfValuation.StockPriceValuation * .8;
      dcfValuation.Enhancements.IntrinsicValue = GrahamGenerator.IntrinsicValue(fundamental.EPS,dcfValuation.Enhancements.EPSGrowth);
      if (null == yieldCurve || double.NaN.Equals(yieldCurve[0].Yr20)) dcfValuation.Enhancements.IntrinsicValueRevised = double.NaN;
      else dcfValuation.Enhancements.IntrinsicValueRevised = GrahamGenerator.IntrinsicValueRevised(fundamental.EPS,dcfValuation.Enhancements.EPSGrowth,yieldCurve[0].Yr30); 
      if (null == dcfValuation.CurrentPrice || double.NaN.Equals(dcfValuation.CurrentPrice.Close)) dcfValuation.Enhancements.RGVIntrinsic = double.NaN;
      else dcfValuation.Enhancements.RGVIntrinsic = dcfValuation.Enhancements.IntrinsicValue / dcfValuation.CurrentPrice.Close;
      if (null == dcfValuation.CurrentPrice || double.NaN.Equals(dcfValuation.CurrentPrice.Close)) dcfValuation.Enhancements.RGVIntrinsicRevised = double.NaN;
      else dcfValuation.Enhancements.RGVIntrinsicRevised = dcfValuation.Enhancements.IntrinsicValueRevised / dcfValuation.CurrentPrice.Close;
    }
    private static bool Discount(DCFValuation dcfValuation,int years=10)
    {
      List<Element> sourceElements = new List<Element>();
      List<Element> destElements = null;

// pull cashflow history from database and apply filters
      TimeSeriesCollection freeCashflows = HistoricalDA.GetTimeSeries(dcfValuation.Symbol, TimeSeriesElement.ElementType.FreeCashflow);
      if (null == freeCashflows || 0 == freeCashflows.Count) { dcfValuation.Message = "Missing Freecashflows"; return false; }
      dcfValuation.FreeCashflowGrowth = Numerics.AverageReturnWithSpline(freeCashflows,dcfValuation.ReturnItems);   // if free cashflow growth > 100% perform an average throwing away all returns in the set > 100%
      if (dcfValuation.FreeCashflowGrowth.Equals(double.NaN)) { dcfValuation.Message = "Freecashflow growth is undefined"; return false; }

// set up spline parameters.  Apply 3% cap to freecashflow growth in the 10th year and spline to that.
      sourceElements.Add(new Element(1, dcfValuation.FreeCashflowGrowth));
      sourceElements.Add(new Element(years, .03 < dcfValuation.FreeCashflowGrowth ? .03 : dcfValuation.FreeCashflowGrowth));

// set the starting date and the initial freecashflow value, run interpolation
      DateTime startingDate = freeCashflows[0].AsOf;
      double runningCashflow = freeCashflows[0].Value;
      for (int year = 1; year <= years; year++)
      {
        startingDate = new DateTime(startingDate.Year + 1, startingDate.Month, startingDate.Day);
        destElements = new List<Element>();
        destElements.Add(new Element(year, 0));
        CatmullRom.PerformSpline((Element[])sourceElements.ToArray(), (Element[])destElements.ToArray());
        double cashflowGrowth = destElements[0].Row;
        if (year < years) runningCashflow = runningCashflow * (1.00 + cashflowGrowth);
        else runningCashflow = (runningCashflow * (1.00 + cashflowGrowth)) / (Math.Abs(dcfValuation.WACC - cashflowGrowth));
        dcfValuation.Cashflows.Add(new CashflowElement(startingDate, runningCashflow));
      }
// Discount cashflows by WACC
      for (int index = 0; index < dcfValuation.Cashflows.Count; index++)
      {
        dcfValuation.Cashflows[index].Cashflow /= Math.Pow(1.00 + dcfValuation.WACC, index + 1);
      }
// sum up the discounted values to get the present value;
      dcfValuation.PresentValue = 0.00;
      for (int index = 0; index < dcfValuation.Cashflows.Count; index++)
      {
        dcfValuation.PresentValue += dcfValuation.Cashflows[index].Cashflow;
      }
// subtract debt
      dcfValuation.PresentValue -= dcfValuation.TotalDebt;
// divide by outstanding shares
      dcfValuation.StockPriceValuation=dcfValuation.PresentValue/dcfValuation.OutstandingShares;

// Apply validation filters
      if (dcfValuation.PresentValue.Equals(double.NaN)) { dcfValuation.Message = "PresentValue is undefined"; return false; }

      if (dcfValuation.StockPriceValuation.Equals(double.NaN)) { dcfValuation.Message = "Stock price valuation is undefined"; return false; }
      return true;
    }
// Calculate the WACC
    private static bool CalculateWACC(DCFValuation dcfValuation)
    {
      dcfValuation.WACC=((dcfValuation.MarketValueOfEquity/dcfValuation.TotalCapitalInvested)*dcfValuation.CostOfEquity)+
        ((dcfValuation.MarketValueOfDebt/dcfValuation.TotalCapitalInvested)*dcfValuation.CostOfDebt*(1.00-dcfValuation.CorporateTaxRate));
      return true;
    }
    private static bool CalculateCostOfEquity(DCFValuation dcfValuation)
    {
      String marketSymbol = "SPY";
      Prices stockPrices = null;
      Prices marketPrices=null;

// calculate 3-year beta of stock vs SP500
      stockPrices = PricingDA.GetPrices(dcfValuation.Symbol, 365 * 3);
      if (null == stockPrices || 0 == stockPrices.Count)
      {
        dcfValuation.Message = "Missing prices for symbol.";
        return false;
      }
      dcfValuation.CurrentPrice = stockPrices[0];
      marketPrices=PricingDA.GetPrices(marketSymbol,stockPrices.Count);
      if (null == stockPrices || 0 == stockPrices.Count || null == marketPrices || 0 == marketPrices.Count)
      {
        dcfValuation.Message = "Missing prices for market.";
        return false;
      }
      dcfValuation.Beta = BetaGenerator.Beta(dcfValuation.Symbol, PricingDA.GetLatestDate(dcfValuation.Symbol));

// get the risk free rate, 90 day treasury note
      dcfValuation.RiskFreeRate = YieldCurveDA.GetRiskFreeRate();
      dcfValuation.RiskFreeRate /= 100.00;

// calculate historic market returns
      if (dcfValuation.UseMarketReturn)
      {
        marketPrices = PricingDA.GetPrices(marketSymbol);
        float[] marketPricesArray = marketPrices.GetPrices();
        dcfValuation.MarketReturn = Numerics.AnnualReturn(ref marketPricesArray);
      }
      else dcfValuation.MarketReturn = DCFValuation.MARKET_RETURN;
// calculate cost of equity
      dcfValuation.CostOfEquity = dcfValuation.RiskFreeRate + dcfValuation.Beta * (dcfValuation.MarketReturn - dcfValuation.RiskFreeRate);
      return true;  
    }
    private static bool CalculateCostOfEquity(DCFValuation dcfValuation,DateTime asOf)
    {
      String marketSymbol = "SPY";
      Prices stockPrices = null;
      Prices marketPrices=null;

// calculate 3-year beta of stock vs SP500
      stockPrices = PricingDA.GetPrices(dcfValuation.Symbol,asOf, 365 * 3);
      if (null == stockPrices || 0 == stockPrices.Count)
      {
        dcfValuation.Message = "Missing prices for symbol.";
        return false;
      }
      dcfValuation.CurrentPrice = stockPrices[0];
      marketPrices=PricingDA.GetPrices(marketSymbol,asOf,stockPrices.Count);
      if (null == stockPrices || 0 == stockPrices.Count || null == marketPrices || 0 == marketPrices.Count)
      {
        dcfValuation.Message = "Missing prices for market.";
        return false;
      }
      //float[] stockPricesArray=stockPrices.GetPrices();
      //float[] marketPricesArray = marketPrices.GetPrices();
      //dcfValuation.Beta = Numerics.Beta(ref stockPricesArray, ref marketPricesArray);
      dcfValuation.Beta = BetaGenerator.Beta(dcfValuation.Symbol, PricingDA.GetLatestDate(dcfValuation.Symbol));

// get the risk free rate, 90 day treasury note
      dcfValuation.RiskFreeRate = YieldCurveDA.GetRiskFreeRate(asOf);
      dcfValuation.RiskFreeRate /= 100.00;

// calculate historic market returns
      if (dcfValuation.UseMarketReturn)
      {
        marketPrices = PricingDA.GetPricesOnOrBefore(marketSymbol,asOf);
        float[] marketPricesArray = marketPrices.GetPrices();
        dcfValuation.MarketReturn = Numerics.AnnualReturn(ref marketPricesArray);
      }
      else dcfValuation.MarketReturn = DCFValuation.MARKET_RETURN;
// calculate cost of equity
      dcfValuation.CostOfEquity = dcfValuation.RiskFreeRate + dcfValuation.Beta * (dcfValuation.MarketReturn - dcfValuation.RiskFreeRate);
      return true;  
    }
// **YAHOO InterestExpense is totally wrong for some stocks.  AER is a good example
// This prioritizes MorningStar InterestExpense number over Yahoo Interest Expense number
    private static bool CalculateCostOfDebt(DCFValuation dcfValuation,double totalDebt)
    {             
      TimeSeriesCollection interestExpenseSeries = HistoricalDA.GetTimeSeries(dcfValuation.Symbol, TimeSeriesElement.ElementType.InterestExpense);
      if (null == interestExpenseSeries || 0 == interestExpenseSeries.Count)
      {
        IncomeStatement incomeStatement = IncomeStatementDA.GetIncomeStatement(dcfValuation.Symbol, IncomeStatement.PeriodType.Annual);
        if(null!=incomeStatement&&incomeStatement.InterestExpense.Equals(double.NaN))
        {
          dcfValuation.InterestExpense=0.00;
        }
        else if(null==incomeStatement)
        {
          dcfValuation.Message = "Missing IncomeStatement or Interest Expense ";
          return false;
        }
        else dcfValuation.InterestExpense=incomeStatement.InterestExpense;
      }
      else
      {
        if(interestExpenseSeries[0].Value.Equals(double.NaN))dcfValuation.InterestExpense=0.00;
        else dcfValuation.InterestExpense=interestExpenseSeries[0].Value;
      }
      TimeSeriesCollection timeSeriesCollection = HistoricalDA.GetTimeSeries(dcfValuation.Symbol, TimeSeriesElement.ElementType.TaxRate);
      if (null == timeSeriesCollection || 0 == timeSeriesCollection.Count) timeSeriesCollection.Add(new TimeSeriesElement() {Value=37 });
      TimeSeriesElement timeSeriesElement = timeSeriesCollection[0];
      dcfValuation.CorporateTaxRate = timeSeriesElement.Value / 100.00;
      dcfValuation.TotalDebt = totalDebt;
      if(0.00==totalDebt)
      {
        dcfValuation.InterestRate=0.00;        
        dcfValuation.CostOfDebt=0.00;  
      }
      else
      {
        dcfValuation.InterestRate=dcfValuation.InterestExpense/dcfValuation.TotalDebt;        
        dcfValuation.CostOfDebt=dcfValuation.InterestRate*(1-dcfValuation.CorporateTaxRate);  
      }
      return true;
    }
    private static bool CalculateCostOfDebt(DCFValuation dcfValuation,double totalDebt,DateTime asOf)
    {             
      TimeSeriesCollection interestExpenseSeries = HistoricalDA.GetTimeSeriesMaxAsOf(dcfValuation.Symbol,asOf, TimeSeriesElement.ElementType.InterestExpense);
      if (null == interestExpenseSeries || 0 == interestExpenseSeries.Count)
      {
        IncomeStatement incomeStatement = IncomeStatementDA.GetIncomeStatementMaxAsOf(dcfValuation.Symbol,asOf, IncomeStatement.PeriodType.Annual);
        if(null!=incomeStatement&&incomeStatement.InterestExpense.Equals(double.NaN))
        {
          dcfValuation.InterestExpense=0.00;
        }
        else if(null==incomeStatement)
        {
          dcfValuation.Message = "Missing IncomeStatement or Interest Expense ";
          return false;
        }
        else dcfValuation.InterestExpense=incomeStatement.InterestExpense;
      }
      else
      {
        if(interestExpenseSeries[0].Value.Equals(double.NaN))dcfValuation.InterestExpense=0.00;
        else dcfValuation.InterestExpense=interestExpenseSeries[0].Value;
      }
      TimeSeriesCollection timeSeriesCollection = HistoricalDA.GetTimeSeriesMaxAsOf(dcfValuation.Symbol,asOf, TimeSeriesElement.ElementType.TaxRate);
      if (null == timeSeriesCollection || 0 == timeSeriesCollection.Count) timeSeriesCollection.Add(new TimeSeriesElement() {Value=37 });
      TimeSeriesElement timeSeriesElement = timeSeriesCollection[0];
      dcfValuation.CorporateTaxRate = timeSeriesElement.Value / 100.00;
      dcfValuation.TotalDebt = totalDebt;
      if(0.00==totalDebt)
      {
        dcfValuation.InterestRate=0.00;        
        dcfValuation.CostOfDebt=0.00;  
      }
      else
      {
        dcfValuation.InterestRate=dcfValuation.InterestExpense/dcfValuation.TotalDebt;        
        dcfValuation.CostOfDebt=dcfValuation.InterestRate*(1-dcfValuation.CorporateTaxRate);  
      }
      return true;
    }
  }
}
