using System;
using System.Collections;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Windows.Forms.DataVisualization.Charting;
using System.Timers;
using Microsoft.AnalysisServices;
using Tom = Microsoft.AnalysisServices.Tabular;
using System.Xml;
using System.Drawing;

namespace ASPerfMon
{
    public partial class ASPerfMon : Form
    {
        public ASPerfMon()
        {
            InitializeComponent();
        }

        private Tom.Server _server = new Tom.Server();
        private System.Timers.Timer _timer;
        private const int _smallestFont = 12;
        private string _serverConnectionString;
        private DateTime _connectionTime;

        //--------------------------------------------------
        //120 X axis markers, one for each array item.
        //Each item holds a dictionary with lookup key of series name (e.g. database name), which returns a SeriesPoint object.
        Dictionary<string, SeriesPoint>[] _seriesData = new Dictionary<string, SeriesPoint>[120];

        class SeriesPoint
        {
            public string XAxisLabel;
            public long MemoryUsedBytes;
            public long MemoryUsedMegabytes
            {
                get
                {
                    return MemoryUsedBytes / 1024 / 1024;
                }
            }

            public SeriesPoint(string xAxisCategoryName)
            {
                this.XAxisLabel = xAxisCategoryName;
                this.MemoryUsedBytes = 0;
            }
        }

        private void ASPerfMon_Load(object sender, EventArgs e)
        {
            InitializeChart(false);
        }

        private void InitializeChart(bool withSampleSeries)
        {
            chartControl.ChartAreas[0].AxisX.MajorGrid.Enabled = false;
            chartControl.ChartAreas[0].AxisX.Interval = 5;
            chartControl.ChartAreas[0].AxisY.IsMarksNextToAxis = true;
            chartControl.ChartAreas[0].AxisY.LabelStyle.Format = "{0:n0}";
            chartControl.ChartAreas[0].AxisY.Title = "Memory Usage MB";
            chartControl.Palette = ChartColorPalette.Pastel;
            chartControl.ChartAreas[0].AxisY.TitleFont = new Font(Font.FontFamily, _smallestFont + 2);
            chartControl.Legends[0].Font = new Font(Font.FontFamily, _smallestFont);
            chartControl.Legends[0].LegendItemOrder = LegendItemOrder.ReversedSeriesOrder;

            //initialize the chart with blanks
            Dictionary<string, SeriesPoint> pointsSample;
            if (withSampleSeries)
                pointsSample = GetNewXAxisMarkerPointsFromAs();
            else
            {
                pointsSample = new Dictionary<string, SeriesPoint>();
            }

            chartControl.Series.Clear();
            if (withSampleSeries)
            {
                for (int i = _seriesData.Length - 1; i >= 0; i--)
                {
                    foreach (KeyValuePair<string, SeriesPoint> entry in pointsSample)//to get series
                    {
                        entry.Value.XAxisLabel = " ";
                        entry.Value.MemoryUsedBytes = 0;

                        //add the series if not there yet
                        if (chartControl.Series.FindByName(entry.Key) == null)
                        {
                            Series series = new Series(entry.Key);
                            chartControl.Series.Add(series);
                        }
                        chartControl.Series[entry.Key].Points.AddXY(" ", 0);
                    }
                    _seriesData[i] = pointsSample;
                }
                PaintChart();
            }
            else
            {
                chartControl.Series.Add("");
                for (int i = 0; i < _seriesData.Length; i++)
                {
                    chartControl.Series[0].Points.AddXY(" ", 0);
                }
                chartControl.Invalidate();
            }

            _timer = new System.Timers.Timer(Settings.Default.SampleInterval);
            _timer.Elapsed += OnTimedEvent;
            _timer.AutoReset = true;
        }

        private void PaintChart()
        {
            //set dynamic scale
            long maxValue = 0;
            for (int i = _seriesData.Length - 1; i >= 0; i--)
            {
                long pointsStack = 0;
                foreach (SeriesPoint point in _seriesData[i].Values)
                {
                    pointsStack += Math.Abs(point.MemoryUsedMegabytes);
                }

                if (pointsStack > maxValue)
                    maxValue = pointsStack;
            }
            chartControl.ChartAreas[0].AxisY.IsStartedFromZero = true;
            chartControl.ChartAreas[0].AxisY.Minimum = double.NaN;
            chartControl.ChartAreas[0].AxisY.Maximum = double.NaN;
            chartControl.ChartAreas[0].RecalculateAxesScale();
            chartControl.Invalidate();
        }

        delegate void SetTimerCallback(Object source, ElapsedEventArgs e);
        private void OnTimedEvent(Object source, ElapsedEventArgs e)
        {
            try
            {
                if (chartControl.InvokeRequired)
                {
                    SetTimerCallback callback = new SetTimerCallback(OnTimedEvent);
                    //todo safer invoke look up bism
                    try
                    {
                        Invoke(callback, new object[] { source, e });
                    }
                    catch { }
                }
                else
                {
                    //Todo delete this once http stability fix is done
                    if ((DateTime.Now - _connectionTime).TotalMinutes > 8)
                    {
                        _server.Disconnect();
                        _server.Connect(_serverConnectionString);
                        _connectionTime = DateTime.Now;
                    }

                    _seriesData[0] = GetNewXAxisMarkerPointsFromAs();

                    //rebind
                    chartControl.Series.Clear();

                    //for each X axis marker
                    for (int i = _seriesData.Length - 1; i > 0; i--)
                    {
                        //push back one category
                        _seriesData[i] = _seriesData[i - 1];

                        //foreach series for the X axis marker we are populating: ...
                        foreach (KeyValuePair<string, SeriesPoint> entry in _seriesData[i])
                        {
                            //add the series if not there yet
                            if (chartControl.Series.FindByName(entry.Key) == null)
                            {
                                Series series = new Series(entry.Key);
                                series.ChartType = SeriesChartType.StackedColumn;
                                chartControl.Series.Add(series);
                            }

                            //add the data point for the series
                            DataPoint point = new DataPoint();
                            point.SetValueXY(entry.Value.XAxisLabel, entry.Value.MemoryUsedMegabytes);
                            point.ToolTip = string.Format($"{entry.Value.XAxisLabel} - {entry.Key}: {string.Format("{0:#,###0}", entry.Value.MemoryUsedMegabytes)} MB");
                            chartControl.Series[entry.Key].Points.Add(point);
                        }
                    }
                    PaintChart();
                }
            }
            catch (Exception exc)
            {
                //Workaround for timeout over HTTP. Try to reconnect - todo delete
                try
                {
                    System.Diagnostics.Debug.WriteLine($"Exception occurred at {DateTime.Now}: {exc.Message}");
                    _server.Disconnect();
                    _server.Connect(_serverConnectionString);
                    _connectionTime = DateTime.Now;
                }
                catch
                {
                    _timer.Enabled = false;
                    MessageBox.Show($"Error: {exc.Message}", "AS PerfMon", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
            }
        }

        private Dictionary<string, SeriesPoint> GetNewXAxisMarkerPointsFromAs()
        {
            Dictionary<string, SeriesPoint> points = new Dictionary<string, SeriesPoint>();
            string xAxisLabel = DateTime.Now.ToString("hh:mm:ss tt");
            string commandStatement = String.Format("SELECT [OBJECT_PARENT_PATH], [OBJECT_MEMORY_NONSHRINKABLE] FROM $SYSTEM.DISCOVER_OBJECT_MEMORY_USAGE");
            XmlNodeList rows = ExecuteXmlaCommand(_server, commandStatement);

            foreach (XmlNode row in rows)
            {
                long result, label;
                if (long.TryParse(row.LastChild.InnerText, out result) && result > 0)
                {
                    string objectName = "";
                    string[] pathMembers = row.ChildNodes[0].InnerText.Split('.');

                    if (pathMembers.Length > 2 && _server.Databases.ContainsName(pathMembers[2]))
                    {
                        //Database name identified
                        objectName = pathMembers[2];
                    }
                    else
                    {
                        string firstMember = row.ChildNodes[0].InnerText;

                        //Ignore if number or blank. Assuming this is a double counted, shared allocation.
                        if (!long.TryParse(firstMember, out label) && firstMember != "")
                        {
                            objectName = firstMember;
                        }
                    }

                    if (objectName != "" && objectName != "Global") //todo: delete global condition
                    {
                        if (!points.ContainsKey(objectName))
                            points.Add(objectName, new SeriesPoint(xAxisLabel));

                        points[objectName].MemoryUsedBytes += Math.Abs(result);
                    }
                }
                else if (result > 0)
                {
                    System.Diagnostics.Debug.WriteLine("We have a problem parsing");
                }
            }
            return points;
        }

        public static XmlNodeList ExecuteXmlaCommand(Microsoft.AnalysisServices.Core.Server amoServer, string commandStatement)
        {
            XmlWriter xmlWriter = amoServer.StartXmlaRequest(XmlaRequestType.Undefined);
            WriteSoapEnvelopeWithCommandStatement(xmlWriter, amoServer.SessionID, commandStatement);
            System.Xml.XmlReader xmlReader = amoServer.EndXmlaRequest();
            xmlReader.MoveToContent();
            string fullEnvelopeResponseFromServer = xmlReader.ReadOuterXml();
            xmlReader.Close();

            XmlDocument documentResponse = new XmlDocument();
            documentResponse.LoadXml(fullEnvelopeResponseFromServer);
            XmlNamespaceManager nsmgr = new XmlNamespaceManager(documentResponse.NameTable);
            nsmgr.AddNamespace("myns1", "urn:schemas-microsoft-com:xml-analysis");
            nsmgr.AddNamespace("myns2", "urn:schemas-microsoft-com:xml-analysis:rowset");
            XmlNodeList rows = documentResponse.SelectNodes("//myns1:ExecuteResponse/myns1:return/myns2:root/myns2:row", nsmgr);
            return rows;
        }

        public static void WriteSoapEnvelopeWithCommandStatement(XmlWriter xmlWriter, string sessionId, string commandStatement)
        {
            //--------------------------------------------------------------------------------
            // This is a sample of the XMLA request we'll write:
            //
            // <Envelope xmlns="http://schemas.xmlsoap.org/soap/envelope/">
            //   <Header>
            //     <Session soap:mustUnderstand="1" SessionId="THE SESSION ID HERE" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns="urn:schemas-microsoft-com:xml-analysis" />
            //   </Header>
            //   <Body>
            //      <Execute xmlns="urn:schemas-microsoft-com:xml-analysis">
            //          <Command>
            //              <Statement>
            //                  ...
            //              </Statement>
            //          </Command>
            //          <Properties/>
            //      </Execute>
            //   </Body>
            // </Envelope>
            //--------------------------------------------------------------------------------
            xmlWriter.WriteStartElement("Envelope", "http://schemas.xmlsoap.org/soap/envelope/");
            xmlWriter.WriteStartElement("Header");
            if (sessionId != null)
            {
                xmlWriter.WriteStartElement("Session", "urn:schemas-microsoft-com:xml-analysis");
                xmlWriter.WriteAttributeString("soap", "mustUnderstand", "http://schemas.xmlsoap.org/soap/envelope/", "1");
                xmlWriter.WriteAttributeString("SessionId", sessionId);
                xmlWriter.WriteEndElement(); // </Session>
            }
            xmlWriter.WriteEndElement(); // </Header>
            xmlWriter.WriteStartElement("Body");
            xmlWriter.WriteStartElement("Execute", "urn:schemas-microsoft-com:xml-analysis");
            xmlWriter.WriteStartElement("Command");
            xmlWriter.WriteElementString("Statement", commandStatement);
            xmlWriter.WriteEndElement(); // </Command>
            xmlWriter.WriteStartElement("Properties");
            xmlWriter.WriteEndElement(); // </Properties>
            xmlWriter.WriteEndElement(); // </Execute>
            xmlWriter.WriteEndElement(); // </Body>
            xmlWriter.WriteEndElement(); // </Envelope>
        }

        private void btnConnect_Click(object sender, EventArgs e)
        {
            Connect();
        }

        private void Connect()
        {
            try
            {
                Connect connForm = new Connect();
                connForm.StartPosition = FormStartPosition.CenterParent;
                connForm.ShowDialog();

                if (connForm.DialogResult == DialogResult.OK)
                {
                    _timer.Enabled = false;

                    if (connForm.IntegratedAuth)
                        _serverConnectionString = $"Provider=MSOLAP;Data Source={connForm.ServerName};";
                    else
                        _serverConnectionString = $"Provider=MSOLAP;Data Source={connForm.ServerName};User ID={connForm.UserName};Password={connForm.Passwrod};Persist Security Info=True;Impersonation Level=Impersonate;";

                    _server = new Tom.Server();
                    _server.Connect(_serverConnectionString);
                    _connectionTime = DateTime.Now;
                    if (_server.ServerProperties.Count == 0)
                    {
                        throw new ConnectionException("User is not AS admin.");
                    }

                    chartControl.Titles.Clear();
                    chartControl.Titles.Add(connForm.ServerName);
                    chartControl.Titles[0].Font = new Font(Font.FontFamily, _smallestFont + 4);

                    InitializeChart(true);
                    _timer.Interval = connForm.SampleInterval;
                    _timer.Enabled = true;

                    Settings.Default.ServerName = connForm.ServerName;
                    Settings.Default.UserName = connForm.UserName;
                    Settings.Default.SampleInterval = connForm.SampleInterval;
                    Settings.Default.IntegratedAuth = connForm.IntegratedAuth;
                    Settings.Default.Save();
                }
            }
            catch (Exception exc)
            {
                MessageBox.Show($"Cannot connect.\n{exc.Message}", "AS PerfMon", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        private void ASPerfMon_FormClosing(object sender, FormClosingEventArgs e)
        {
            Disconnect();
        }

        private void Disconnect()
        {
            try
            {
                if (_server != null) _server.Disconnect();
            }
            catch { }
        }

        private void chartControl_GetToolTipText(object sender, ToolTipEventArgs e)
        {
            //Event handler has to be hooked up in order to display tooltip (!)
        }

        private void ASPerfMon_Shown(object sender, EventArgs e)
        {
            Connect();
        }
    }
}
