using System;
using System.IO;
using System.Windows;
using System.Reflection;
using System.ComponentModel;
using System.Collections.Generic;
using ESRI.ArcGIS.Client;
using ESRI.ArcGIS.Client.Tasks;
using ESRI.ArcGIS.Client.Geometry;

namespace GeoDashboard
{
    public enum InfoScale
    {
        State = 4,
        County = 3,
        Tract = 2
    }
    public class ViewModel : BaseObject
    {
        // ** fields

        Dictionary<string, InfoSource> _sources;
        Envelope _extent;
        InfoScale _infoScale;
        string _selectedLocation;

        static List<string> _tapestryDescriptions;

        // ** ctor

        /// <summary>
        /// Initializes a new instance of a <see cref="Model"/>.
        /// </summary>
        public ViewModel()
        {
            // load info sources using metadata in resource file
            _sources = LoadInfoSources();

            // add summary histogram information (ShortList property)
            var source = Sources["USA_Median_Age"];
            var list = new List<InfoValue>();
            list.Add(new InfoValue(source, "POPU5_CY POP5_CY POP10_CY POP15_CY POP20_CY POP25_CY", "under 30", 0));
            list.Add(new InfoValue(source, "POP30_CY POP35_CY POP40_CY POP45_CY POP50_CY POP55_CY", "30 to 59", 0));
            list.Add(new InfoValue(source, "POP60_CY POP65_CY POP70_CY POP75_CY POP80_CY POP85_CY", "60 and over", 0));
            source.ShortList = list;
	
            source = Sources["USA_Median_Household_Income"];
            list = new List<InfoValue>();
            list.Add(new InfoValue(source, "HINC0_CY HINC10_CY HINC15_CY HINC20_CY HINC25_CY HINC30_CY HINC35_CY", "under 40k", 0));
            list.Add(new InfoValue(source, "HINC40_CY HINC45_CY HINC50_CY HINC60_CY HINC75_CY", "40k to 100k", 0));
            list.Add(new InfoValue(source, "HINC100_CY HINC125_CY HINC150_CY HINC200_CY HINC250_CY HINC500_CY", "100k and above", 0));
            source.ShortList = list;
	
            source = Sources["USA_Median_Net_Worth"];
            list = new List<InfoValue>();
            list.Add(new InfoValue(source, "NW0_CY NW15_CY NW35_CY", "under 50k", 0));
            list.Add(new InfoValue(source, "NW50_CY NW75_CY NW100_CY", "50k to 150k", 0));
            list.Add(new InfoValue(source, "NW150_CY NW250_CY NW500_CY NW1M_CY", "150k and above", 0));
            source.ShortList = list;

            source = Sources["USA_Median_Home_Value"];
            list = new List<InfoValue>();
            list.Add(new InfoValue(source, "VALU10K_CY VAL10K_CY VAL15K_CY VAL20K_CY VAL25K_CY VAL30K_CY VAL35K_CY VAL40K_CY VAL50K_CY VAL60K_CY VAL70K_CY VAL80K_CY VAL90K_CY VAL100K_CY VAL125K_CY", "under 150k", 0));
            list.Add(new InfoValue(source, "VAL150K_CY VAL175K_CY VAL200K_CY VAL250K_CY VAL300K_CY VAL400K_CY", "150k to 500k", 0));
            list.Add(new InfoValue(source, "VAL500K_CY VAL750K_CY VAL1M_CY", "500k and above", 0));
            source.ShortList = list;
        }

        // ** object model

        /// <summary>
        /// Gets a dictionary with all the <see cref="InfoSource"/> objects available.
        /// </summary>
        public Dictionary<string, InfoSource> Sources
        {
            get { return _sources; }
        }
        /// <summary>
        /// Gets or sets the area for which we are providing information.
        /// </summary>
        public Envelope Extent
        {
            get { return _extent; }
            set
            {
                if (value != _extent)
                {
                    _extent = value;
                    OnPropertyChanged("Extent");
                    UpdateValues();
                }
            }
        }
        /// <summary>
        /// Gets the scale for which we are providing information (function of Extent).
        /// </summary>
        public InfoScale InfoScale
        {
            get { return _infoScale; }
            private set
            {
                if (value != _infoScale)
                {
                    _infoScale = value;
                    OnPropertyChanged("InfoScale");
                }
            }
        }
        /// <summary>
        /// Gets the name of the last location queried.
        /// </summary>
        public string SelectedLocation
        {
            get { return _selectedLocation; }
            set
            {
                if (value != _selectedLocation)
                {
                    _selectedLocation = value;
                    OnPropertyChanged("SelectedLocation");
                }
            }
        }
        /// <summary>
        /// Gets a list with tapestry descriptions.
        /// </summary>
        public static List<string> TapestryDescriptons
        {
            get
            {
                if (_tapestryDescriptions == null)
                {
                    _tapestryDescriptions = LoadTapestryDescriptions();
                }
                return _tapestryDescriptions;
            }
        }

        // ** implementation

        // refresh the detail information for the currently selected 
        void UpdateValues()
        {
            // can't query without an extent
            if (_extent != null)
            {
                // update info scale
                InfoScale = _extent.Width < 2e5 ? InfoScale.Tract :
                            _extent.Width < 6e6 ? InfoScale.County :
                            InfoScale.State;

                // perform queries for each infoType in dictionary
                foreach (var source in _sources.Values)
                {
                    if (source.HasListeners)
                    {
                        source.UpdateValues();
                    }
                }
            }
        }

        // load all info sources from our catalog
        Dictionary<string, InfoSource> LoadInfoSources()
        {
            var dct = new Dictionary<string, InfoSource>(StringComparer.CurrentCultureIgnoreCase);
            bool addToList = false;

            using (var stream = GetResourceStream("infoSourceCatalog.txt"))
            using (var sr = new StreamReader(stream))
            {
                InfoSource source = null;
                for (var line = sr.ReadLine(); line != null; line = sr.ReadLine())
                {
                    // blanks end group
                    if (line.Length == 0)
                    {
                        // finish pending groups
                        if (source != null)
                        {
                            dct[source.ShortUrl] = source;
                            source = null;
                        }
                        continue;
                    }

                    // skip comments
                    if (line[0] == ';')
                    {
                        continue;
                    }

                    // parse line
                    var parts = line.Trim().Split('\t');
                    if (parts.Length != 2)
                    {
                        throw new Exception("expecting key/value pair, found: " + line);
                    }
                    var key = parts[0].Trim();
                    var name = parts[1].Trim();

                    if (source == null)
                    {
                        // create new InfoSource
                        source = new InfoSource(this, key, name);
                    }
                    else
                    {
                        // start adding to list
                        if (!addToList && name.StartsWith("less than", StringComparison.OrdinalIgnoreCase))
                        {
                            addToList = true;
                        }

                        // add value to current info source
                        var value = new InfoValue(source, key, name, null);
                        source.Dictionary[key] = value;
                        if (addToList)
                        {
                            source.List.Add(value);
                        }

                        // stop adding to list
                        if (addToList && name.EndsWith("or more", StringComparison.OrdinalIgnoreCase))
                        {
                            addToList = false;
                        }
                    }
                }

                // finish last pending group
                if (source != null)
                {
                    dct[source.ShortUrl] = source;
                    source = null;
                }
            }

            // done, return list
            return dct;
        }

        // load tapestry descriptions from our catalog
        static List<string> LoadTapestryDescriptions()
        {
            var list = new List<string>();
            using (var stream = GetResourceStream("tapestryDescriptions.txt"))
            using (var sr = new StreamReader(stream))
            {
                for (var line = sr.ReadLine(); line != null; line = sr.ReadLine())
                {
                    if (line.StartsWith(";"))
                    {
                        continue;
                    }
                    var parts = line.Split('\t');
                    if (parts.Length != 2)
                    {
                        throw new Exception("bad tapestry description.");
                    }
                    list.Add(parts[1].Trim());
                }
            }
            return list;
        }

        // get a stream from an embedded resource
        static Stream GetResourceStream(string resName)
        {
            var asm = typeof(ViewModel).Assembly;
            foreach (var res in asm.GetManifestResourceNames())
            {
                if (res.EndsWith(resName))
                {
                    return asm.GetManifestResourceStream(res);
                }
            }
            throw new Exception("resource not found: " + resName);
        }
    }
}
