using System;
using System.IO;
using System.ComponentModel;
using System.Globalization;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using C1.Silverlight;
using C1.Silverlight.FlexGrid;
using C1.Silverlight.Chart;

namespace SalesDashboard
{
    public partial class MainPage : UserControl
    {
        // Data model that drives the app (defined as a xaml resource)
        Model _model;

        // Opacity of the region elements when selected and not selected
        const double OPACITY_SELECTED = 0.50;
        const double OPACITY_UNSELECTED = 0.35;
        const double THICKNESS_SELECTED = 5;
        const double THICKNESS_UNSELECTED = 0.5;

        // Initialize the application
        public MainPage()
        {
            // parse xaml
            InitializeComponent();

            // Listen to selection changes
            _model = Resources["_model"] as Model;
            _model.SelectionChanged += _model_SelectionChanged;
            _model.Regions.MoveCurrentToFirst();

            // Add regions to the map as they get added to the model
            _model.Regions.CollectionChanged += Regions_CollectionChanged;

            // Add stores to the map as they get added to the model
            _model.Stores.CollectionChanged += Stores_CollectionChanged;

            // Initialize the chart
            UpdateChart();
        }

        // Update grid, map, chart when selection changes
        void _model_SelectionChanged(object sender, EventArgs e)
        {
            // If a store is selected, show it on the grid
            var store = _model.SelectedStore;
            if (store != null)
            {
                _flex.SelectedItem = store;
            }

            // Show selected region on the map, hide others
            UpdateMap();

            // Update the chart
            UpdateChart();
        }

        //------------------------------------------------------------------
        #region ** grid handling

        // Apply background to group rows when the grid is populated
        void _flex_LoadedRows(object sender, EventArgs e)
        {
            var flex = sender as C1FlexGrid;
            foreach (var row in flex.Rows)
            {
                var region = row.DataItem as RegionData;
                if (region != null)
                {
                    row.HeaderBackground = Util.CreateBrush(region.Color, 0.8);
                    row.Background = row.HeaderBackground;
                    row.FontWeight = FontWeights.Bold;
                }
                else
                {
                    var store = row.DataItem as StoreData;
                    if (store != null)
                    {
                        row.HeaderBackground = Util.CreateBrush(store.Region.Color, 0.8);
                    }
                }
            }
        }

        // If the user selects a region on the grid, select it on the model
        void _flex_SelectionChanged(object sender, CellRangeEventArgs e)
        {
            if (_model != null && _flex != null)
            {
                var store = _flex.SelectedItem as StoreData;
                if (store != null)
                {
                    _model.SelectedStore = store;
                }
                else
                {
                    var region = _flex.SelectedItem as RegionData;
                    if (region != null)
                    {
                        _model.SelectedStore = null;
                        _model.SelectedRegion = region;
                    }
                }
            }
        }

        // Center a region or store when the user double-clicks on it
        void _flex_DoubleClick(object sender, MouseButtonEventArgs e)
        {
            CenterSelection();
        }

        #endregion

        //------------------------------------------------------------------
        #region ** map handling

        RegionData _lastSelectedRegion;

        // Add regions to the map as they are created
        void Regions_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
            {
                // Create a symbol to represent the new region
                var region = e.NewItems[0] as RegionData;
                var symbol = new ESRI.ArcGIS.Client.Symbols.SimpleFillSymbol();
                symbol.Fill = Util.CreateBrush(region.Color, OPACITY_UNSELECTED);
                symbol.BorderBrush = Util.CreateBrush(region.Color, 1);
                symbol.BorderThickness = THICKNESS_UNSELECTED;

                // Create a graphic to represent the new region
                var graphic = new ESRI.ArcGIS.Client.Graphic();
                graphic.Geometry = region.Geometry;
                graphic.Symbol = symbol;
                graphic.Attributes["DataContext"] = region;

                // Add graphic to layer
                var layer = _map.Layers["_lyrRegions"] as ESRI.ArcGIS.Client.GraphicsLayer;
                layer.Graphics.Add(graphic);

                // Select the new region
                if (_model.SelectedRegion == null)
                {
                    _model.SelectedRegion = region;
                }
            }
        }

        // Add stores to the map as they are created
        void Stores_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
            {
                // Create a graphic to represent the new store
                var store = e.NewItems[0] as StoreData;
                var graphic = new ESRI.ArcGIS.Client.Graphic();
                graphic.Geometry = store.Geometry;
                graphic.Attributes["DataContext"] = store;

                // Add graphic to layer
                var layer = _map.Layers["_lyrStores"] as ESRI.ArcGIS.Client.GraphicsLayer;
                layer.Graphics.Add(graphic);
            }
        }

        // Update map selection
        void UpdateMap()
        {
            var region = _model.SelectedRegion;
            if (region != _lastSelectedRegion)
            {
                // Make de-selected region transparent
                if (_lastSelectedRegion != null)
                {
                    var graphic = GetGraphic(_lastSelectedRegion);
                    var symbol = graphic.Symbol as ESRI.ArcGIS.Client.Symbols.SimpleFillSymbol;
                    symbol.Fill = Util.CreateBrush(_lastSelectedRegion.Color, OPACITY_UNSELECTED);
                    symbol.BorderThickness = THICKNESS_UNSELECTED;
                }

                // Make selected region opaque and center it on the map
                if (region != null)
                {
                    var graphic = GetGraphic(region);
                    var symbol = graphic.Symbol as ESRI.ArcGIS.Client.Symbols.SimpleFillSymbol;
                    symbol.Fill = Util.CreateBrush(region.Color, OPACITY_SELECTED);
                    symbol.BorderThickness = THICKNESS_SELECTED;
                    _map.Extent = region.Geometry.Extent;
                }

                // Remember selection
                _lastSelectedRegion = region;
            }
        }

        // Gets the graphic that represents a given region on the map
        ESRI.ArcGIS.Client.Graphic GetGraphic(RegionData region)
        {
            var layer = _map.Layers["_lyrRegions"] as ESRI.ArcGIS.Client.GraphicsLayer;
            foreach (var graphic in layer.Graphics)
            {
                if (graphic.Geometry == region.Geometry)
                    return graphic;
            }
            return null;
        }

        // Map hit testing (select regions and stores when the user clicks them)
        object _mdObj;
        Point _mdPoint;
        void _map_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            _mdPoint = e.GetPosition(_map);
            _mdObj = Util.HitTestMap(_map, e);
        }
        void _map_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            // Check it wasn't a drag
            var pt = e.GetPosition(_map);
            if (Math.Abs(pt.X - _mdPoint.X) + Math.Abs(pt.Y - _mdPoint.Y) <= 3)
            {
                // Check we're still over the same object
                if (_mdObj != null && object.Equals(_mdObj, Util.HitTestMap(_map, e)))
                {
                    var store = _mdObj as StoreData;
                    if (store != null)
                    {
                        // User selected a store
                        _model.SelectedStore = store;
                    }
                    else
                    {
                        var region = _mdObj as RegionData;
                        if (region != null)
                        {
                            // User selected a region
                            _model.SelectedRegion = region;
                        }
                    }
                }
            }
        }

        // Center the map on the current selection
        void CenterSelection()
        {
            // Center and zoom region
            var region = _model.SelectedRegion;
            if (region != null)
            {
                _map.Extent = region.Geometry.Extent;
            }

            // Center store
            var store = _model.SelectedStore;
            if (store != null)
            {
                _map.PanTo(store.Geometry);
                return;
            }
        }

        #endregion

        //------------------------------------------------------------------
        #region ** map toolbar handling

       // void _mapToolbar_Click(object sender, RoutedEventArgs e)
      //  {
         //   if (sender == _btnLabels)
          //  {
            //    var l = _map.Layers["_mapLabels"];
              //  l.Visible = !l.Visible;
          //  }
          //  if (sender == _btnRoads)
          //  {
            //    var l = _map.Layers["_mapRoads"];
              //  l.Visible = !l.Visible;

          //  }
         //   else if (sender == _btnDashboard)
          //  {
            //    MessageBox.Show("todo...");
         //   }
       // }

        #endregion

        //------------------------------------------------------------------
        #region ** chart handling

        ChartType _chartType = ChartType.Bar;
        string _chartProperty = "Sales";    // Property being plotted on the chart
        string _chartFormat = "c0";         // Format used in chart axis and tooltips

        void UpdateChart()
        {
            var store = _model.SelectedStore;
            if (store != null)
            {
                _chartTitle.Text = string.Format("{0} for {1} stores", _chartProperty, store.Region.Name);
                UpdateChart(store.Region.Stores);
            }
            else
            {
                _chartTitle.Text = string.Format("{0} for all regions", _chartProperty);
                UpdateChart(_model.Regions);
            }
        }
        void UpdateChart(IEnumerable items)
        {
            // Check item type
            var firstItem = (from object i in items select i).FirstOrDefault();
            if (firstItem == null)
            {
                return;
            }

            // Start
            _chart.BeginUpdate();
            _chart.Data.Children.Clear();
            _chart.View.Axes.Clear();
            _chart.ChartType = _chartType;

            // Configure Y axis
            var isBar = _chart.ChartType == ChartType.Bar;
            var ay = isBar  ? _chart.View.AxisX  : _chart.View.AxisY;
            ay.ItemsSource = null;
            ay.MinorTickStroke = null;
            ay.MajorGridStroke = Util.CreateBrush(Colors.White, 0.3);
            ay.AnnoFormat = _chartFormat;
            ay.AnnoAngle = 0;
            ay.Reversed = false;
            ay.Foreground = Util.CreateBrush(Colors.LightGray, 1);

            // Configure X axis
            var ax = isBar ? _chart.View.AxisY : _chart.View.AxisX;
            ax.ItemsSource = items;
            ax.ItemsValueBinding = new Binding("Name");
            ax.MinorTickStroke = null;
            ax.MajorGridStroke = null;
            ax.AnnoFormat = null;
            ax.AnnoAngle = isBar ? 0 : -45;
            ax.Reversed = isBar;
            ax.Foreground = ax.Foreground;

            // Add series
            var valueBinding = new Binding(_chartProperty);
            var ds = CreateDataSeries(items, valueBinding);
            _chart.Data.Children.Add(ds);

            // Done
            _chart.EndUpdate();
        }
        DataSeries CreateDataSeries(IEnumerable items, Binding valueBinding)
        {
            // Create data series
            var ds = new DataSeries();

            // Make symbols invisible by default
            // (will set colors later in the PlotElementLoaded event handler)
            ds.SymbolFill = Util.CreateBrush(Colors.Transparent, 0);
            ds.SymbolStroke = ds.SymbolFill;
            ds.PlotElementLoaded += ds_PlotElementLoaded;

            // Bind series
            ds.ItemsSource = items;
            ds.ValueBinding = valueBinding;

            // Done
            return ds;
        }
        void ds_PlotElementLoaded(object sender, EventArgs e)
        {
            // Get region represented by this element
            var item = sender as PlotElement;
            var store = item.DataPoint.DataObject as StoreData;
            var region = store != null
                ? store.Region
                : item.DataPoint.DataObject as RegionData;

            // Set opacity based on selection status
            var opacity = 0.6;
            if ((store != null && store == _model.SelectedStore) ||
                (store == null && region == _model.SelectedRegion))
            {
                opacity = 1.0;
            }

            // Set element's fill/outline brushes
            item.Fill = Util.CreateBrush(region.Color, opacity);
            item.Stroke = Util.CreateBrush(region.Color, 1);

            // Set plot element tooltip
            var fmt = "{0}: {1:n0}";
            fmt = fmt.Replace("n0", _chartFormat);
            if (store != null)
            {
                var pi = typeof(StoreData).GetProperty(_chartProperty);
                var tip = string.Format(fmt, store.Name, pi.GetValue(store, null));
                ToolTipService.SetToolTip(item, tip);
            }
            else
            {
                var pi = typeof(RegionData).GetProperty(_chartProperty);
                var tip = string.Format(fmt, region.Name, pi.GetValue(region, null));
                ToolTipService.SetToolTip(item, tip);
            }

            // Update model selection when the user clicks an item on the chart
            item.MouseLeftButtonDown += (ss, ee) =>
                {
                    if (store != null)
                    {
                        // Select store
                        _model.SelectedStore = store;
                    }
                    else
                    {
                        // Select region
                        _model.SelectedRegion = region;
                    }

                    // Center map on selection
                    CenterSelection();
                };
        }

        #endregion

        //------------------------------------------------------------------
        #region ** chart toolbar handling

        // Show/hide toolbar when the mouse hovers
        double _tbOpacityHide = 0;
        double _tbOpacityShow = 0.8;
        void _toolbar_MouseEnter(object sender, MouseEventArgs e)
        {
            var toolbar = sender as FrameworkElement;
            if (_tbOpacityHide == 0)
            {
                _tbOpacityHide = toolbar.Opacity;
            }
            toolbar.Opacity = _tbOpacityShow;
        }
        void _toolbar_MouseLeave(object sender, MouseEventArgs e)
        {
            var toolbar = sender as FrameworkElement;
            toolbar.Opacity = _tbOpacityHide;
        }
        void _chartToolbar_Click(object sender, RoutedEventArgs e)
        {
            if (sender == _btnEmployees)
            {
                _chartProperty = "Employees";
                _chartFormat = "n0";
            }
            else if (sender == _btnPenetration)
            {
                _chartProperty = "Penetration";
                _chartFormat = "##0'%'";
            }
            else if (sender == _btnSales)
            {
                _chartProperty = "Sales";
                _chartFormat = "c0";
            }
            else if (sender == _btnBar)
            {
                _chartType = ChartType.Bar;
            }
            else if (sender == _btnColumn)
            {
                _chartType = ChartType.Column;
            }
            else if (sender == _btnPie)
            {
                _chartType = ChartType.Pie;
            }
            UpdateChart();
        }

        #endregion
    }
}
