using System;
using System.Text;
using System.Linq;
using System.Globalization;
using System.ComponentModel;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Media;
using System.Windows.Data;

namespace SalesDashboard
{
    public class Model
    {
        // List of stores and regions
        IList<RegionData> _regions;
        IList<StoreData> _stores;
        ICollectionView _storeView, _regionView;
        RegionData _selRegion;
        StoreData _selStore;

        // Use WebMercator spatial reference for all service queries 
        // (to match projection used by the map control)
        static ESRI.ArcGIS.Client.Geometry.SpatialReference _spatialReference = new ESRI.ArcGIS.Client.Geometry.SpatialReference(102100);

        // Data service URLs
        const string URL_STORES  = @"http://services.arcgis.com/RkjCp6A0cLN4ubJm/ArcGIS/rest/services/Stores_ACME/FeatureServer/0";
        const string URL_REGIONS = @"http://services.arcgis.com/RkjCp6A0cLN4ubJm/ArcGIS/rest/services/TerritorySC/FeatureServer/0";

        // Color palette for regions (will wrap if needed, add more if there are many regions)
        string[] _rgnPalette = new string[] 
        {
            "#45b7f6", "#806594", "#15703c", "#d1d069", "#0e3f7c", "#0d767c", "#b93448", "#f78445",
           
        };

        /// <summary>
        /// Initializes a new instance of our data model.
        /// </summary>
        public Model()
        {
            // Create observable collections of regions and stores
            _regions = new ObservableCollection<RegionData>();
            _stores = new ObservableCollection<StoreData>();

            // Create views on the collections (to support grouping, sorting, currency, etc)
            _storeView = new PagedCollectionView(_stores);
            _regionView = new PagedCollectionView(_regions);

            // Go get the stores and regions
            GetStoreData();
        }

        // Fill collection with store data
        void GetStoreData()
        {
            // Define query to retrieve stores
            var query = new ESRI.ArcGIS.Client.Tasks.Query();
            query.Where = "OBJECTID > 0";
            query.ReturnGeometry = true;
            query.OutSpatialReference = _spatialReference;
            foreach (var f in "StoreName,StoreRevenue,NumberEmployees,StoreRegion,MkPent".Split(','))
            {
                query.OutFields.Add(f);
            }

            // Execute stores query
            var queryTask = new ESRI.ArcGIS.Client.Tasks.QueryTask();
            queryTask.Url = URL_STORES;
            queryTask.ExecuteCompleted += storeQueryTask_ExecuteCompleted;
            queryTask.Failed += storeQueryTask_Failed;
            queryTask.ExecuteAsync(query);
        }
        void storeQueryTask_ExecuteCompleted(object sender, ESRI.ArcGIS.Client.Tasks.QueryEventArgs e)
        {
            // Got data, parse and populate store list
            if (e.FeatureSet != null && e.FeatureSet.Features != null)
            {
                int id = 0;
                foreach (var item in e.FeatureSet.Features)
                {
                    var store = new StoreData();
                    store.ID = id++;
                    var atts = item.Attributes;
                    store.Name = ((string)atts["StoreName"]).Trim();
                    store.Sales = GetDouble(atts, "StoreRevenue");
                    store.Employees = (int)GetDouble(atts, "NumberEmployees");
                    store.Penetration = GetDouble(atts, "MkPent");
                    store.RegionName = ((string)atts["StoreRegion"]).Trim();
                    store.Geometry = item.Geometry;
                    _stores.Add(store);
                }
            }

            // Got stores, now go get regions
            GetRegionData();
        }
        void storeQueryTask_Failed(object sender, ESRI.ArcGIS.Client.Tasks.TaskFailedEventArgs e)
        {
            MessageBox.Show(e.Error.Message, "Error Getting Store Data", MessageBoxButton.OK);
        }

        // Fill collection with region data
        void GetRegionData()
        {
            // Define query to retrieve regions
            var query = new ESRI.ArcGIS.Client.Tasks.Query();
            query.Where = "OBJECTID > 0";
            query.ReturnGeometry = true;
            query.OutSpatialReference = _spatialReference;
            foreach (var f in "RegionID,RegionName".Split(','))
            {
                query.OutFields.Add(f);
            }

            // Create/invoke task
            var queryTask = new ESRI.ArcGIS.Client.Tasks.QueryTask();
            queryTask.Url = URL_REGIONS;
            queryTask.ExecuteCompleted += regionQueryTask_ExecuteCompleted;
            queryTask.Failed += queryTask_Failed;
            queryTask.ExecuteAsync(query);
        }
        void regionQueryTask_ExecuteCompleted(object sender, ESRI.ArcGIS.Client.Tasks.QueryEventArgs e)
        {
            // Got data, parse and populate region list
            if (e.FeatureSet != null && e.FeatureSet.Features != null)
            {
                int id = 0;
                foreach (var item in e.FeatureSet.Features)
                {
                    // Create new region
                    var rgn = new RegionData();
                    var atts = item.Attributes;
                    rgn.ID = (int)atts["RegionID"];
                    rgn.Name = ((string)atts["RegionName"]).Trim();
                    rgn.Abbreviation = GetAbbreviation(rgn.Name);
                    rgn.Color = GetColor(_rgnPalette[id++ % _rgnPalette.Length]);

                    // NOTE: The service we are using returns region geometries in the 
                    // WebMercator projection, which is the projection commonly used with 
                    // map controls. Because of this, we can add these geometries to the
                    // map without any additional conversions.
                    rgn.Geometry = item.Geometry;

                    // Build list of stores that belong to this region
                    foreach (var store in _stores)
                    {
                        if (store.RegionName == rgn.Name)
                        {
                            rgn.Stores.Add(store);
                            store.Region = rgn;
                        }
                    }

                    // Add region to collection
                    _regions.Add(rgn);
                }
            }

            // All done, make sure all stores belong to regions
            foreach (var store in _stores)
            {
                System.Diagnostics.Debug.Assert(
                    store.Region != null, 
                    "All stores should be assigned to regions!");
            }
        }
        void queryTask_Failed(object sender, ESRI.ArcGIS.Client.Tasks.TaskFailedEventArgs e)
        {
            MessageBox.Show(e.Error.Message, "Error Getting Region Data", MessageBoxButton.OK);
        }

        /// <summary>
        /// Gets a reference to the Regions list.
        /// </summary>
        public ICollectionView Regions
        {
            get { return _regionView; }
        }
        /// <summary>
        /// Gets a reference to the Stores list.
        /// </summary>
        public ICollectionView Stores
        {
            get { return _storeView; }
        }
        /// <summary>
        /// Gets or sets the currently selected <see cref="RegionData"/>.
        /// </summary>
        public RegionData SelectedRegion
        {
            get { return _selRegion; }
            set
            {
                if (value != SelectedRegion)
                {
                    OnSelectionChanging();
                    _selRegion = value;
                    _regionView.MoveCurrentTo(value);
                    if (_selStore != null && _selStore.Region != value)
                    {
                        _selStore = null;
                        _storeView.MoveCurrentTo(null);
                    }
                    OnSelectionChanged();
                }
            }
        }
        /// <summary>
        /// Gets or sets the currently selected <see cref="StoreData"/>.
        /// </summary>
        public StoreData SelectedStore
        {
            get { return _selStore; }
            set
            {
                if (value != SelectedStore)
                {
                    OnSelectionChanging();
                    _selStore = value;
                    _storeView.MoveCurrentTo(value);
                    if (value != null)
                    {
                        _selRegion = value.Region;
                        _regionView.MoveCurrentTo(value);
                    }
                    OnSelectionChanged();
                }
            }
        }
        /// <summary>
        /// Fires before the selection changes.
        /// </summary>
        public event EventHandler SelectionChanging;
        /// <summary>
        /// Raises the <see cref="SelectionChanging"/> event.
        /// </summary>
        protected virtual void OnSelectionChanging()
        {
            if (SelectionChanging != null)
                SelectionChanging(this, EventArgs.Empty);
        }
        /// <summary>
        /// Fires after the selection changes.
        /// </summary>
        public event EventHandler SelectionChanged;
        /// <summary>
        /// Raises the <see cref="SelectionChanged"/> event.
        /// </summary>
        protected virtual void OnSelectionChanged()
        {
            if (SelectionChanged != null)
                SelectionChanged(this, EventArgs.Empty);
        }
        /// <summary>
        /// Gets a <see cref="RegionData"/> based on its name.
        /// </summary>
        public RegionData GetRegion(string name)
        {
            foreach (var rgn in _regions)
            {
                if (rgn.Name == name)
                    return rgn;
            }
            return null;
        }

        #region ** utilities to create data source

        static string GetAbbreviation(string name)
        {
            var sb = new StringBuilder();
            foreach (var c in name)
            {
                if (char.IsUpper(c))
                {
                    sb.Append(c);
                }
            }
            return sb.ToString();
        }
        static Color GetColor(string color)
        {
            byte a = 0xff;
            byte r = byte.Parse(color.Substring(1, 2), NumberStyles.HexNumber);
            byte g = byte.Parse(color.Substring(3, 2), NumberStyles.HexNumber);
            byte b = byte.Parse(color.Substring(5, 2), NumberStyles.HexNumber);
            return Color.FromArgb(a, r, g, b);
        }
        static double GetDouble(IDictionary<string, object> atts, string key)
        {
            var value = atts[key];
            try
            {
                return (double)Convert.ChangeType(value, typeof(double), CultureInfo.InvariantCulture);
            }
            catch
            {
                return double.NaN;
            }
        }

        #endregion
    }
    /// <summary>
    /// Class that represents a Store.
    /// </summary>
    public class StoreData
    {
        public int ID { get; set; }
        public string Name { get; set; }
        public int Employees { get; set; }
        public double Sales { get; set; }
        public double Penetration { get; set; }
        public RegionData Region { get; set; }
        public string RegionName { get; set; }
        public ESRI.ArcGIS.Client.Geometry.Geometry Geometry { get; set; }
        public string DisplayName { get { return Name; } }
        public string FullName
        {
            get { return string.Format("{0}, {1}", Name, Region.Abbreviation); }
        }
        public override string ToString() { return DisplayName; }
    }
    /// <summary>
    /// Class that represents a Region.
    /// </summary>
    public class RegionData
    {
        List<StoreData> _stores = new List<StoreData>();
        public int ID { get; set; }
        public string Name { get; set; }
        public string Abbreviation { get; set; }
        public Color Color { get; set; }
        public string DisplayName
        { 
            get { return string.Format("{0} ({1})", Name, Abbreviation); } 
        }
        public override string ToString() { return DisplayName; }
        public List<StoreData> Stores { get { return _stores; } }
        public int Employees 
        {
            get
            {
                return (from store in Stores select store.Employees).Sum();
            }
        }
        public double Sales 
        { 
            get
            {
                return (from store in Stores select store.Sales).Sum();
            }
        }
        public double Penetration 
        {
            get
            {
                return (from store in Stores select store.Penetration * store.Sales).Sum() / Sales;
            }
        }
        public ESRI.ArcGIS.Client.Geometry.Geometry Geometry { get; set; }
    }
}
