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


// This cache is mainly used by the models.  It is a short lived cache that gets cleared out every 2 minutes.
// This cache will attempt to load a price from the database if it is found in the cache.
namespace MarketData.Cache
{
  public class GBPriceCache
  {
    private Thread cacheMonitorThread=null;
    private volatile bool threadRun=true;
    private Object thisLock=new Object();
    private Dictionary<String,PricesByDate> priceCache=new Dictionary<String,PricesByDate>();      // the main cache
    private Dictionary<String,Price> realTimePriceCache=new Dictionary<String,Price>();            // short lived cache of realtime prices gets cleared out every cacheRefreshAfter(ms)
    private Dictionary<String,bool> nullCache=new Dictionary<String,bool>();
	  private DateGenerator dateGenerator=new DateGenerator();
    private static GBPriceCache priceCacheInstance=null;
    private int cacheRefreshAfter=120000;                                                          // the cache will be cleaned up after 2 minutes

    protected GBPriceCache()
    {
      cacheMonitorThread=new Thread(new ThreadStart(ThreadProc));
      cacheMonitorThread.Start();
    }
    public static GBPriceCache GetInstance()
    {
      lock(typeof(GBPriceCache))
      {
        if(null==priceCacheInstance)
        {
          priceCacheInstance=new GBPriceCache();
        }
        return priceCacheInstance;
      }
    }
    public void Clear()
    {
      lock(thisLock)
      {
        priceCache=new Dictionary<String,PricesByDate>();     
        realTimePriceCache=new Dictionary<String,Price>();           
        nullCache=new Dictionary<String,bool>();
      }
    }
    public void Dispose()
    {
      lock(thisLock)
      {
        if(null==priceCacheInstance || false==threadRun)return;
        threadRun=false;
        if(null!=cacheMonitorThread)
        {
          MDTrace.WriteLine(LogLevel.DEBUG,String.Format("[GBPriceCache:Dispose]Thread state is '{0}'.  Joining main thread...",Utility.ThreadStateToString(cacheMonitorThread)));
          cacheMonitorThread.Join(5000);
          this.cacheMonitorThread=null;
        }
        MDTrace.WriteLine(LogLevel.DEBUG,"[GBPriceCache:Dispose] End.");
        priceCacheInstance=null;
      }
    }
    public void ClearCacheOnOrBefore(DateTime onOrBeforeDate,bool collect=false)
    {
      lock(thisLock)
      {
        MDTrace.WriteLine(LogLevel.DEBUG,"Clearing GBPriceCache cache.");
        List<String> symbols=new List<String>(priceCache.Keys);
        foreach(String symbol in symbols)
        {
          PricesByDate pricesByDate=priceCache[symbol];
          List<DateTime> symbolDates=new List<DateTime>(pricesByDate.Keys);
          foreach(DateTime symbolDate in symbolDates)
          {
            if(symbolDate<onOrBeforeDate) pricesByDate.Remove(symbolDate);
          }
        }
        MDTrace.WriteLine(LogLevel.DEBUG,"Calling garbage collector...");
        if(collect) GC.Collect();
      }
    }
    public Price GetPriceOrLatestAvailable(String symbol,DateTime date)
    {
      lock(thisLock)
      {
        Price price=GetPrice(symbol,date);
        if(null!=price) return price;
        DateTime latestPricingDate=PricingDA.GetLatestDateOnOrBefore(symbol,date);
        price=GetPrice(symbol,latestPricingDate);
        if(null!=price) return price;
        price=PricingDA.GetPrice(symbol,latestPricingDate);
        if(null!=price) AddPrice(price);
        return price;
      }
    }
    public Price GetRealtimePrice(String symbol)
    {
      if(realTimePriceCache.ContainsKey(symbol)) return realTimePriceCache[symbol];
      Price price=MarketDataHelper.GetLatestPrice(symbol);
      if(null!=price)
      {
        realTimePriceCache.Add(symbol,price);
      }
      return price;
    }
    public Price GetPrice(String symbol,DateTime date)
    {
      lock(thisLock)
      {
        date=date.Date;
        if(!ContainsPrice(symbol,date))
        {
          String key=symbol+Utility.DateTimeToStringMMHDDHYYYY(date);
          if(nullCache.ContainsKey(key)) return null;
          Price price=PricingDA.GetPrice(symbol,date);
          if(null==price)
          {
            nullCache.Add(key,true);
            return price;
          }
          AddPrice(price);
        }
        if(!priceCache.ContainsKey(symbol)) return null;
        PricesByDate pricesByDate=priceCache[symbol];
        if(!pricesByDate.ContainsKey(date.Date)) return null;
        return pricesByDate[date];
      }
    }

    public Prices GetPrices(String symbol, DateTime earlierDate, DateTime laterDate)
    {
      DateGenerator dateGenerator = new DateGenerator();

      if(laterDate<earlierDate)
      {
        DateTime tempDate = earlierDate;
        earlierDate = laterDate;
        laterDate=tempDate;
      }
      List<DateTime> datesList = dateGenerator.GenerateHistoricalDates(earlierDate, laterDate);
      datesList = datesList.Where(x => x >= earlierDate).ToList();
      return GetPrices(symbol, laterDate, datesList.Count);
    }

// The most recent price is returned at the lowest index
    public Prices GetPrices(String symbol,DateTime startDate,int dayCount)
    {
      lock(thisLock)
      {
        List<DateTime> historicalDates=dateGenerator.GenerateHistoricalDates(startDate,dayCount+60);
        Prices prices=null;
				List<DateTime> missingDates=null;
        foreach(DateTime historicalDate in historicalDates)
        {
          if(!ContainsPrice(symbol,historicalDate))
          {
            String key=symbol+Utility.DateTimeToStringMMHDDHYYYY(historicalDate);
            if(nullCache.ContainsKey(key)) continue;
			      if(null==missingDates)missingDates=new List<DateTime>();
            missingDates.Add(historicalDate);
          }
        }
        if(null!=missingDates)
        {
          DateTime minDate=(from DateTime date in missingDates select date).Min();
          DateTime maxDate=(from DateTime date in missingDates select date).Max();
          prices=PricingDA.GetPrices(symbol,maxDate,minDate);
          foreach(Price price in prices) AddPrice(price);
          prices=new Prices();
          foreach(DateTime historicalDate in historicalDates)
          {
            if(!ContainsPrice(symbol,historicalDate))
            {
              String key=symbol+Utility.DateTimeToStringMMHDDHYYYY(historicalDate);
              if(!nullCache.ContainsKey(key)) nullCache.Add(key,true);
            }
            else
            {
              if(!priceCache.ContainsKey(symbol)) continue;
              PricesByDate pricesByDate=priceCache[symbol];
              if(!pricesByDate.ContainsKey(historicalDate.Date)) continue;
              prices.Add(pricesByDate[historicalDate]);
            }
          }
        }
        else
        {
          prices=new Prices();
          foreach(DateTime historicalDate in historicalDates)
          {
            if(!priceCache.ContainsKey(symbol)) continue;
            if(!priceCache[symbol].ContainsKey(historicalDate.Date))
            {
              continue;
            }
            prices.Add((priceCache[symbol])[historicalDate]);
          }
        }
        return new Prices(prices.OrderByDescending(x => x.Date).ToList().Take(dayCount).ToList());
      }
    }
    private void AddPrice(Price price)
    {
      lock(thisLock)
      {
        if(null==price) return;
        PricesByDate pricesByDate=null;
        if(!priceCache.ContainsKey(price.Symbol))
        {
          pricesByDate=new PricesByDate();
          pricesByDate.Add(price.Date,price);
          priceCache.Add(price.Symbol,pricesByDate);
        }
        else
        {
          pricesByDate=priceCache[price.Symbol];
          if(pricesByDate.ContainsKey(price.Date.Date)) return;
          pricesByDate.Add(price.Date.Date,price);
        }
      }
    }
    public bool ContainsPrice(String symbol,DateTime date)
    {
      if(!priceCache.ContainsKey(symbol)) return false;
      PricesByDate pricesByDate=priceCache[symbol];
      if(!pricesByDate.ContainsKey(date.Date)) return false;
      return true;
    }
    private void ThreadProc()
    {
      int quantums=0;
      int quantumInterval=1000;
      while(threadRun)
      {
        Thread.Sleep(quantumInterval);
        if(!threadRun) break;
        quantums+=quantumInterval;
        if(quantums>cacheRefreshAfter)
        {
          quantums=0;
          lock(thisLock)
          {
            realTimePriceCache.Clear();
            MDTrace.WriteLine(LogLevel.DEBUG,"Clearing GBPriceCache price cache.");
          }
        }
      }
      MDTrace.WriteLine(LogLevel.DEBUG,"[GBPriceCache:ThreadProc]Thread ended.");
    }
  }
}

