#region Disclaimer / License
// Copyright (C) 2011, Jackie Ng
// http://trac.osgeo.org/mapguide/wiki/maestro, jumpinjackie@gmail.com
// 
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
// 
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
// 
#endregion
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using OSGeo.MapGuide.MaestroAPI.Mapping;
using OSGeo.MapGuide.MaestroAPI;
using OSGeo.MapGuide.ObjectModels.MapDefinition;
using OSGeo.MapGuide.ObjectModels;
using OSGeo.MapGuide.MaestroAPI.Services;
using OSGeo.MapGuide.MaestroAPI.Resource;
using OSGeo.MapGuide.ObjectModels.LayerDefinition;
using OSGeo.MapGuide.MaestroAPI.Exceptions;
using OSGeo.MapGuide.ObjectModels.FeatureSource;
using Maestro.Shared.UI;
using ICSharpCode.Core;

namespace Maestro.Base.UI
{
    public partial class ProfilingDialog : Form
    {
        private IServerConnection m_connection;
        private IResource m_item;
        private string m_resourceId;

        private RuntimeMap m_tempmap;

        public ProfilingDialog(IResource item, string resourceId, IServerConnection connection)
            : this()
        {
            m_connection = connection;
            m_item = item;
            m_resourceId = resourceId;
        }

        private ProfilingDialog()
        {
            InitializeComponent();
            m_tempmap = null;
        }

        private void CancelBtn_Click(object sender, EventArgs e)
        {
            if (!backgroundWorker.IsBusy)
                this.Close();
            else if (!backgroundWorker.CancellationPending)
                backgroundWorker.CancelAsync();
        }

        private void ProfileFeatureSource(IFeatureSource fs)
        {
            //TODO: Determine what profiling benchmarks to use
            string resourceId = fs == m_item ? m_resourceId : fs.ResourceID;
            backgroundWorker.ReportProgress(0, (string.Format(Properties.Resources.Prof_LogMessageFeatureSource, resourceId)));
        }

        private static void SetTempLayer(IMapDefinition mdf, string resourceId)
        {
            var layers = new List<IMapLayer>(mdf.MapLayer);
            for (int i = 0; i < layers.Count; i++)
            {
                mdf.RemoveLayer(layers[i]);
            }

            var layer = mdf.AddLayer(null, "x", resourceId);
            layer.Visible = true;
            layer.Selectable = true;
            layer.Name = "x";
            layer.LegendLabel = "";
        }

        private void MakeTempMap()
        {
            if (m_tempmap == null)
            {
                IMapDefinition m = ObjectFactory.CreateMapDefinition(m_connection, "");
                m.CoordinateSystem = @"LOCAL_CS[""*XY-M*"", LOCAL_DATUM[""*X-Y*"", 10000], UNIT[""Meter"", 1], AXIS[""X"", EAST], AXIS[""Y"", NORTH]]";
                m.SetExtents(-1, -1, 1, 1);

                m.ResourceID = "Library://non-existing.MapDefinition";
                var mpsvc = (IMappingService)m_connection.GetService((int)ServiceType.Mapping);
                var rid = new ResourceIdentifier(Guid.NewGuid().ToString(), ResourceTypes.RuntimeMap, m_connection.SessionID);

                m_tempmap = mpsvc.CreateMap(m);
            }
        }


        private void ProfileLayerDefinition(ILayerDefinition ldef)
        {
            //TODO: This was a line-by-line port from 2.x to match the 3.x APIs
            //we should find time to clean this up and ensure the profiling numbers are
            //truly reflective of actual performance metrics
            if (backgroundWorker.CancellationPending)
                return;

            string resourceId = ldef == m_item ? m_resourceId : ldef.ResourceID;

            MakeTempMap();

            backgroundWorker.ReportProgress(0, (string.Format(Properties.Resources.Prof_LogMessageLayerDefinition, resourceId)));
            using (new Timer(Properties.Resources.Prof_LogMessageRuntimeLayer, backgroundWorker))
            {
                try
                {
                    IMapDefinition mdef = ObjectFactory.CreateMapDefinition(m_connection, "");
                    mdef.ResourceID = "Library://ProfileTest.MapDefinition"; //Flub it
                    IMapLayer layer = mdef.AddLayer(null, "Test Layer", ldef.ResourceID);
                    layer.Visible = false;
                    layer.Selectable = false;

                    if (backgroundWorker.CancellationPending)
                        return;

                    var mpsvc = (IMappingService)m_connection.GetService((int)ServiceType.Mapping);
                    
                    var map = mpsvc.CreateMap(mdef);
                    using (new Timer(Properties.Resources.Prof_LogMessageIdentifyFetching, backgroundWorker))
                    {
                        var rtl = map.GetLayerByName("Test Layer");
                        rtl.Visible = true;
                        rtl.Selectable = true;
                    }

                    map.Save();
                }
                catch (Exception ex)
                {
                    string msg = NestedExceptionMessageProcessor.GetFullMessage(ex);
                    backgroundWorker.ReportProgress(0, (string.Format(Properties.Resources.Prof_LayerDefinitionProfilingError, resourceId, msg)));
                }
            }

            if (backgroundWorker.CancellationPending)
                return;

            ILayerDefinition lx = (ILayerDefinition)ldef.Clone();
            if (lx.SubLayer.LayerType == LayerType.Vector || lx.SubLayer.LayerType == LayerType.Raster)
            {
                using (new Timer(Properties.Resources.Prof_LogMessageRenderingScales, backgroundWorker))
                {
                    if (lx.SubLayer.LayerType == LayerType.Vector)
                    {
                        IVectorLayerDefinition vlx = lx.SubLayer as IVectorLayerDefinition;
                        //VectorScaleRangeTypeCollection ranges = vlx.VectorScaleRange;
                        List<IVectorScaleRange> ranges = new List<IVectorScaleRange>(vlx.VectorScaleRange);
                        foreach (var vsr in ranges)
                        {
                            if (backgroundWorker.CancellationPending)
                                return;

                            string tmp1 = new ResourceIdentifier(Guid.NewGuid().ToString(), ResourceTypes.LayerDefinition, m_connection.SessionID);

                            try
                            {
                                double minscale = vsr.MinScale.HasValue ? vsr.MinScale.Value : 0;
                                double maxscale = vsr.MaxScale.HasValue ? vsr.MaxScale.Value : 10000000;

                                vlx.RemoveAllScaleRanges();
                                vsr.MinScale = null;
                                vsr.MaxScale = null;
                                vlx.AddVectorScaleRange(vsr);

                                m_connection.ResourceService.SaveResourceAs(lx, tmp1);

                                if (backgroundWorker.CancellationPending)
                                    return;

                                var lst = m_connection.FeatureService.GetSpatialContextInfo(vlx.ResourceId, false);

                                //Create a runtime map just containing this particular layer at this particular scale range
                                //We are profiling the stylization settings for this layer
                                var mdf = ObjectFactory.CreateMapDefinition(m_connection, "");
                                if (lst.SpatialContext != null && lst.SpatialContext.Count >= 1)
                                {
                                    mdf.CoordinateSystem = lst.SpatialContext[0].CoordinateSystemWkt;
                                    if (string.IsNullOrEmpty(m_tempmap.CoordinateSystem))
                                        mdf.CoordinateSystem = @"LOCAL_CS[""*XY-M*"", LOCAL_DATUM[""*X-Y*"", 10000], UNIT[""Meter"", 1], AXIS[""X"", EAST], AXIS[""Y"", NORTH]]";
                                    
                                    double llx = double.Parse(lst.SpatialContext[0].Extent.LowerLeftCoordinate.X, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture);
                                    double lly = double.Parse(lst.SpatialContext[0].Extent.LowerLeftCoordinate.Y, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture); ;
                                    double urx = double.Parse(lst.SpatialContext[0].Extent.UpperRightCoordinate.X, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture); ;
                                    double ury = double.Parse(lst.SpatialContext[0].Extent.UpperRightCoordinate.Y, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture); ;

                                    m_tempmap.DataExtent = ObjectFactory.CreateEnvelope(llx, lly, urx, ury);
                                }

                                SetTempLayer(mdf, tmp1);
                                
                                var mpsvc = (IMappingService)m_connection.GetService((int)ServiceType.Mapping);

                                mdf.ResourceID = "Library://ProfileTest.MapDefinition"; //Flub it
                                var rtmap = mpsvc.CreateMap(mdf);

                                if (m_connection.ResourceService.ResourceExists(rtmap.ResourceID))
                                    m_connection.ResourceService.DeleteResource(rtmap.ResourceID);

                                rtmap.Save();

                                if (backgroundWorker.CancellationPending)
                                    return;

                                using (new Timer(string.Format(Properties.Resources.Prof_LogMessageScaleRange, minscale, maxscale), backgroundWorker))
                                {
                                    //TODO: Use extents rather than scale
                                    //using (System.IO.Stream s = m_connection.RenderRuntimeMap(tmp2, m.Extents, 1024, 800, 96))
                                    using (System.IO.Stream s = mpsvc.RenderRuntimeMap(rtmap.ResourceID, ((rtmap.DataExtent.MaxX - rtmap.DataExtent.MinX) / 2) + rtmap.DataExtent.MinX, ((rtmap.DataExtent.MaxY - rtmap.DataExtent.MinY) / 2) + rtmap.DataExtent.MinY, 50000, 1024, 800, 96))
                                    {
                                        backgroundWorker.ReportProgress(0, (string.Format(Properties.Resources.Prof_MapRenderingImageSize, s.Length)));
                                    }
                                }
                            }
                            finally
                            {
                                try { m_connection.ResourceService.DeleteResource(tmp1); }
                                catch { }
                            }
                        }

                    }
                }
            }

            if (backgroundWorker.CancellationPending)
                return;

            backgroundWorker.ReportProgress(0, ("\r\n"));
        }

        private void ProfileMapDefinition(IMapDefinition mapDef)
        {
            //TODO: This was a line-by-line port from 2.x to match the 3.x APIs
            //we should find time to clean this up and ensure the profiling numbers are
            //truly reflective of actual performance metrics
            var mdef = (IMapDefinition)mapDef.Clone();
            if (backgroundWorker.CancellationPending)
                return;

            string resourceId = mdef == m_item ? m_resourceId : mdef.ResourceID;

            backgroundWorker.ReportProgress(0, (string.Format(Properties.Resources.Prof_LogMessageMapDefinition, resourceId)));

            using (new Timer(Properties.Resources.Prof_LogMessageRuntimeMap, backgroundWorker))
            {
                foreach (var ml in mdef.MapLayer)
                {
                    try
                    {
                        if (backgroundWorker.CancellationPending)
                            return;

                        ILayerDefinition ldef = (ILayerDefinition)mdef.CurrentConnection.ResourceService.GetResource(ml.ResourceId);
                        ProfileLayerDefinition(ldef);
                    }
                    catch (Exception ex)
                    {
                        string msg = NestedExceptionMessageProcessor.GetFullMessage(ex);
                        backgroundWorker.ReportProgress(0, (string.Format(Properties.Resources.Prof_LayerDefinitionProfilingError, ml.ResourceId, msg)));
                    }
                }

                if (mdef.BaseMap != null)
                {
                    foreach (var g in mdef.BaseMap.BaseMapLayerGroup)
                    {
                        if (g.BaseMapLayer != null)
                        {
                            foreach (var ml in g.BaseMapLayer)
                            {
                                try
                                {
                                    if (backgroundWorker.CancellationPending)
                                        return;

                                    ILayerDefinition ldef = (ILayerDefinition)mdef.CurrentConnection.ResourceService.GetResource(ml.ResourceId);
                                    ProfileLayerDefinition(ldef);
                                }
                                catch (Exception ex)
                                {
                                    string msg = NestedExceptionMessageProcessor.GetFullMessage(ex);
                                    backgroundWorker.ReportProgress(0, (string.Format(Properties.Resources.Prof_LayerDefinitionProfilingError, ml.ResourceId, msg)));
                                }
                            }
                        }
                    }
                }
            }

            if (backgroundWorker.CancellationPending)
                return;

            var mpsvc = (IMappingService)m_connection.GetService((int)ServiceType.Mapping);

            try
            {
                if (backgroundWorker.CancellationPending)
                    return;

                //m_connection.ResetFeatureSourceSchemaCache();
                using (new Timer(Properties.Resources.Prof_LogMessageRuntimeMapTotal, backgroundWorker))
                    mpsvc.CreateMap(mdef);
            }
            catch (Exception ex)
            {
                string msg = NestedExceptionMessageProcessor.GetFullMessage(ex);
                backgroundWorker.ReportProgress(0, (string.Format(Properties.Resources.Prof_RuntimeMapProfilingError, resourceId, msg)));
            }

            try
            {
                if (backgroundWorker.CancellationPending)
                    return;

                //Flub for runtime map creation
                mdef.ResourceID = "Library://ProfilingTest.MapDefinition";

                var rtmap = mpsvc.CreateMap(mdef);

                if (m_connection.ResourceService.ResourceExists(rtmap.ResourceID))
                    m_connection.ResourceService.DeleteResource(rtmap.ResourceID);

                rtmap.Save();

                using (new Timer(Properties.Resources.Prof_LogMessageRenderingMap, backgroundWorker))
                {
                    //TODO: Use extents rather than scale
                    //using (System.IO.Stream s = m_connection.RenderRuntimeMap(tmp2, mdef.Extents, 1024, 800, 96))
                    using (System.IO.Stream s = mpsvc.RenderRuntimeMap(rtmap.ResourceID, ((mdef.Extents.MaxX - mdef.Extents.MinX) / 2) + mdef.Extents.MinX, ((mdef.Extents.MaxY - mdef.Extents.MinY) / 2) + mdef.Extents.MinY, 50000, 1024, 800, 96))
                    {
                        //Just dispose it after being read
                        backgroundWorker.ReportProgress(0, (string.Format(Properties.Resources.Prof_MapRenderingImageSize, s.Length)));
                    }
                }
            }
            catch (Exception ex)
            {
                string msg = NestedExceptionMessageProcessor.GetFullMessage(ex);
                backgroundWorker.ReportProgress(0, (string.Format(Properties.Resources.Prof_MapRenderingError, resourceId, msg)));
            }
        }

        private class Timer : IDisposable 
        {
            private string m_text;
            private bool m_isDisposed;
            private DateTime m_begin;
            private BackgroundWorker m_worker;

            public Timer(string text, BackgroundWorker worker)
            {
                m_begin = DateTime.Now;
                m_isDisposed = false;
                m_text = text;
                m_worker = worker;
            }

            #region IDisposable Members

            public void Dispose()
            {
                if (!m_isDisposed)
                {
                    TimeSpan ts = DateTime.Now - m_begin;
                    m_isDisposed = true;
                    m_worker.ReportProgress(0, (m_text + ts.TotalMilliseconds)); //ts.Minutes.ToString("00") + ":" + ts.Seconds.ToString("00") + "." + ts.Milliseconds.ToString("000")));
                    m_worker = null;
                    m_text = null;
                }
            }

            #endregion
        }

        private void Profiling_Load(object sender, EventArgs e)
        {
            backgroundWorker.RunWorkerAsync();
        }

        private void Profiling_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (backgroundWorker.IsBusy)
            {
                e.Cancel = true;

                if (!backgroundWorker.CancellationPending)
                    backgroundWorker.CancelAsync();
            }
        }

        private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            if (m_item.ResourceType == ResourceTypes.FeatureSource)
            {
                ProfileFeatureSource(m_item as IFeatureSource);
            }
            else if (m_item.ResourceType == ResourceTypes.LayerDefinition)
            {
                ProfileLayerDefinition(m_item as ILayerDefinition);
            }
            else if (m_item.ResourceType == ResourceTypes.MapDefinition)
            {
                ProfileMapDefinition(m_item as IMapDefinition);
            }
            else
            {
                backgroundWorker.ReportProgress(0, Properties.Resources.Prof_LogMessageUnsupportedResourceType);
            }
        }

        private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            CancelBtn.Text = Properties.Resources.CloseButtonText;
            btnSave.Enabled = true;
            WriteString(Properties.Resources.Prof_LogMessageDone);
        }

        private void WriteString(string message)
        {
            if (string.IsNullOrEmpty(message))
                return;

            bool scroll = Math.Abs((Results.SelectionStart + Results.SelectionLength) - Results.Text.Length) < 20;

            Results.Text += message + "\r\n";
            if (scroll)
            {
                Results.SelectionLength = 0;
                Results.SelectionStart = Results.Text.Length;
                Results.ScrollToCaret();
            }
        }

        private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            WriteString(e.UserState as string);
        }

        private void btnSave_Click(object sender, EventArgs e)
        {
            using (var diag = DialogFactory.SaveFile())
            {
                if (diag.ShowDialog() == DialogResult.OK)
                {
                    System.IO.File.WriteAllText(diag.FileName, Results.Text);
                    MessageService.ShowMessage(string.Format(Properties.Resources.Log_Saved, diag.FileName));
                }
            }
        }

    }
}