#define XDateTimeAxis

using System;
using System.Collections.Generic;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using DDUtilityApp.LOGPARSER.DATA;
using JWH;
using JWH.DATA;
using Telerik.Charting;
using Telerik.WinControls.UI;

namespace DDUtilityApp.LOGPARSER
{

    public partial class FrmMessageReplyTime1 : Form
    {

        #region [ Deleage ] ===================================================
        #endregion

        #region [ Events ] ====================================================
        #endregion

        #region [ Variables ] =================================================

        private Dictionary<string, StandardDataAnalysis> AnalysisCollection = new Dictionary<string, StandardDataAnalysis>();

        private CartesianAxis HorizontalAxis { get; set; } = null;

        private LinearAxis VerticalAxis { get; set; } = null;

        #endregion

        #region [ Properties ] ================================================

        public StandardCollection StandardCollection { get; set; } = null;

        public Dictionary<string, List<StandardDataPair>> MessageCollection { get; set; } = new Dictionary<string, List<StandardDataPair>>();

        public RadGridView GridView { get; set; } = null;

        #endregion

        #region [ Constructor ] ===============================================

        public FrmMessageReplyTime1()
        {
            InitializeComponent();

            this.SetLayout();
            this.SetEventHandler();
        }

        public FrmMessageReplyTime1(StandardCollection standardCollection) : this()
        {
            this.StandardCollection = standardCollection;
        }

        private void SetLayout()
        {
            Font font = new Font("돋움체", 9);
            this.cboxMessageName.Font = font;

            this.Grid_Setting();
            this.GridAnalysis_Setting();
            this.Chart_Setting();
        }

        private void SetEventHandler()
        {
            this.btnGenerate.Click += this.BtnGenerate_Click;
            this.chkTrimmed.CheckedChanged += this.ChkTrimmed_CheckedChanged;
            this.button1.Click += this.Button1_Click;
            this.cboxMessageName.SelectedIndexChanged += this.CboxMessageName_SelectedIndexChanged;
            this.chkShowLabel.CheckedChanged += this.ChkShowLabel_CheckedChanged;

            ChartTrackballController controller = new ChartTrackballController();
            controller.TextNeeded += this.ChartTrackball_TextNeeded;
            this.chart.Controllers.Add(controller);

            this.grid.RowFormatting += this.Grid_RowFormatting;
            this.grid.CellDoubleClick += this.Grid_CellDoubleClick;

            this.gridAnalysis.RowFormatting += this.GridAnalysis_RowFormatting;
            this.gridAnalysis.CellFormatting += this.GridAnalysis_CellFormatting;
        }

        #endregion

        #region [ Control Events ] ============================================

        private void BtnGenerate_Click(object sender, EventArgs e)
        {
            this.Generate();
        }

        private void ChkTrimmed_CheckedChanged(object sender, EventArgs e)
        {
        }

        private void CboxMessageName_SelectedIndexChanged(object sender, EventArgs e)
        {
            this.chart.Series.Clear();

            try
            {

                #region Grid Display

                List<StandardDataPair> lst = new List<StandardDataPair>();
                if (string.IsNullOrEmpty(this.cboxMessageName.Text))
                {
                    foreach (KeyValuePair<string, List<StandardDataPair>> pair in this.MessageCollection)
                    {
                        lst.AddRange(pair.Value.ToArray());
                        this.Chart_AddSeries(pair.Key, pair.Value.ToArray());
                    }
                }
                else
                {
                    string key = this.cboxMessageName.Text;
                    if (this.MessageCollection.ContainsKey(key) == false) return;

                    lst.AddRange(this.MessageCollection[key].ToArray());
                    this.Chart_AddSeries(key, this.MessageCollection[key].ToArray());
                }

                this.grid.AutoBinding(lst.ToArray());

                #endregion

                #region Chart Display

                if (lst.Count < 1) return;
                if (this.chart.Series.Count < 1) return;

                StandardDataPair min = lst[0];
                StandardDataPair max = lst[0];
                foreach (StandardDataPair pair in lst)
                {
                    if (pair.RequestTime < min.RequestTime) min = pair;
                    if (pair.RequestTime > max.RequestTime) max = pair;
                }

                foreach (CartesianAxis item in this.chart.Axes)
                {
                    if (item.GetType() == typeof(DateTimeContinuousAxis))
                    {
                        DateTimeContinuousAxis axis = item as DateTimeContinuousAxis;
                        //datetimeAxis.PlotMode = AxisPlotMode.BetweenTicks;
                        //datetimeAxis.LabelFormat = "{0:d}";
                        axis.MajorStepUnit = TimeInterval.Hour;
                        axis.LabelFormatProvider = new DateTimeFormatProvider();

                    }
                    else if (item.GetType() == typeof(CategoricalAxis))
                    {
                        CategoricalAxis axis = item as CategoricalAxis;
                        if (lst.Count < 10) axis.MajorTickInterval = 1;
                        else axis.MajorTickInterval = (int)Math.Round(lst.Count / 10.0);
                    }
                    else if (item.GetType() == typeof(LinearAxis))
                    {
                        LinearAxis axis = item as LinearAxis;
                    }
                }

                #endregion

            }
            catch (Exception ex)
            {
                XLogger.Instance.Fatal(ex);
            }
        }

        private void Button1_Click(object sender, EventArgs e)
        {
        }

        private void ChkShowLabel_CheckedChanged(object sender, EventArgs e)
        {
            foreach (ChartSeries series in this.chart.Series)
                series.ShowLabels = this.chkShowLabel.Checked;
        }

        private void ChartTrackball_TextNeeded(object sender, TextNeededEventArgs e)
        {
            StandardDataPair data = e.Points[0].DataPoint.DataItem as StandardDataPair;
            if (data != null)
            {
                e.Text = $"{data.RequestTime}{Environment.NewLine}{data.MessageName} : {data.Interval}";
            }

        }

        private void Grid_RowFormatting(object sender, RowFormattingEventArgs e)
        {
            StandardDataPair stdPair = e.RowElement.RowInfo.DataBoundItem as StandardDataPair;
            if (stdPair == null) return;
            if (this.AnalysisCollection.ContainsKey(stdPair.MessageName) == false) return;

            StandardDataAnalysis stdAnalysis = this.AnalysisCollection[stdPair.MessageName];
            if (stdAnalysis.CheckOver(stdPair)) e.RowElement.ForeColor = Color.Red;
            else e.RowElement.ForeColor = SystemColors.ControlText;
        }

        private void Grid_CellDoubleClick(object sender, GridViewCellEventArgs e)
        {
            StandardDataPair pair = e.Row.DataBoundItem as StandardDataPair;
            if (pair == null) return;

            if (this.GridView == null) return;
            RadGridView grid = this.GridView;
            try
            {
                this.Cursor = Cursors.WaitCursor;
                StandardData data = pair.Request;
                if (e.Column.FieldName.ToUpper() == "RESPONSETIME") data = pair.Response;

                int lineNumber = data.LineNumber;
                foreach (GridViewRowInfo row in grid.Rows)
                {
                    try
                    {
                        StandardData standardData = row.DataBoundItem as StandardData;
                        if (standardData == null) continue;
                        if (lineNumber <= standardData.LineNumber)
                        {
                            this.grid.Focus();
                            row.IsSelected = false;
                            row.IsCurrent = false;
                            row.IsSelected = true;
                            row.IsCurrent = true;
                            break;
                        }
                    }
                    catch (Exception ex)
                    {
                        XLogger.Instance.Debug(ex);
                    }
                }

            }
            catch (Exception ex)
            {
                XLogger.Instance.Fatal(ex);
            }
            finally
            {
                this.Cursor = Cursors.Default;
            }
        }

        private void GridAnalysis_RowFormatting(object sender, RowFormattingEventArgs e)
        {
        }

        private void GridAnalysis_CellFormatting(object sender, CellFormattingEventArgs e)
        {
            string[] arrTrimmed = new string[] { "TrimmedMean", "TrimmedStdDeviation" };
            string[] arrArithmetic = new string[] { "ArithmeticMean", "ArithmeticStdDeviation" };
            if (this.chkTrimmed.Checked)
            {
                if (arrArithmetic.Contains(e.Column.FieldName)) e.CellElement.ForeColor = Color.LightGray;
                else e.CellElement.ForeColor = SystemColors.ControlText;
            }
            else
            {
                if (arrTrimmed.Contains(e.Column.FieldName)) e.CellElement.ForeColor = Color.LightGray;
                else e.CellElement.ForeColor = SystemColors.ControlText;
            }
        }

        #endregion

        #region [ Public Method ] =============================================

        public void Generate()
        {
            this.DataGenerate();
            this.DataAnalysis();

            List<string> lstMessage = new List<string>();
            lstMessage.Add("");
            lstMessage.AddRange(this.MessageCollection.Keys.ToArray());
            lstMessage.Sort();
            this.cboxMessageName.DataSource = lstMessage.ToArray();
            this.cboxMessageName.SelectedIndex = 0;
        }

        #endregion

        #region [ Method ] ====================================================

        private void DataGenerate()
        {
            Dictionary<string, List<StandardDataPair>> dicSrc = new Dictionary<string, List<StandardDataPair>>();

            try
            {
                this.MessageCollection.Clear();

                string[] arrServer = new string[] { "MES", "FDC", "RMS", "RTD" };
                foreach (StandardData data in this.StandardCollection)
                {
                    if (arrServer.Contains(data.Server) == false) continue;
                    if (string.IsNullOrWhiteSpace(data.TID)) continue;

                    string key = data.MessageName.Replace("Reply", "");
                    if (dicSrc.ContainsKey(key) == false)
                    {
                        dicSrc.Add(key, new List<StandardDataPair>());
                        StandardDataPair pair = dicSrc[key].Where(x => x.TID == data.TID).FirstOrDefault();
                        if (pair == null) dicSrc[key].Add(new StandardDataPair(data));
                        else pair.Add(data);
                    }
                    else
                    {
                        StandardDataPair pair = dicSrc[key].Where(x => x.TID == data.TID).FirstOrDefault();
                        if (pair == null) dicSrc[key].Add(new StandardDataPair(data));
                        else pair.Add(data);
                    }
                }

                foreach (KeyValuePair<string, List<StandardDataPair>> srcPair in dicSrc)
                {
                    string key = srcPair.Key;
                    foreach (StandardDataPair pair in srcPair.Value)
                    {
                        if (pair.Request == null || pair.Response == null) continue;

                        if (this.MessageCollection.ContainsKey(key) == false) MessageCollection.Add(key, new List<StandardDataPair>());
                        this.MessageCollection[key].Add(pair);
                    }
                }

            }
            catch (Exception ex)
            {
                XLogger.Instance.Fatal(ex);
            }
        }

        private void DataAnalysis()
        {
            this.AnalysisCollection.Clear();

            foreach (KeyValuePair<string, List<StandardDataPair>> item in this.MessageCollection)
            {
                if (this.AnalysisCollection.ContainsKey(item.Key) == false)
                {
                    StandardDataAnalysis analysis = new StandardDataAnalysis(item.Key);
                    analysis.UseTrimmed = this.chkTrimmed.Checked;
                    analysis.TrimmedBegin = (double)this.numTrimmedBegin.Value;
                    analysis.TrimmedEnd = (double)this.numTrimmedEnd.Value;
                    this.AnalysisCollection.Add(item.Key, analysis);
                }

                this.AnalysisCollection[item.Key].AddRange(item.Value.ToArray());
            }

            this.gridAnalysis.AutoBinding(this.AnalysisCollection.Values.ToArray());
        }

        private void Grid_Setting()
        {
            this.grid.MultiSelect = true;
            this.grid.Columns.Clear();
            this.grid.TableElement.RowHeight = 20;

            this.grid.AddColumn("MessageName");
            this.grid.AddColumn("RequestTime");
            this.grid.AddColumn("ResponseTime");
            this.grid.AddColumn("Interval", "", true, "{0:#,##0.000}");
        }

        private void GridAnalysis_Setting()
        {
            this.gridAnalysis.MultiSelect = true;
            this.gridAnalysis.Columns.Clear();
            this.gridAnalysis.TableElement.RowHeight = 20;

            this.gridAnalysis.AddColumn("MessageName");
            this.gridAnalysis.AddColumn("Count");
            this.gridAnalysis.AddColumn("Min", "", true, "{0:#,##0.000}");
            this.gridAnalysis.AddColumn("Max", "", true, "{0:#,##0.000}");
            this.gridAnalysis.AddColumn("TrimmedMean", "절사평균", true, "{0:#,##0.000}");
            this.gridAnalysis.AddColumn("TrimmedStdDeviation", "절사편차", true, "{0:#,##0.000}");
            this.gridAnalysis.AddColumn("ArithmeticMean", "산술평균", true, "{0:#,##0.000}");
            this.gridAnalysis.AddColumn("ArithmeticStdDeviation", "산술편차", true, "{0:#,##0.000}");
        }

        private void Chart_Setting()
        {
            this.chart.ShowGrid = true;
            this.chart.ShowLegend = true;

#if DateTimeAxis
            this.HorizontalAxis = new DateTimeContinuousAxis()
            {
                MajorStepUnit = TimeInterval.Hour,
                LabelFormatProvider = new DateTimeFormatProvider()
            };
            this.VerticalAxis = new LinearAxis()
            {
            };
#else
            this.HorizontalAxis = new CategoricalAxis()
            {
            };
            this.VerticalAxis = new LinearAxis()
            {
            };
#endif
        }

        private ChartSeries Chart_AddSeries(string name, StandardDataPair[] values)
        {
            LineSeries series = new LineSeries();
            series.Name = name;
            //series.HorizontalAxis = this.HorizontalAxis;
            //series.VerticalAxis = this.VerticalAxis;
            series.PointSize = new SizeF(3, 3);
            series.BorderWidth = 2;
            series.CategoryMember = "RequestTime";
            series.ValueMember = "Interval";
            series.ShowLabels = this.chkShowLabel.Checked;
            series.Spline = true;
            series.CombineMode = ChartSeriesCombineMode.Stack;
            series.DataSource = values;
            this.chart.Series.Add(series);

            return series;
        }

        #endregion

    }

    public class DateTimeFormatProvider : IFormatProvider, ICustomFormatter
    {
        public object GetFormat(Type formatType)
        {
            return this;
        }

        public string Format(string format, object arg, IFormatProvider formatProvider)
        {
            DateTime val = (DateTime)arg;
            return val.ToString("HH:mm:ss");
        }
    }

    public class StandardDataAnalysis : DataTableBase
    {

        /// <summary>MessageName</summary>
        public string MessageName { get; set; } = string.Empty;

        public int Count { get { return this.Collection.Count; } }

        public bool UseTrimmed { get; set; } = false;

        public double Min { get; set; } = -1;

        public double Max { get; set; } = -1;

        /// <summary>산술평균</summary>
        public double ArithmeticMean { get; set; } = 0;

        /// <summary>산술평균>표준편차</summary>
        public double ArithmeticStdDeviation { get; set; } = 0;

        /// <summary>절사평균</summary>
        public double TrimmedMean { get; set; } = 0;

        /// <summary>절사평균>표준편차</summary>
        public double TrimmedStdDeviation { get; set; } = 0;

        /// <summary>절사평균 범위</summary>
        public double TrimmedBegin { get; set; } = 0;

        /// <summary>절사평균 범위</summary>
        public double TrimmedEnd { get; set; } = 80;

        private List<StandardDataPair> Collection { get; set; } = new List<StandardDataPair>();

        public StandardDataAnalysis() { }

        public StandardDataAnalysis(string name, params StandardDataPair[] value)
        {
            this.MessageName = name;
            this.Collection.AddRange(value);
            if (value.Length > 0) this.Calculation();
        }

        public void AddRange(params StandardDataPair[] values)
        {
            this.Collection.AddRange(values);
            this.Calculation();
        }

        private void Calculation()
        {

            double sum = 0;
            double average = 0;

            #region 절사평균
            sum = 0;
            int trimStartIndex = (int)(this.Collection.Count * (this.TrimmedBegin / 100));
            int trimEndIndex = (int)(this.Collection.Count * (this.TrimmedEnd / 100));
            int trimCount = 0;
            int trimIndex = -1;
            foreach (StandardDataPair item in this.Collection.OrderBy(item => item.Interval))
            {
                trimIndex++;
                if (trimIndex < trimStartIndex) continue;
                if (trimIndex > trimEndIndex) break;
                sum += item.Interval;
                trimCount++;
            }
            average = sum / trimCount;
            this.TrimmedMean = average;
            #endregion

            #region 산술평균
            sum = 0;
            double min = -1;
            double max = -1;
            foreach (StandardDataPair item in this.Collection)
            {
                sum += item.Interval;
                if (item.Interval < min || min == -1) min = item.Interval;
                if (item.Interval > max || max == -1) max = item.Interval;
            }
            average = sum / this.Collection.Count;
            this.Min = min; this.Max = max;
            this.ArithmeticMean = average;
            #endregion

            double deviation = 0;
            double sumTrimVariance = 0;
            double sumArithmeticVariance = 0;
            double variance = 0;
            foreach (StandardDataPair item in this.Collection)
            {
                deviation = item.Interval - this.TrimmedMean;           // 절사평균 편차
                sumTrimVariance += Math.Pow(deviation, 2);

                deviation = item.Interval - this.ArithmeticMean;        // 산술평균 편차
                sumArithmeticVariance += Math.Pow(deviation, 2);
            }
            variance = sumTrimVariance / this.Collection.Count;         // 절사평균 분산
            this.TrimmedStdDeviation = Math.Sqrt(variance);             // 절사평균 표준편차

            variance = sumArithmeticVariance / this.Collection.Count;   // 산술평균 분산
            this.ArithmeticStdDeviation = Math.Sqrt(variance);          // 산술평균 표준편차
        }

        public bool CheckOver(StandardDataPair pair)
        {
            double average = 0;
            if (this.UseTrimmed) average = this.ArithmeticMean; else average = this.TrimmedMean;

            double min = average - (this.ArithmeticStdDeviation / 2);
            double max = average + (this.ArithmeticStdDeviation / 2);

            if (pair.Interval > max) return true;
            return false;
        }

    }

}
