using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Diagnostics;
using System.ServiceModel;
using C1.Silverlight.Chart;
using C1.Silverlight.Data;
using StockPortfolio.localhost;
using System.Windows.Controls.Primitives;

namespace StockPortfolio
{
    public class ChartLogic
    {
        enum TimeRange
        {
            All,
            TwoYears,
            Year,
            YTD,
            SixMonths,
            ThreeMonths,
            Month
        }

        UserControl  _parent;
        C1.Silverlight.C1RangeSlider  _slider;
        C1Chart      _chartUpper;
        C1Chart      _chartLower;
        TextBlock      _lblRange;
        StackPanel _symHolder;

        Rectangle _paUpper;
        Rectangle _paLower;

        DateTime _min = new DateTime(2007, 8, 1);//(0 DateTime.MaxValue;
        DateTime _max = new DateTime(2007, 9, 1);

        DateTime _activeMin = new DateTime(2007, 8, 1);
        DateTime _activeMax = new DateTime(2007, 9, 1);

        StockInfoServiceClient _siservice;

        double _scale = 0.1;

        Storyboard _stb;
        bool _stb_finished = false;

        bool _canUpdateSlider = false;

        List<Ellipse> _marks = new List<Ellipse>();
        Line _vline;

        public ChartLogic(UserControl parent, C1Chart upper, C1Chart lower,
            C1.Silverlight.C1RangeSlider slider, StackPanel symHolder,
            TextBlock lblRange)
        {
            _parent = parent;

            _chartUpper = upper;
            _vline = new Line();
            _vline.Stroke = new SolidColorBrush(
                Color.FromArgb(0xFF, 0x3F, 0x8c, 0xe5));
            _chartUpper.View.Children.Add(_vline);

            _paUpper = new Rectangle();
            _paUpper.SetValue(Canvas.ZIndexProperty, -1);
            _paUpper.Fill = new SolidColorBrush(Colors.White);
            _chartUpper.View.Children.Add(_paUpper);

            _chartUpper.LegendChanged += new EventHandler(_chartUpper_LegendChanged);
            _chartUpper.MouseMove += new MouseEventHandler(_chartUpper_MouseMove);
            _chartUpper.Data.DataChanged += new EventHandler(Data_DataChanged);
            _chartUpper.SizeChanged += new SizeChangedEventHandler(_chartUpper_SizeChanged);

            _chartLower = lower;
            _chartLower.View.AxisY.Visible = false;

            _paLower = new Rectangle();
            _paLower.SetValue(Canvas.ZIndexProperty, -1);
            _paLower.Fill = new SolidColorBrush(Colors.White);
            _chartLower.View.Children.Add(_paLower);
            _chartLower.LegendChanged += new EventHandler(_chartLower_LegendChanged);

            _slider = slider;
            _slider.Visibility = Visibility.Collapsed;
            _slider.LowerValueChanged+=new EventHandler(_slider_ValueChanged);
            _slider.UpperValueChanged += new EventHandler(_slider_ValueChanged);

            _symHolder = symHolder;

            _lblRange = lblRange;

            SetTimeRange(TimeRange.YTD);
        }

        void _chartUpper_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            RemoveMarks();
        }

        void Data_DataChanged(object sender, EventArgs e)
        {
            RemoveMarks();
        }

        void _chartUpper_LegendChanged(object sender, EventArgs e)
        {
            Rect prect = _chartUpper.View.PlotRect;
            _paUpper.SetValue(Canvas.LeftProperty, prect.Left);
            _paUpper.SetValue(Canvas.TopProperty, prect.Top);
            _paUpper.Width = prect.Width; _paUpper.Height = prect.Height;

            // update the list of marks
            int len = _chartUpper.LegendItems.Count;
            if (_marks.Count == len)
                return;

            RemoveMarks();

            for (int i = 0; i < len; i++)
            {
                LegendItem li = _chartUpper.LegendItems[i];
                var pe = ((DataSeries)li.Item).Connection as PlotElement;
                if (pe == null)
                  continue;

                Ellipse mark = new Ellipse();
                mark.Width = mark.Height = 6;
                mark.Fill = pe.Stroke;
                mark.Visibility = Visibility.Collapsed;
                _marks.Add(mark);
                _chartUpper.View.Children.Add(mark);
            }
        }

        private void RemoveMarks()
        {
            foreach (Ellipse mark in _marks)
            {
                if (_chartUpper.View.Children.Contains(mark))
                    _chartUpper.View.Children.Remove(mark);
            }
            _marks.Clear();
            _vline.Visibility = Visibility.Collapsed;
        }

        void SetSymValue(string sym, string val)
        {
            int cnt = _symHolder.Children.Count;
            for (int i = 0; i < cnt; i++)
            {
                SymbolButton btn = _symHolder.Children[i] as SymbolButton;
                if (btn != null && (string)btn.Tag == sym)
                    btn.Text = string.Format("{0}:{1}", btn.Tag, val);
            }
        }

        void _chartUpper_MouseMove(object sender, MouseEventArgs e)
        {
            _vline.Visibility = Visibility.Visible;
            Point mp = e.GetPosition(_chartUpper);
            Rect prect = _chartUpper.View.PlotRect;

            if (prect.Contains(mp))
            {
                Point dp = _chartUpper.View.PointToData(mp);
                _vline.Y1 = prect.Top; _vline.Y2 = prect.Bottom;

                int len = _chartUpper.Data.Children.Count;

                for (int i = 0; i < len; i++)
                {
                    XYDataSeries ds = (XYDataSeries)_chartUpper.Data.Children[i];

                    int di = XValueToIndex(ds, dp.X);
                    Point dpt = _chartUpper.View.DataIndexToPoint(i, di);

                    if (ds.ValuesSource is double[])
                    {
                        _vline.X1 = _vline.X2 = dpt.X;
                        SetSymValue(ds.Label, string.Format(len > 1 ?
                        "{0:0.0%}" : "{0:0.0}", ((double[])ds.ValuesSource)[di]));
                    }
                    else
                        SetSymValue(ds.Label, "");

                    if (i < _marks.Count)
                    {
                        Ellipse mark = _marks[i];

                        if (double.IsNaN(dpt.X))
                            mark.Visibility = Visibility.Collapsed;
                        else
                        {
                            mark.Visibility = Visibility.Visible;
                            mark.SetValue(Canvas.LeftProperty, dpt.X - 0.5 * mark.Width);
                            mark.SetValue(Canvas.TopProperty, dpt.Y - 0.5 * mark.Height);
                        }
                    }
                }
            }
        }

        void _slider_ValueChanged(object sender, EventArgs e)
        {
            Debug.WriteLine(_slider.UpperValue - _slider.LowerValue);
            if (_canUpdateSlider)
            {
                _canUpdateSlider = false;
                double scale = _slider.UpperValue - _slider.LowerValue;
                UpdateChart(false);
                _canUpdateSlider = true;
            }
        }

        void _chartLower_LegendChanged(object sender, EventArgs e)
        {
            if (_chartLower.Data.Children.Count == 0)
                return;

            Rect prect = _chartLower.View.PlotRect;
            _paLower.SetValue(Canvas.LeftProperty, prect.Left);
            _paLower.SetValue(Canvas.TopProperty, prect.Top);
            _paLower.Width = prect.Width; _paLower.Height = prect.Height;

            if (_slider.Visibility != Visibility.Visible)
            {
                double total = (_max - _min).TotalDays;
                _slider.LowerValue = (_activeMin - _min).TotalDays / total;
                _canUpdateSlider = true;
                _slider.Visibility = Visibility.Visible;
            }
        }

        internal StockInfoServiceClient StockInfo
        {
            get
            {
                if (_siservice == null)
                {
                    BasicHttpBinding binding = new BasicHttpBinding();
                    binding.MaxReceivedMessageSize = 2147483647; // int.MaxValue
                    binding.MaxBufferSize = 2147483647; // int.MaxValue
                    string url = Main.GetAbsoluteUri("StockInfoService.svc");
                    EndpointAddress address = new EndpointAddress(url);
                    _siservice = new StockInfoServiceClient(binding, address);
                }

                return _siservice;
            }
        }

        void UpdateChart(double min, double max)
        {
            _chartUpper.BeginUpdate();

            DataSeriesCollection sc = _chartUpper.Data.Children;
            int cnt = sc.Count;
            for (int i = 0; i < cnt; i++)
            {
                XYDataSeries ds = (XYDataSeries)sc[i];

                if (i == 0)  
                {
                    if (cnt == 1)
                    {
                        // change lines -> area
                        if (!(ds.Connection is Area))
                        {
                            SolidColorBrush stroke = ((Lines)ds.Connection).Stroke as SolidColorBrush;
                            Area area = new Area();

                            if (stroke != null)
                                area.Stroke = new SolidColorBrush(stroke.Color);
                            else
                                area.Stroke = new SolidColorBrush(Color.FromArgb(0xff, 0x3f, 0x8c, 0xe5));
                            area.Fill = new SolidColorBrush(Color.FromArgb(0xcb, 0xbb, 0xe1, 0xff));
                            ds.Connection = area;
                        }
                    }
                    else
                    {
                        // change area -> lines
                        if (ds.Connection is Area)
                        {
                            SolidColorBrush stroke = ((Area)ds.Connection).Stroke as SolidColorBrush;
                            Lines lines = new Lines();

                            if (stroke != null)
                                lines.Stroke = new SolidColorBrush(stroke.Color);
                            else
                                lines.Stroke = new SolidColorBrush(Color.FromArgb(0xff, 0x3f, 0x8c, 0xe5));
                            ds.Connection = lines;
                        }
                    }
                }

                HistoricalStockInfo hsinfo = ds.Tag as HistoricalStockInfo;
                if (hsinfo != null)
                {
                    int len = hsinfo._values.Length;
                    double dt2 = hsinfo._lastDate.ToOADate();
                    double dt1 = dt2 - len + 1;

                    int k1 = dt1 >= min ? 0 : (int)(min - dt1);
                    int k2 = dt2 <= max ? len - 1 : (int)(max - dt1);

                    int len0 = k2 - k1 + 1;

                    if (len0 > 0)
                    {
                        double[] vals = new double[len0];
                        double[] dates = new double[len0];
                        double last = hsinfo._lastDate.ToOADate();

                        for (int k = k1; k <= k2; k++)
                        {
                            if (cnt > 1)
                                vals[k - k1] = (hsinfo._values[k] - hsinfo._values[k1]) / hsinfo._values[k1];
                            else
                                vals[k - k1] = hsinfo._values[k];
                            dates[k - k1] = last + (k + 1 - len);
                        }

                        ds.ValuesSource = vals; ds.XValuesSource = dates;
                    }
                    else
                    {
                        ds.ValuesSource = null; ds.XValuesSource = null;
                    }
                }
            }
            Axis ax = _chartUpper.View.AxisX;
            ax.Min = min; ax.Max = max;
            _chartUpper.View.AxisY.AnnoFormat = cnt > 1 ? "0%" : "f2";

            _chartUpper.EndUpdate();
        }

        internal void UpdateChart(bool immediate)
        {
            if (_min >= _max)
                return;

            _activeMin = DateTime.FromOADate(_min.ToOADate() + _slider.LowerValue * 
                (_max.ToOADate() - _min.ToOADate())).Date;

            _activeMax = DateTime.FromOADate(_min.ToOADate() + _slider.UpperValue *
              (_max.ToOADate() - _min.ToOADate())).Date;

            if (_activeMin == _activeMax)
            {
                _activeMax = _activeMin.AddDays(1);
            }

            double total = (_max - _min).TotalDays;

            _lblRange.Text = string.Format("{0:MMM dd,yyyy} - {1:MMM dd,yyyy}", _activeMin, _activeMax);

            /*UpdateInfoPanel();

            Rect prc = _chartFull.View.PlotRect;
            Point p1 = _chartFull.View.PointFromData(
              new Point(_activeMin.ToOADate(), 0));
            Point p2 = _chartFull.View.PointFromData(
              new Point(_activeMax.ToOADate(), 0));
            _selectedArea.SetValue<double>(Canvas.LeftProperty, p1.X);
            _selectedArea.SetValue<double>(Canvas.TopProperty, prc.Top);
            _selectedArea.Width = (p2.X - p1.X);
            _selectedArea.Height = prc.Height;*/

            foreach (Ellipse mark in _marks)
                mark.Visibility = Visibility.Collapsed;

            if (immediate)
            {
                UpdateChart(_activeMin.ToOADate(), _activeMax.ToOADate());
            }
            else
            {
                try
                {
                    if (_stb == null)
                    {
                        _stb = new Storyboard();
                        _stb.Duration = new Duration(TimeSpan.FromMilliseconds(40));
                        _stb.Completed += new EventHandler(_stb_Completed);
                        _stb_finished = true;
                    }
                    else if (_stb_finished)
                        _stb.Begin();
                }
                catch { }
            }
        }

        void SetTimeRange(TimeRange range)
        {
            if (_min >= _max)
                return;

            DateTime dt = new DateTime();

            // save left slider
            double val = _slider.LowerValue;
            double lowValue;

            switch (range)
            {
                case TimeRange.All:
                    _slider.LowerValue = 0;
                    _slider.UpperValue = 1;
                    break;
                case TimeRange.YTD:
                    _slider.UpperValue = 1;
                    lowValue = 1 - (365 / (_max - _min).TotalDays);
                    _slider.LowerValue = (lowValue >= 0 ? lowValue : 0);
                    break;
                case TimeRange.TwoYears:
                    lowValue = _slider.UpperValue - (730 / (_max - _min).TotalDays);
                    _slider.LowerValue = (lowValue >= 0 ? lowValue : 0);
                    break;
                case TimeRange.Year:
                    lowValue = _slider.UpperValue - (365 / (_max - _min).TotalDays);
                    _slider.LowerValue = (lowValue >= 0 ? lowValue : 0);
                    break;
                case TimeRange.Month:
                    lowValue = _slider.UpperValue - (30 / (_max - _min).TotalDays);
                    _slider.LowerValue = (lowValue >= 0 ? lowValue : 0);
                    break;
                case TimeRange.SixMonths:
                    lowValue = _slider.UpperValue - (182 / (_max - _min).TotalDays);
                    _slider.LowerValue = (lowValue >= 0 ? lowValue : 0);
                    break;
                case TimeRange.ThreeMonths:
                    lowValue = _slider.UpperValue - (90 / (_max - _min).TotalDays);
                    _slider.LowerValue = (lowValue >= 0 ? lowValue : 0);
                    break;
            }

            UpdateChart(true);

        }

        /// <summary>
        /// Performs real update after a small pause.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void _stb_Completed(object sender, EventArgs e)
        {
            UpdateChart(_activeMin.ToOADate(), _activeMax.ToOADate());
            _stb_finished = true;
        }

        private string ReplaceSpecialSyms(string sym)
        {
            switch (sym)
            {
                case "Nasd":
                    return "!NASDAQ";
                case "Dow":
                    return "!DOW";
                case "S&P500":
                    return "!S&P500";
            }
            return sym;
        }

        public void Add(string sym)
        {
            var hsinfo = ClientStockInfo.GetHistoricalStockInfo(sym);
            if (hsinfo != null)
            {
                int len = hsinfo._values != null ? hsinfo._values.Length : 0;
                if (len > 0)
                {
                    if (_min > hsinfo._lastDate.AddDays(-len + 1))
                        _min = hsinfo._lastDate.AddDays(-len + 1);
                    if (_max < hsinfo._lastDate)
                        _max = hsinfo._lastDate;
                    AddFullChart(hsinfo);

                    _chartUpper.BeginUpdate();

                    XYDataSeries ds = new XYDataSeries();
                    //string sym = e.UserState.ToString();
                    ds.Label = sym;

                    DataSeriesCollection sc = _chartUpper.Data.Children;
                    Lines lines;

                    if (sc.Count == 0)
                    {
                        lines = new Area();
                        lines.Stroke = new SolidColorBrush(Color.FromArgb(0xff, 0x3f, 0x8c, 0xe5));
                        lines.Fill = new SolidColorBrush(Color.FromArgb(0xcb, 0xbb, 0xe1, 0xff));
                    }
                    else
                        lines = new Lines();

                    Brush stroke = CreateBrush(sym);
                    if (stroke != null)
                        lines.Stroke = stroke;

                    lines.StrokeThickness = 1;
                    lines.IsHitTestVisible = false;

                    //if (sc.Count > 0)
                    {
                        lines.Created += new EventHandler(plotElement_Created);
                        lines.Loaded += new RoutedEventHandler(plotElement_Loaded);
                    }

                    ds.Symbol = null;
                    ds.Connection = lines;
                    ds.Tag = hsinfo;

                    sc.Add(ds);

                    UpdateChart(true);

                    _chartUpper.EndUpdate();
                }
            }
        }

        public void Remove(string sym)
        {
            DataSeries ds0 = null;

            foreach (DataSeries ds in _chartUpper.Data.Children)
            {
                if (ds.Label == sym)
                {
                    ds0 = ds; break;
                }
            }

            if (ds0 != null)
            {
                _chartUpper.Data.Children.Remove(ds0);

                if (_chartLower.Data.Children.Count > 0)
                {
                    if (_chartLower.Data.Children[0].Label == sym)
                    {
                        _chartLower.Data.Children.RemoveAt(0);

                        if (_chartUpper.Data.Children.Count > 0)
                            AddFullChart(_chartUpper.Data.Children[0].Tag as HistoricalStockInfo);
                    }
                }

                UpdateChart(true);
            }
            else
                Debug.WriteLine(string.Format("Symbol {0} is not in chart.", sym));
        }

        public void RemoveAll()
        {
            _chartUpper.Data.Children.Clear();
        }

        public void SetTimeRange(string range)
        {
            switch (range)
            {
                case "ALL":
                    SetTimeRange(TimeRange.All); break;
                case "2Y":
                    SetTimeRange(TimeRange.TwoYears); break;
                case "1Y":
                    SetTimeRange(TimeRange.Year); break;
                case "YTD":
                    SetTimeRange(TimeRange.YTD); break;
                case "6M":
                    SetTimeRange(TimeRange.SixMonths); break;
                case "3M":
                    SetTimeRange(TimeRange.ThreeMonths); break;
                case "1M":
                    SetTimeRange(TimeRange.Month); break;
            }
        }

        public Color GetSymbolColor(string sym)
        {
            LegendItemCollection items = _chartUpper.LegendItems;
            int cnt = items.Count;
            for (int i = 0; i < cnt; i++)
            {
                if (items[i].Label == sym)
                {
                  var ds = items[i].Item as DataSeries;
                  if (ds != null)
                  {
                    var ps = ds.Connection as PlotElement;
                    if (ps != null)
                    {
                      SolidColorBrush sb = ps.Stroke as SolidColorBrush;
                    if (sb != null)
                        return sb.Color;
                    }
                  }
                }
            }

            return Colors.DarkGray;
        }

        Brush CreateBrush(string sym)
        {
            switch (sym)
            {
                case "Nasd":
                    return new SolidColorBrush(Color.FromArgb(0xff, 0xcc, 0, 0));
                case "Dow":
                    return new SolidColorBrush(Color.FromArgb(0xff, 0xff, 0x99, 0));
                case "S&P500":
                    return new SolidColorBrush(Color.FromArgb(0xff, 0x10, 0x8a, 0x0d));
            }

            return null;
        }

        // add series to the full(bottom) chart if necessary
        void AddFullChart(HistoricalStockInfo hsinfo)
        {
            if (hsinfo == null)
                return;

            _chartLower.BeginUpdate();
            _chartLower.ChartType = ChartType.Area;
            if (_chartLower.Data.Children.Count == 0)
            {
                XYDataSeries ds = new XYDataSeries();
                ds.Label = hsinfo._symbol;

                Area lines = new Area();
                lines.StrokeThickness = 1;
                lines.Stroke = new SolidColorBrush(Color.FromArgb(0xff, 0x3f, 0x8c, 0xe5));
                lines.Fill = new SolidColorBrush(Color.FromArgb(0xcb, 0xbb, 0xe1, 0xff));

                lines.IsHitTestVisible = false;
                ds.Connection = lines;
                ds.Symbol = null;

                int len = hsinfo._values.Length;

                // max polygon size in SL is about 900pts
                // so reduce the number of points 
                const int maxpts = 500;

                if (len > maxpts)
                {
                    float[] vals0 = hsinfo._values;
                    double[] dates0 = DatesFromInfo(hsinfo);
                    int step = len / maxpts + 1;
                    len = len / step;
                    double[] vals = new double[len];
                    double[] dates = new double[len];

                    for (int i = 0; i < len; i++)
                    {
                        vals[i] = vals0[i * step]; dates[i] = dates0[i * step];
                    }
                    ds.ValuesSource = vals;
                    ds.XValuesSource = dates;
                }
                else
                {
                    ds.ValuesSource = hsinfo._values;
                    ds.XValuesSource = DatesFromInfo(hsinfo);
                }

                _chartLower.Data.Children.Add(ds);
            }
            _chartLower.View.AxisX.Min = _min.ToOADate();
            _chartLower.View.AxisX.Max = _max.ToOADate();
            _chartLower.EndUpdate();
        }

        // fast conversion value to index
        int XValueToIndex(XYDataSeries ds, double xval)
        {
            // equidistant
            double[] dates = ds.XValuesSource as double[];
            if (dates == null)
                return 0;
            int len = dates.Length;
            double x1 = dates[0];
            double x2 = dates[len - 1];

            double index = (len - 1) * (xval - x1) / (x2 - x1);

            if (index < 0)
                index = 0;
            else if (index > len - 1)
                index = len - 1;

            return (int)Math.Round(index);
        }

        double[] DatesFromInfo(HistoricalStockInfo hsinfo)
        {
            int len = hsinfo._values.Length;
            double[] dates = new double[len];

            double last = hsinfo._lastDate.ToOADate();
            for (int i = 0; i < len; i++)
                dates[i] = last + i + 1 - len;

            return dates;
        }

        #region Plot elemen Animation
        int cnt = 0; // name counter

        void plotElement_Created(object sender, EventArgs e)
        {
            PlotElement pe = sender as PlotElement;
            if (pe != null)
            {
                // set names
                // M4: NameProperty no longer exists
                pe.SetValue(Control.NameProperty, "n" + cnt.ToString()); cnt++;

                // create transformation
                ScaleTransform st = new ScaleTransform();
                st.ScaleX = 1; st.ScaleY = 0;
                Rect rect = _chartUpper.View.PlotRect;
                st.CenterX = 0; st.CenterY = rect.Bottom;

                pe.RenderTransform = st;
            }
        }

        void plotElement_Loaded(object sender, EventArgs e)
        {
            PlotElement pe = sender as PlotElement;
            if (pe != null)
            {
                Storyboard sb = new Storyboard();
                sb.SetValue(Control.NameProperty, "sb" + pe.Name);

                DoubleAnimation da = new DoubleAnimation();
                da.Duration = new Duration(TimeSpan.FromSeconds(1));
                da.To = 1;
                da.SetValue(Storyboard.TargetNameProperty, pe.Name);
                da.SetValue(Storyboard.TargetPropertyProperty, new PropertyPath("(UIElement.RenderTransform).(ScaleTransform.ScaleY)"));
                sb.Children.Add(da);
                sb.Completed += new EventHandler(sb_Completed);

                _parent.Resources.Add("sb" + pe.Name, sb);

                sb.Begin();
            }
        }

        void sb_Completed(object sender, EventArgs e)
        {
            // remove animation after finishing
            Storyboard sb = sender as Storyboard;
            if (sb != null)
            {
                // M4: Storyboard.Name no longer exists
                // PlotElement pe = _chartUpper.FindName(sb.Name.Substring(2)) as PlotElement;
                // if (pe != null)
                    // pe.Resources.Remove(sb);
                string name = sb.GetValue(Control.NameProperty) as string;
                Shape pe = _chartUpper.FindName(name.Substring(2)) as Shape;

                DataSeriesCollection sc = _chartUpper.Data.Children;
                int cnt = sc.Count;
                for (int i = 0; i < cnt; i++)
                {
                    PlotElement lines = sc[i].Connection as PlotElement;
                    if (lines != null) // stop animation
                    {
                        lines.Created -= new EventHandler(plotElement_Created);
                        lines.Loaded -= new RoutedEventHandler(plotElement_Loaded);
                    }
                }
            }
        }
        #endregion
    }

    /// <summary>
    /// Create HistoricalStockInfo for charting on the client to avoid maintenance
    /// issues (the web service sometimes goes AWOL...)
    /// </summary>
    static class ClientStockInfo
    {
        static Random _random = new Random();
        static Dictionary<string, HistoricalStockInfo> _cacheHistoricalStockInfo = new Dictionary<string, HistoricalStockInfo>();

        public static HistoricalStockInfo GetHistoricalStockInfo(string symbol)
        {
            // get from cache if possible
            HistoricalStockInfo hsi;
            if (_cacheHistoricalStockInfo.TryGetValue(symbol, out hsi))
            {
                return hsi;
            }

            // generate random data to keep the sample small
            // (don't want to download the 80meg stock data file)
            var dt = new DataTable();
            dt.Columns.Add("Date", typeof(DateTime));
            dt.Columns.Add("Close", typeof(float));
            int count = 2500;
            float value = 10;
            DateTime d = DateTime.Today.AddDays(-count);
            for (int i = 0; i < count; i++)
            {
                dt.Rows.Add(d, value);
                value *= (float)(1 + (_random.NextDouble() - 0.49) / 100.0);
                d = d.AddDays(1);
            }

            // convert data into return value, cache it, and return the value
            hsi = CreateHistoricalStockInfo(symbol, dt);
            _cacheHistoricalStockInfo[symbol] = hsi;
            return hsi;
        }
        static HistoricalStockInfo CreateHistoricalStockInfo(string symbol, DataTable dt)
        {
            // save the symbol
            var hsi = new HistoricalStockInfo();
            hsi._symbol = symbol;

            // sort values by date
            if (dt.Rows.Count > 0)
            {
                // get column indices
                int valueIndex = dt.Columns["Close"].Ordinal;
                int dateIndex = dt.Columns["Date"].Ordinal;

                // get date range
                hsi._firstDate = ((DateTime)dt.Rows[0][dateIndex]).Date;
                hsi._lastDate = ((DateTime)dt.Rows[dt.Rows.Count - 1][dateIndex]).Date;

                // build list of values, one per day (filling up any blanks)
                List<float> values = new List<float>();
                DateTime date = hsi._firstDate;
                float value = (float)dt.Rows[0][valueIndex];
                foreach (DataRow dr in dt.Rows)
                {
                    // fill up empty dates (holidays/weekends)
                    DateTime thisDate = (DateTime)dr[dateIndex];
                    while (date < thisDate)
                    {
                        date = date.AddDays(1);
                        values.Add(value);
                    }

                    // add value to the list
                    value = (float)dr[valueIndex];
                    values.Add(value);

                    // move on to next day
                    date = date.AddDays(1);
                }

                // convert values to array
                hsi._values = values.ToArray();
                hsi._valid = true;
            }

            // done
            return hsi;
        }
    }
}
