using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MarketData.ValueAtRisk
{
  public class BinResult<T>
  {
    public double Value{get;private set;}
    public T Item{get;private set;}
    public BinResult()
    {
    }
    public BinResult(double value,T item)
    {
      this.Value=value;
      this.Item=item;
    }
  }
  public class BinManager<T>
  {
    private List<BinItem<T>> binItems;
    private long binCount;
    private long samples;

    public BinManager(long binCount=100)
    {
      this.binCount = binCount;
      InitializeBins();
    }
    private void InitializeBins()
    {
      this.binItems = new List<BinItem<T>>();
      this.samples=0;
    }
    public BinResult<T> GetVaRReturn(double[] values,List<T> items,double percentile) 
    {
      double minValue = float.NaN;
      double maxValue = float.NaN;
      double binSize = double.NaN;
      T item=default(T);

      InitializeBins();
      GetMinMax(ref minValue,ref maxValue,values);
      binSize=(maxValue-minValue)/(double)binCount;
      for (double value = minValue; value < maxValue; value += binSize)
      {
        binItems.Add(new BinItem<T>(value));
      }
      for (int index = 0; index < values.Length; index++)
      {
        AddValueToBin(values[index]);
        AddObjectToBin(values[index],items[index]);
      }
      double samplesAtRank = samples-(samples * (percentile/100.00));
      if (samplesAtRank < 1) samplesAtRank = 1;
      long samplesInRank = 0;
      double percentVaR = 0;
      for (int index = 0; index < binItems.Count; index++)
      {
        BinItem<T> binItem = binItems[index];
        samplesInRank += binItem.BinCount;
        percentVaR = binItem.BinValue;
        item=binItem.BinObject;
        if (samplesInRank > samplesAtRank) break;
      }
      return new BinResult<T>(percentVaR,item);
    }
    public double GetVaRReturn(double[] values,double percentile) 
    {
      double minValue = float.NaN;
      double maxValue = float.NaN;
      double binSize = double.NaN;

      InitializeBins();
      GetMinMax(ref minValue,ref maxValue,values);
      binSize=(maxValue-minValue)/(double)binCount;
      for (double value = minValue; value < maxValue; value += binSize)
      {
        binItems.Add(new BinItem<T>(value));
      }
      for (int index = 0; index < values.Length; index++)
      {
        AddValueToBin(values[index]);
      }
      double samplesAtRank = samples-(samples * (percentile/100.00));
      if (samplesAtRank < 1) samplesAtRank = 1;
      long samplesInRank = 0;
      double percentVaR = 0;
      for (int index = 0; index < binItems.Count; index++)
      {
        BinItem<T> binItem = binItems[index];
        samplesInRank += binItem.BinCount;
        percentVaR = binItem.BinValue;
        if (samplesInRank > samplesAtRank) break;
      }
      return percentVaR;
    }
    private static void GetMinMax(ref double minValue,ref double maxValue,double[] values)
    {
      for (int index = 0; index < values.Length; index++)
      {
        double value = values[index];
        if (0 == index)
        {
          minValue = maxValue = value;
          continue;
        }
        if (value > maxValue) maxValue = value;
        if (value < minValue) minValue = value;
      }
    }
    private void AddValueToBin(double value)
    {
      bool added = false;
      samples++;
      for (int index = 0; index < binItems.Count; index++)
      {
        BinItem<T> binItem = binItems[index];
        if (value <= binItem.BinValue)
        {
          binItem.BinCount++;
          added = true;
          break;
        }
      }
      if (false == added && binItems.Count>0) binItems[binItems.Count - 1].BinCount++;
    }
    private void AddObjectToBin(double value,T item)
    {
      bool added = false;
//      samples++;
      for (int index = 0; index < binItems.Count; index++)
      {
        BinItem<T> binItem = binItems[index];
        if (value <= binItem.BinValue)
        {
          binItem.BinObject=item;
          added = true;
          break;
        }
      }
      if (false == added && binItems.Count>0) binItems[binItems.Count - 1].BinObject=item;
    }
  }
}
