using ArcGIS.Core.Geometry;
using ArcGIS.Desktop.Core;
using ArcGIS.Desktop.Framework;
using ArcGIS.Desktop.Framework.Contracts;
using ArcGIS.Desktop.Framework.Controls;
using ArcGIS.Desktop.Framework.Dialogs;
using ArcGIS.Desktop.Framework.Threading.Tasks;
using ArcGIS.Desktop.Internal.KnowledgeGraph.DataModelling;
using ArcGIS.Desktop.Internal.Mapping.Symbology;
using ArcGIS.Desktop.Mapping;
using ArcGIS.Desktop.Mapping.Controls;
using ArcGIS.Desktop.Mapping.Events;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using System.Windows.Media.Media3D;

namespace DataExplorationWithSpatialClauses
{
    //Los Angeles county parcels
    public class ProWindowDefinitionQueryBuilderVM : PropertyChangedBase
    {
        private MapControlContent _mapControlContent = null;
        private bool _subscribed = false;
       
        //Constructor for the ProWindow View Model
        public ProWindowDefinitionQueryBuilderVM()
        {
            System.Diagnostics.Debug.WriteLine("ProWindowDefinitionQueryBuilderVM Constructor");
            if (MapView.Active != null)
            {
                //Configure the DefinitionQueryBuilderControlProperties for the LA County parcels layer
                // so that we can display/modify the definition queries in this layer.
                //This method is where the magic happens.
                BuildControlProperties();
            }
            //subscribe to events here if needed
            //Subscribe to events
            if (!_subscribed)
            {
                _subscribed = true;
                // connect to events
                MapMemberPropertiesChangedEvent.Subscribe(OnMapMemberPropertiesChanged);
            }
            
        }
        ~ProWindowDefinitionQueryBuilderVM()
        {
            if (_subscribed)
            {
                _subscribed = false;
                // unsubscribe from events
                MapMemberPropertiesChangedEvent.Unsubscribe(OnMapMemberPropertiesChanged);
            }
        }
        private async void OnMapMemberPropertiesChanged(MapMemberPropertiesChangedEventArgs args)
        {
            //get all the mapMembers that have changed
            List<MapMember> changedMapMembers = args.MapMembers.ToList();
            //Check if the LA County parcels layer changes
            var laCountyLyrChanged = changedMapMembers.Any(n => n.Name == "Los Angeles county parcels");
            //Store the LA County parcels layer in a variable to use later
            var laCountyParcelsLyr = MapView.Active?.Map.GetLayersAsFlattenedList().FirstOrDefault(f => f.Name == "Los Angeles county parcels") as FeatureLayer;
            var definitionQueries = laCountyParcelsLyr?.DefinitionQueries;
            var activeDefQuery = laCountyParcelsLyr?.ActiveDefinitionQuery;
            //Get the fire zone layer to use its geometry for zooming in later
            var fireZoneSpatialDefLyr = MapView.Active?.Map.GetLayersAsFlattenedList().FirstOrDefault(f => f.Name == "Los Angeles fires perimeter 2025") as FeatureLayer;
            //get the index of the laCountyLayer in the changedMapMembers list
            int index = changedMapMembers.FindIndex(n => n.Name == "Los Angeles county parcels");
            if (index != -1)
            {
                //get EventHints for this layer
                //EvenHints collection matches the MapMembers collection,
                //so we can use the same index to get the EventHints for the LA County parcels layer
                var eventHints = args.EventHints[index];
                //Check if DefinitionQueries have changed
                if (eventHints == MapMemberEventHint.DefinitionQuery)
                {
                    //Re-create the MapControlContent to reflect
                    //the changes in the definition queries of the LA County parcels layer
                    await QueuedTask.Run(() =>
                    {
                        IList<FeatureLayer> layersToShowInMapControl = new List<FeatureLayer>();
                        if (laCountyParcelsLyr != null)
                            layersToShowInMapControl.Add(laCountyParcelsLyr);
                        if (fireZoneSpatialDefLyr != null)
                            layersToShowInMapControl.Add(fireZoneSpatialDefLyr);
                        _mapControlContent = MapControlContentFactory.Create(layersToShowInMapControl, MapView.Active.Extent, ArcGIS.Core.CIM.MapViewingMode.Map);
                        NotifyPropertyChanged(() => MapContent);
                    });
                    //Zoom the map to the layer's extent
                    var mapSR = MapView.Active.Map.SpatialReference;
                    if (mapSR == null) return;
                    //Get the definition Query
                    if (laCountyParcelsLyr.ActiveDefinitionQuery == null)
                        return;
                    var defQueryName = laCountyParcelsLyr.ActiveDefinitionQuery.Name;
                    //Get the geometry of the fire by matching the definition query name
                    //to the fire name in the fire geometry dictionary
                    //Get Map Control's LA County parcels layer

                    if (Module1.FireGeometryDictionary.TryGetValue(defQueryName, out var fireGeometry))
                    {
                        //Get the fire geometry's extent and get the X and Y of the center of the extent to
                        //use as the new center for the map
                        var fireExtentCenter = fireGeometry.Extent.Center;
                        var projectedFireExtentCenter = GeometryEngine.Instance.Project(fireExtentCenter, mapSR) as MapPoint;
                        var camera = new ArcGIS.Desktop.Mapping.Camera(projectedFireExtentCenter.X, projectedFireExtentCenter.Y, 42277.249991199184, 0, MapView.Active.Map.SpatialReference);
                        camera.Scale *= 2.8;
                        CameraProperty = camera;
                    }
                }
            }
        }

        #region Binding properties
        /// <summary>
        /// Gets and sets the name of currently selected mapMember.
        /// </summary>
        private string _mapMemberName;
        public string MapMemberName
        {
            get { return _mapMemberName; }
            set { SetProperty(ref _mapMemberName, value); }
        }
        private MapMember _mapMember;
        public MapMember MapMember
        {
            get { return _mapMember; }
            set { SetProperty(ref _mapMember, value); }
        }
        public MapControlContent MapContent
        {
            get { return _mapControlContent; }
            set
            {
                SetProperty(ref _mapControlContent, value, () => MapContent);
            }
        }
        private ArcGIS.Desktop.Mapping.Camera _camera = null;
        public ArcGIS.Desktop.Mapping.Camera CameraProperty
        {
            get { return _camera; }
            set
            {
                SetProperty(ref _camera, value, () => CameraProperty);

            }
        }

        /// <summary>
        /// Gets and sets the QueryBuilderControlProperties to bind to the QueryBuilderControl.
        /// </summary>
        private DefinitionQueryBuilderControlProperties _myConfigureControlProperties = null;
        public DefinitionQueryBuilderControlProperties MyConfigureControlProperties
        {
            get { return _myConfigureControlProperties; }
            set
            {
                SetProperty(ref _myConfigureControlProperties, value);
            }
        }
        //Commands
        /// <summary>
        /// Gets the Apply command to write query definition to mapMember.
        /// </summary>
        private RelayCommand _applyCommand;
        public ICommand ApplyCommand
        {
            get
            {
                if (_applyCommand == null)
                {
                    _applyCommand = new RelayCommand(() => ApplyChanges());
                }
                return _applyCommand;
            }
        }
        public ICommand CmdCancel => new RelayCommand((proWindow) =>
        {
            // TODO: set dialog result and close the window
            (proWindow as ProWindow).DialogResult = false;
            (proWindow as ProWindow).Close();
            if (_subscribed)
            {
                _subscribed = false;
                // unsubscribe from events
                MapMemberPropertiesChangedEvent.Unsubscribe(OnMapMemberPropertiesChanged);
            }

        }, () => true);
        #endregion
        public async Task UpdateMapControlContent()
        {
            
            await QueuedTask.Run(() =>
            {
                var currentMap = MapView.Active.Map;
                //laCountyParcelsLyr
                var laCountyParcelLyr = currentMap.GetLayersAsFlattenedList().OfType<FeatureLayer>().Where(n => n.Name == "Los Angeles county parcels").FirstOrDefault();
                var firePerimeterLyr = currentMap.GetLayersAsFlattenedList().OfType<FeatureLayer>().Where(n => n.Name == "Los Angeles fires perimeter 2025").FirstOrDefault();
                var basemapMap = Project.Current.GetItems<MapProjectItem>().FirstOrDefault(m => m.Name == "Basemap");
                var basemap = basemapMap?.GetMap();
                var worldStreetMap = basemap?.GetLayersAsFlattenedList().OfType<Layer>().Where(n => n.Name == "World Street Map").FirstOrDefault();
                var hybridBasemapLyr = basemap?.GetLayersAsFlattenedList().OfType<Layer>().Where(n => n.Name == "Hybrid Reference Layer").FirstOrDefault();
                IList<Layer> layersToShowInMapControl = new List<Layer>();
                if (laCountyParcelLyr != null)
                    layersToShowInMapControl.Add(laCountyParcelLyr);
                if (firePerimeterLyr != null)
                    layersToShowInMapControl.Add(firePerimeterLyr);
                //if (hybridBasemapLyr != null)
                //    layersToShowInMapControl.Add(hybridBasemapLyr);
                if (worldStreetMap != null)
                    layersToShowInMapControl.Add(worldStreetMap);


                _mapControlContent = MapControlContentFactory.Create(layersToShowInMapControl, MapView.Active.Extent, currentMap.DefaultViewingMode);
                NotifyPropertyChanged(() => MapContent);
            });
        }
        /// <summary>
        /// Build a QueryBuilderControlProperties for the specified mapView.  Finds the first BasicFeatureLayer or StandAloneTable highlighted in the TOC.
        /// </summary>
        /// <param name="mapView">a mapView.</param>
        private void BuildControlProperties()
        {
            BasicFeatureLayer laCountyParcelLyr = null;
            var mapView = MapView.Active;
            if (mapView != null)
            {
                // only interested in FeatureLayers ... they are the ones with definition queries
                laCountyParcelLyr = mapView.Map.GetLayersAsFlattenedList().OfType<BasicFeatureLayer>().Where(n => n.Name == "Los Angeles county parcels").FirstOrDefault();
                if (laCountyParcelLyr == null)
                {
                    MessageBox.Show("No BasicFeatureLayer named 'Los Angeles county parcels' found in the map.", "DefinitionQueryDockPaneViewModel");
                    return;
                }
            }
            // build the control properties
            try
            {
                DefinitionQuery activeDefinitionQuery = null;

                List<DefinitionQuery> definitionQueries = null;
                if (laCountyParcelLyr != null)
                {
                    definitionQueries = laCountyParcelLyr.DefinitionQueries.ToList();
                    activeDefinitionQuery = laCountyParcelLyr.ActiveDefinitionQuery;
                }

                // Create a DefinitionQueryBuilderControlProperties object
                // Configures the properties to be used to initialize the DefinitionQueryBuilderControl.
                var props = new DefinitionQueryBuilderControlProperties()
                {
                    MapMember = laCountyParcelLyr,
                    DefinitionQueries = definitionQueries,
                    ActiveDefinitionQuery = activeDefinitionQuery
                };

                // set the binding properties
                // ConfigureControl = null;
                if (this.MyConfigureControlProperties != null && MyConfigureControlProperties.DefinitionQueries != null)
                    this.MyConfigureControlProperties.DefinitionQueries.Clear();
                this.MyConfigureControlProperties = props;
                MapMemberName = laCountyParcelLyr?.Name ?? "";
                MapMember = laCountyParcelLyr;
            }
            catch (Exception ex)
            {
                MessageBox.Show("Exception in BuildControlProperties: " + ex.Message, "DefinitionQueryDockPaneViewModel");
                //throw;
            }
        }

        /// <summary>
        /// Saves the current expression to the appropriate mapMember according to user response. 
        /// </summary>
        private async void ApplyChanges()
        {
            //access the usercontrol to get the properties
            var userControlProWindowDefinitionQueryBuilder = ProWindowDefinitionQueryBuilder.Current;
            //get the definition queries and active definition query from the control   
            var myControlsQueries = userControlProWindowDefinitionQueryBuilder.DefinitionQueryBuilderControl.DefinitionQueries;
            string  myControlsActiveQuery = null;
            //check if active definition query is null to avoid exception
            if (userControlProWindowDefinitionQueryBuilder.DefinitionQueryBuilderControl.ActiveDefinitionQuery != null)
            {
                myControlsActiveQuery = userControlProWindowDefinitionQueryBuilder.DefinitionQueryBuilderControl.ActiveDefinitionQuery.Name;
            }
            var fLayer = MyConfigureControlProperties.MapMember as FeatureLayer;
            await QueuedTask.Run(() =>
            {
                if (fLayer != null)
                {
                    //remove all existing definition queries and
                    //add the new ones from the control,
                    //then set the active definition query
                    fLayer.RemoveAllDefinitionQueries();
                    fLayer.InsertDefinitionQueries(myControlsQueries);
                    fLayer.SetActiveDefinitionQuery(myControlsActiveQuery);
                }
            });
        }
    }
}

