using System;
using System.Net;
using System.Diagnostics;
using System.Windows;
using System.Windows.Data;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.ComponentModel;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using C1.Silverlight.Chart;

namespace InvExplorer
{
    public class PortfolioChart : C1Chart
    {
        // ** fields
        ICommand _setMinDateCommand;
        Storyboard _sbCreateChart;
        Portfolio _portfolio;

        // ** ctor
        public PortfolioChart()
        {
            // expose command to set the chart range
            _setMinDateCommand = new SetMinDateCommand(this);

            // initialize chart
            MinDate = DateTime.Today.AddMonths(-12);

            // initialize chart axes
            var ax = View.AxisX;
            ax.IsTime = true;
            ax.MajorGridStroke = null;
            ax.MinorTickHeight = 0;
            var ay = View.AxisY;
            ay.MajorGridStroke = new SolidColorBrush(Color.FromArgb(0x30, 0, 0, 0));
            ay.MajorGridStrokeDashes = null;
            ay.MajorTickHeight = 5;

            // add legend
            var legend = new C1ChartLegend();
            legend.Orientation = Orientation.Horizontal;
            legend.HorizontalAlignment = HorizontalAlignment.Left;
            legend.Position = LegendPosition.Top;
            //legend.FontWeight = FontWeights.Bold;
            Children.Add(legend);
        }

        // ** object model

        /// <summary>
        /// Gets or sets the minimum date to show in the chart.
        /// </summary>
        public DateTime MinDate
        {
            get { return (DateTime)GetValue(MinDateProperty); }
            set { SetValue(MinDateProperty, value); }
        }
        /// <summary>
        /// Identifies the <see cref="MinDate"/> dependency property.
        /// </summary>
        public static readonly DependencyProperty MinDateProperty =
            DependencyProperty.Register(
                "MinDate", typeof(DateTime), typeof(PortfolioChart),
                new PropertyMetadata(DateTime.MinValue, OnPropertyChanged));
        /// <summary>
        /// Gets or sets the minimum date to show in the chart.
        /// </summary>
        public Portfolio Portfolio
        {
            get { return (Portfolio)GetValue(PortfolioProperty); }
            set { SetValue(PortfolioProperty, value); }
        }
        /// <summary>
        /// Identifies the <see cref="Portfolio"/> dependency property.
        /// </summary>
        public static readonly DependencyProperty PortfolioProperty =
            DependencyProperty.Register(
                "Portfolio", typeof(Portfolio), typeof(PortfolioChart),
                new PropertyMetadata(OnPropertyChanged));
        /// <summary>
        /// Command used to set the chart's MinDate property.
        /// </summary>
        public ICommand SetMinDate
        {
            get { return _setMinDateCommand; }
        }

        // ** implementation

        // chart property changed, update chart
        static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var chart = (PortfolioChart)d;
            chart.OnPropertyChanged(e);
        }
        void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
        {
            UpdateNotifications();
            UpdateChart();
        }
        void UpdateNotifications()
        {
            // we may have changed the portfolio or the items in a portfolio
            var items = _portfolio != null ? _portfolio.Items : null;
            if (items != null)
            {
                items.CollectionChanged -= Items_CollectionChanged;
                items.ItemPropertyChanged -= Items_CollectionChanged;
            }

            // change portfolio
            _portfolio = Portfolio;

            // connect notifications for current portfolio
            items = Portfolio != null ? Portfolio.Items : null;
            if (items != null)
            {
                items.CollectionChanged += Items_CollectionChanged;
                items.ItemPropertyChanged += Items_CollectionChanged;
            }
        }

        // portfolio changed, update chart
        void Items_CollectionChanged(object sender, EventArgs e)
        {
            UpdateNotifications();
            UpdateChart();
        }

        // portfolio item changed, update chart
        void Item_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            UpdateChart();
        }

        // price data arrived, update chart
        void Prices_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            UpdateChart();
        }

        // re-create chart on a timer to avoid doing it too often
        void UpdateChart()
        {
            if (_sbCreateChart == null)
            {
                _sbCreateChart = new Storyboard();
                _sbCreateChart.Duration = new Duration(TimeSpan.FromMilliseconds(250));
                _sbCreateChart.Completed += UpdateChart_Completed;
            }
            _sbCreateChart.Stop();
            _sbCreateChart.Seek(TimeSpan.Zero);
            _sbCreateChart.Begin();
        }

        // create the chart
        void UpdateChart_Completed(object sender, EventArgs e)
        {
            // remove all existing series
            Data.Children.Clear();

            // count how many items we have to add to the chart
            var cnt = 0;
            var items = Portfolio != null ? Portfolio.Items : null;
            if (items != null)
            {
                foreach (PortfolioItem item in items)
                {
                    if (item.ShowChart)
                        cnt++;
                }
            }

            // show relative or absolute values depending on series count
            var relativePrices = cnt > 1;
            ChartType = relativePrices ? ChartType.Line : ChartType.Area;;

            // update axes
            var ay = View.AxisY;
            ay.AnnoFormat = relativePrices ? "p0" : "n0";
            var ax = View.AxisX;
            ax.AnnoFormat = MinDate > DateTime.MinValue && DateTime.Now.Subtract(MinDate).TotalDays < 45
                ? "dd/MM"
                : "MMM yy";

            // add series
            if (cnt > 0)
            {
                var palette = ViewModel.Palette;
                this.BeginUpdate();
                for (int i = 0; i < items.Count; i++)
                {
                    var item = items[i];
                    if (item.ShowChart && item.PriceHistory.Count > 0)
                    {
                        var brush = new SolidColorBrush(palette[i % palette.Length]);
                        var ds = CreateSeries(item, brush, relativePrices);
                        Data.Children.Add(ds);
                    }
                }
                this.EndUpdate();
            }
        }

        // create a new data series
        DataSeries CreateSeries(PortfolioItem item, SolidColorBrush brush, bool relativePrices)
        {
            // create new series
            var ds = new XYDataSeries();
            ds.Name = ds.Label = item.Symbol;
            ds.ConnectionStrokeThickness = 2;
            ds.ConnectionStroke = brush;
            if (ChartType == ChartType.Area)
            {
                ds.ConnectionFill = GetFillBrush(brush);
            }

            // get reference for relative values
            var referencePrice = double.NaN;
            var referenceDate = DateTime.Today;
            if (relativePrices)
            {
                foreach (var p in item.PriceHistory)
                {
                    if (p.Date >= MinDate && p.Date < referenceDate)
                    {
                        referenceDate = p.Date;
                        referencePrice = p.Price;
                    }
                }
            }

            // build series
            var x = new DoubleCollection();
            var y = new DoubleCollection();
            foreach (var p in item.PriceHistory)
            {
                if (p.Date >= MinDate)
                {
                    x.Add(p.Date.ToOADate());
                    y.Add(relativePrices ? p.Price / referencePrice - 1 : p.Price);
                }
                ds.XValues = x;
                ds.Values = y;
            }

            // add series tooltip
            //var tip = string.Format("{0}\r\n{1}", item.Symbol, item.Name);
            //ToolTipService.SetToolTip(ds, tip);

            // done
            return ds;
        }
        Brush GetFillBrush(SolidColorBrush brush)
        {
            var c = brush.Color;
            var br = new LinearGradientBrush();
            br.GradientStops.Add(new GradientStop() { Color = Color.FromArgb(0xe0, c.R, c.G, c.B), Offset = 0 });
            br.GradientStops.Add(new GradientStop() { Color = Color.FromArgb(0x10, c.R, c.G, c.B), Offset = 1 });
            br.StartPoint = new Point(0, 0);
            br.EndPoint = new Point(0, 1);
            return br;
        }
    }
    /// <summary>
    /// Command to set the chart range.
    /// </summary>
    public class SetMinDateCommand : ICommand
    {
        PortfolioChart _chart;
        public SetMinDateCommand(PortfolioChart chart)
        {
            _chart = chart;
        }
        public bool CanExecute(object parameter)
        {
            return true;
        }
        public event EventHandler CanExecuteChanged;
        public void Execute(object parameter)
        {
            var p = parameter as string;
            switch (p.ToLower())
            {
                case "ytd":
                    _chart.MinDate = new DateTime(DateTime.Today.Year, 1, 1);
                    break;
                case "all":
                    _chart.MinDate = DateTime.MinValue;
                    break;
                default:
                    var months = int.Parse(p);
                    _chart.MinDate = DateTime.Today.AddMonths(-months);
                    break;
            }
        }
    }
}
