using System.Collections.Generic;
using System;
using System.Drawing;
using System.Diagnostics;
using System.Data;
using System.Collections;
using System.Windows.Forms;
using System.Linq;
using System.Data.Linq;
using System.Runtime.InteropServices;
using TrendCtrlII;
using AR;
using System.Windows.Media.Animation;
using System.Threading.Tasks;
using System.Collections.Concurrent;
using System.Diagnostics.Eventing.Reader;
using System.Windows.Forms.DataVisualization.Charting;
using System.Text.RegularExpressions;
using OpenTK.Graphics.ES20;
using ScottPlot.WinForms;
using ScottPlot.Control;

namespace vmsnet
{

    public partial class Frm_trend
    {
        ChartListData[] ChartListData = new ChartListData[10];
        string SelectedGRPChValue = "";
        List<int> totalChlist = new List<int>();
        DocumentElement.CHANNELDataTable DTCHLIST;

        readonly ScottPlot.WinForms.FormsPlot formsPlot1;

        /* 작성자: 이재웅, 작성일: 2024-09-26, 내용: 데이터그리드 행의 전체선택인지 결정하는 변수 선언 */
        private bool selALL = true;

        List<string> LoadFileList = new List<string>();
        List<int> LoadCHList = new List<int>();
        ScottPlot.Plottables.Crosshair CrossHair;
        ScottPlot.Plottables.Scatter[] myPlots;
        int RecordCount = 0;
        ScottPlot.Plottables.VerticalLine[] CursorLine;
        ScottPlot.Plottables.AxisLine PlottableBeingDragged = null;
        List<DateTime>[] myPlotDataX = new List<DateTime>[0];
        List<float>[] myPlotDataY = new List<float>[0];

        public Frm_trend()
        {
            // 디자이너에서 이 호출이 필요합니다.
            InitializeComponent();

            PUB.INIT(out List<string> message);

            //차트 채널 및 파일데이터 변수
            for (int i = 0; i < ChartListData.Length; i++)
                ChartListData[i] = new ChartListData();

            CursorLine = new ScottPlot.Plottables.VerticalLine[2];

            formsPlot1 = new ScottPlot.WinForms.FormsPlot() { Dock = DockStyle.Fill };
            formsPlot1.MouseDown += FormsPlot1_MouseDown;
            formsPlot1.MouseUp += FormsPlot1_MouseUp;
            formsPlot1.MouseMove += FormsPlot1_MouseMove;

            panel8.Controls.Add(formsPlot1);

            // 초기 데이터 설정
            FormsPlot_Init(formsPlot1); /* 작성자: 이재웅, 작성일: 2024-09-09 */

            /* 작성자: 이재웅, 작성일: 2024-09-26, 내용: 사용자에 의한 셀수정 차단 */
            this.dv_chlist.EditMode = DataGridViewEditMode.EditProgrammatically;
        }

        public void Frm_trend_FormClosing(object sender, System.Windows.Forms.FormClosingEventArgs e)
        {
            if (this.dv_grp.SelectedRows.Count > 0)
            {
                PUB.TREND.tv_selectgroup0 = this.cmb_group.SelectedIndex.ToString();
                PUB.TREND.tv_selectgroup = this.bs_grp.Position.ToString();
            }
        }

        public void Frm_trend_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
        {
            switch (e.KeyCode)
            {
                case Keys.D:
                    if (e.Control) this.formsPlot1.Refresh();
                    break;
                case Keys.F10:
                    this.bt_cursor1.PerformClick();
                    break;
                case Keys.F11:
                    this.bt_cursor2.PerformClick();
                    break;
            }
        }
        public void Frm_trend_Load(System.Object sender, System.EventArgs e)
        {
            this.Show();
            Application.DoEvents();

            ////화면좌측의 그룹목록을 갱신
            RefreshGroupList();

            ////마지막으로 선택한 그룹을 선택해준다.
            var selidx0 = 0;
            int.TryParse(PUB.TREND.tv_selectgroup0, out selidx0);
            if (selidx0 < cmb_group.Items.Count)
                this.cmb_group.SelectedIndex = selidx0;
            else
                this.cmb_group.SelectedIndex = this.cmb_group.Items.Count > 0 ? 0 : -1;

            var selidx = 0;
            int.TryParse(PUB.TREND.tv_selectgroup, out selidx); // XMl.Data("trendview", "selectgroup", "0")
            this.bs_grp.Position = selidx;

            ////모든채널정보를 가져온다.
            PUB.smsg("Refresh GroupName");
            DTCHLIST = new DocumentElement.CHANNELDataTable();// pub.DS.CHANNEL.Clone(); // DBC.GetTable("Select * from CHANNEL ORDER BY IDX")
            DTCHLIST.Merge(PUB.DS.CHANNEL);
            //DTCHLIST.Columns.Add("GRPNAME");
            foreach (DocumentElement.CHANNELRow Dr in DTCHLIST.Rows)
            {
                Dr.GRPNAME = PUBC.GetGrpName(Dr.GIDX);
            }
            DTCHLIST.AcceptChanges();
            PUB.smsg("");
            toolStripLabel3.Text = $"<PATH:{PUB.CONFIG.GetDatabasePath()}>";

            //scootplot();
        }

        private ScottPlot.Plottables.AxisLine GetLineUnderMouse(float x, float y)
        {
            ScottPlot.CoordinateRect rect = formsPlot1.Plot.GetCoordinateRect(x, y, radius: 10);

            foreach (var axLine in formsPlot1.Plot.GetPlottables<ScottPlot.Plottables.AxisLine>().Reverse())
            {
                if (axLine.IsUnderMouse(rect))
                    return axLine;
            }

            return null;
        }
        private void FormsPlot1_MouseDown(object sender, MouseEventArgs e)
        {
            var lineUnderMouse = GetLineUnderMouse(e.X, e.Y);
            if (lineUnderMouse != null)
            {
                PlottableBeingDragged = lineUnderMouse;
                formsPlot1.Interaction.Disable(); // disable panning while dragging
            }
        }

        private void FormsPlot1_MouseUp(object sender, MouseEventArgs e)
        {
            PlottableBeingDragged = null;
            formsPlot1.Interaction.Enable(); // enable panning again
            formsPlot1.Refresh();
            this.UpdateCursorValue();
        }

        private void FormsPlot1_MouseMove(object sender, MouseEventArgs e)
        {
            // update cross line
            ScottPlot.Pixel mousePixel = new ScottPlot.Pixel(e.X, e.Y);
            ScottPlot.Coordinates mouseCoordinates = formsPlot1.Plot.GetCoordinates(mousePixel);
            //this.Text = $"X={mouseCoordinates.X:N3}, Y={mouseCoordinates.Y:N3}";
            if (CrossHair != null)
            {
                CrossHair.Position = mouseCoordinates;
                var time = DateTime.FromOADate(mouseCoordinates.X);

                CrossHair.VerticalLine.Text = $"{time:yy-MM-dd\nHH:mm:ss}";
                CrossHair.HorizontalLine.Text = $"{mouseCoordinates.Y:N2}v";
                formsPlot1.Refresh();
            }



            // this rectangle is the area around the mouse in coordinate units
            ScottPlot.CoordinateRect rect = formsPlot1.Plot.GetCoordinateRect(e.X, e.Y, radius: 10);

            // set cursor based on what's beneath the plottable
            var lineUnderMouse = GetLineUnderMouse(e.X, e.Y);

            if (PlottableBeingDragged is null)
            {
                if (lineUnderMouse is null) Cursor = Cursors.Default;
                else if (lineUnderMouse.IsDraggable && lineUnderMouse is ScottPlot.Plottables.VerticalLine) Cursor = Cursors.SizeWE;
                else if (lineUnderMouse.IsDraggable && lineUnderMouse is ScottPlot.Plottables.HorizontalLine) Cursor = Cursors.SizeNS;
            }
            else
            {
                // update the position of the plottable being dragged
                if (PlottableBeingDragged is ScottPlot.Plottables.HorizontalLine hl)
                {
                    hl.Y = rect.VerticalCenter;
                    hl.Text = $"{hl.Y:0.00}v";
                }
                else if (PlottableBeingDragged is ScottPlot.Plottables.VerticalLine vl)
                {
                    vl.X = rect.HorizontalCenter;
                    var time = DateTime.FromOADate(vl.X);
                    //vl.Text = $"{vl.X:0.00}";
                    if (lineUnderMouse == CursorLine[0])
                        vl.Text = $"[C1] {time:yy-MM-dd\nHH:mm:ss}";
                    else if (lineUnderMouse == CursorLine[1])
                        vl.Text = $"[C2] {time:yy-MM-dd\nHH:mm:ss}";
                    else
                        vl.Text = $"{time:yy-MM-dd\nHH:mm:ss}";
                }
                formsPlot1.Refresh();
            }
        }


        /// <summary>
        /// 그룹목록을 추가합니다.
        /// </summary>
        /// <param name="name">표시그룸명(사용자구분추가)</param>
        /// <param name="value">표시채널목록(comma)</param>
        /// <param name="idx">일련번호</param>
        /// <param name="rname">그룹명</param>
        /// <remarks></remarks>
        private void AddGroupRow(string name, int idx, string value, string rname)
        {
            ////최대idx를 찾아서 그보다 크게한다.
            var dr = this.dS1.group.NewgroupRow(); // Me.DataSet1.Tables("group").NewRow
            dr.cname = name;
            dr.value = value;
            dr.idx = idx;
            dr.rname = rname;
            dS1.group.AddgroupRow(dr);
            dS1.group.AcceptChanges();
        }

        /// <summary>
        /// 표시그룹목록을 갱신합니다. 좌측하단
        /// </summary>
        /// <remarks></remarks>
        private void RefreshGroupList()
        {

            ////상단 그룹목록
            //Dim sql As String = "Select * from GRP where USE=1 order by TITLE"
            var DT = PUB.DS.GRP.Where(t => t.USE == 1).OrderBy(t => t.TITLE).ToList();//.CopyToDataTable();//.Select("use=1", "title") as DocumentElement.GRPRow[]; // DBC.GetTable(sql)
            this.cmb_group.DataSource = DT;
            this.cmb_group.DisplayMember = "TITLE";
            this.cmb_group.ValueMember = "IDX";
            if (DT.Count() > 0)
            {
                this.cmb_group.SelectedIndex = 0;
            }

            ////현재의 그룹목록을 삭제
            dS1.group.Clear();

            ////사용자그룹추가
            //sql = "select * from VIEWGROUP order by TITLE,IDx"
            foreach (DocumentElement.VIEWGROUPRow dr in PUB.DS.VIEWGROUP.Select("", "title,idx")) //  DBC.GetTable(sql).Rows
            {
                string v = dr.VAL.Trim();
                if (string.IsNullOrEmpty(v))
                {
                    continue;
                }
                string newgrpname = "[사용자] " + dr.TITLE;
                AddGroupRow(newgrpname, dr.IDX, v, dr.GNAME);
            }

            ////시스템그룹추가
            // sql = "select * from GRP where USE=1"
            totalChlist.Clear();

            foreach (DocumentElement.GRPRow Dr in PUB.DS.GRP.Select("use=1")) // DBC.GetTable(sql).Rows
            {
                ////이그룹을 사용하는 채널중 사용채널을 찾는다.
                string v = "";
                foreach (DocumentElement.CHANNELRow Dr2 in PUB.DS.CHANNEL.Select("gidx=" + Dr.IDX.ToString() + " and enable=1", "idx")) // DBC.GetTable("select * from CHANNEL where GIDX=" & Dr("IDX") & " AND ENABLE=1 ORDER BY IDX").Rows
                {
                    v += System.Convert.ToString((string.IsNullOrEmpty(v) ? "" : ",") + Dr2.IDX.ToString());

                    if (totalChlist.Contains(Dr2.IDX) == false)
                    {
                        totalChlist.Add(Dr2.IDX);
                    }
                }
                if (!string.IsNullOrEmpty(v))
                {
                    //AddGroupRow("[시스템]" + Dr.TITLE, Dr.IDX, v, Dr.TITLE);
                }
            }

            short AllIDX = (short)dS1.group.Rows.Count; ////전체보기메뉴의 인덱스
            AddGroupRow("모든채널표시", -1, "", "전체");
        }

        #region 우측 채널 목록

        ////우측채널목록을 갱신합니다.
        private void RefreshChannelList()
        {
            this.dS1.channel.Rows.Clear();
            this.dS1.channel.AcceptChanges();

            //파일목록가져온다
            GrabFilelist();

            foreach (var charr in this.ChartListData)
            {
                foreach (var ch in charr.ChannelList)
                {
                    //전체채널목록에 포함되어있는 대상중 채널목록에 존재하지 않는 경우에만 처리한다.
                    if (DTCHLIST.Where(t => t.IDX == ch).Any() && this.dS1.channel.Where(t => t.idx == ch).Any() == false)
                    {
                        //전체채널정보에서 데이터 수집
                        var CHDataRow = DTCHLIST.Rows[ch - 1] as DocumentElement.CHANNELRow;
                        if (CHDataRow == null) continue;

                        //채널목록에도 추가한다.
                        var activeCount = this.dS1.channel.Where(t => t.use == true).Count();
                        var Dr = this.dS1.channel.NewchannelRow();
                        Dr.use = activeCount < PUB.CONFIG.MaxChCount;
                        Dr.cname = CHDataRow.TITLE;
                        Dr.idx = ch;
                        Dr.cc = CHDataRow.COLOR;
                        Dr.tidx = this.dS1.channel.Rows.Count; //// chinof 의 index번호를 설정해야한다.
                        this.dS1.channel.Rows.Add(Dr);
                    }
                }
            }
            this.dS1.channel.AcceptChanges();
        }

        #endregion



        void GrabFilelist()
        {
            /* 주석날짜: 2024-09-20 | 주석내용: VIEWGROUP.xml 파일내용 | 주석자: 이재웅
             * 
             * <VIEWGROUUP>
             *   <IDX>9</IDX>
             *   <GNAME>EL2500A</GNAME>
             *   <TITLE>GRP(1...10)</TITLE>
             *   <VAL>1,2,3,4,5,6,7,8,9,10</VAL>
             * </VIEWGROUUP>
             * 
             **************************************
             *
             * DRGRP.idx = 9
             * DRGRP.mame = "EL2500A"
             * DRGRP.cname = "[사용자] GRP(1...10)"
             * DRGRP.value = "1,2,3,4,5,6,7,8,9,10"
             */

            var sttdate = PUB.TREND.graph_time_start;
            var enddate = PUB.TREND.graph_time_end;
            //각 파일 그룹별 대상 파일을 수집합니다.
            Parallel.For(0, this.ChartListData.Length - 1, i =>
            {
                var DataFileName = $"DATAB{i + 1}";
                if (ChartListData[i].ChannelList.Any() == true) this.ChartListData[i].FileList = PUB.DB.GetfileS(DataFileName, sttdate, enddate);
                else this.ChartListData[i].FileList = new List<string>();

                Console.WriteLine($"Processing index {i} on thread {Task.CurrentId}");
            });

        }

        private void FormsPlot_Init(ScottPlot.WinForms.FormsPlot plot)
        {
            plot.Plot.Clear();

            // 초기 데이터 설정
            plot.Plot.Add.Palette = new ScottPlot.Palettes.Penumbra();
            // change figure colors
            plot.Plot.FigureBackground.Color = ScottPlot.Color.FromHex("#181818");
            plot.Plot.DataBackground.Color = ScottPlot.Color.FromHex("#1f1f1f");

            // change axis and grid colors
            plot.Plot.Axes.Color(ScottPlot.Color.FromHex("#d7d7d7"));
            plot.Plot.Grid.MajorLineColor = ScottPlot.Color.FromHex("#404040");

            // change legend colors
            plot.Plot.Legend.BackgroundColor = ScottPlot.Color.FromHex("#404040");
            plot.Plot.Legend.FontColor = ScottPlot.Color.FromHex("#d7d7d7");
            plot.Plot.Legend.OutlineColor = ScottPlot.Color.FromHex("#d7d7d7");
        }

        /// <summary>
        /// 데이터를 조회합니다.
        /// </summary>
        /// <param name="SearchFileList">파일목록을 다시 갱신합니다.</param>
        /// <remarks></remarks>
        private void ShowData(bool AllClear = true)
        {
            //return;
            Console.WriteLine("Enter : ShowData");
            this.Text = $"TrendViewer ({cmb_group.Text})";
            this.lb_selgroup.Text = $"[{cmb_group.Text}]";

            DateTime SDTotal = DateTime.Now;
            var chcount2 = this.dS1.channel.Rows.Count;
            if (chcount2 == 0)
            {
                UTIL.MsgE($"선택된 채널목록이 없습니다\n채널그룹이 손상되었거나 다시 선택하세요");
                return;
            }

            GrabFilelist();

            TimeSpan Tsing = DateTime.Now - SDTotal;
            Console.WriteLine($"[ShowData] Title And Ch : {Tsing.TotalMilliseconds}ms");


            DateTime fsd = DateTime.Now;
            var FileCount = ChartListData.Sum(t => t.FileList.Count);
            Tsing = DateTime.Now - SDTotal;
            Console.WriteLine($"[ShowData] {FileCount} Files Checked : {Tsing.TotalMilliseconds}");
            TimeSpan fts = DateTime.Now - fsd;
            lb_filesearchtime.Text = $"[Search : {fts.TotalMilliseconds}ms]";

            ////Record Count Check
            var avdate = PUB.DB.GetAvailableDates();    //변수선언 이동
            if (FileCount == 0)
            {
                Console.WriteLine("ShowData : FileCount 0 Refresh");
                PUB.smsg("");

                //var avdate = PUB.DB.GetAvailableDates();  //전위치
                if (avdate.Any() == false)
                {
                    UTIL.MsgE("지정된 폴더내에 저장된 자료가 없습니다");
                }
                else
                {
                    UTIL.MsgI("기간내 조회된 자료가 없습니다\n\n조회 가능 범위\n\n" +
                        $"{avdate.First().Year}-{avdate.First().Month}~" +
                        $"{avdate.Last().Year}-{avdate.Last().Month}");
                }
                return;
            }

            Tsing = DateTime.Now - SDTotal;
            Console.WriteLine($"[ShowData] Set Date Area : {Tsing.TotalMilliseconds}");

            //채널수만큼 데이터버퍼를 확보한다.
            var maxchno = this.dS1.channel.Max(t => t.idx); //idx는 실제 채널번호가 들어있다(1~
            this.myPlotDataX = new List<DateTime>[maxchno];
            this.myPlotDataY = new List<float>[maxchno];

            // 채널 개수만큼 Scatter 그래프 생성
            this.myPlots = new ScottPlot.Plottables.Scatter[maxchno];

            //커서삭제
            this.CursorLine[0] = null;
            this.CursorLine[1] = null;

            ////데이터를 모두 초기화하는경우
            if (AllClear)
            {
                LoadFileList.Clear();
                LoadCHList.Clear();
                Tsing = DateTime.Now - SDTotal;
                Console.WriteLine($"[ShowData] Clear Data : {Tsing.TotalMilliseconds}");
            }

            //커서데이터 삭제
            foreach (DS1.channelRow dr in this.dS1.channel)
            {
                dr.c1 = "";
                dr.c2 = "";
            }
            this.dS1.channel.AcceptChanges();


            ////추가된채널목록을 가진다.
            DateTime STime = DateTime.Now;

            int totalchcount = this.dS1.channel.Rows.Count;
            if (totalchcount > PUB.CONFIG.MaxChCount)
            {
                UTIL.MsgE($"채널 수({totalchcount})가 {PUB.CONFIG.MaxChCount}을 초과 합니다. [{PUB.CONFIG.MaxChCount}]이하로 사용자 채널을 생성하여 조회 하세요");
                return;
            }

            /* 주석날짜: 2024-09-20 | 주석자: 이재웅
             * 
             * ParseChDataToChart() : 채널데이터를 차트데이터로 변환 
             */

            //대상파일의 자료를 읽어와서 차트데이터를 구축한다.
            RecordCount = 0;
            Parallel.For(0, this.ChartListData.Length, i =>
            {
                ParseChDataToChart(i, ChartListData[i], DTCHLIST);
            });

            Tsing = DateTime.Now - SDTotal;
            Console.WriteLine($"[ShowData] Pase Data Complete : {Tsing.TotalMilliseconds}");

            var sttdate = PUB.TREND.graph_time_start;
            var enddate = PUB.TREND.graph_time_end;
            TimeSpan term = DateTime.Now - STime;

            ////조회영역 및 쿼리시간표시
            this.lb_Area.Text = "[ " + sttdate.ToString("yy-MM-dd HH:mm:ss") + "~" + enddate.ToString("yy-MM-dd HH:mm:ss") + " ]";
            this.lb_querytime.Text = $"[Loading : {term.TotalMilliseconds:#0.0}ms ]";

            ////SET OK
            STime = DateTime.Now;
            Console.WriteLine("ShowData : Chart Refresh");

            term = DateTime.Now - STime;
            this.lb_charttime.Text = $"[Chart : {term.TotalMilliseconds:#0.0}ms ]";

            Tsing = DateTime.Now - SDTotal;
            //Console.WriteLine($"[ShowData] Chart Refresh : {Tsing.TotalMilliseconds:#0.0}");

            TimeSpan Tstotal = DateTime.Now - SDTotal;
            this.lb_totaltime.Text = $"[total : {Tstotal.TotalMilliseconds:#0.0}ms]";

            Tsing = DateTime.Now - SDTotal;
            //Console.WriteLine($"[ShowData] Complete : {Tsing.TotalMilliseconds:#0.0}");



            // 초기 데이터 생성
            //this.formsPlot1.Plot.Clear();
            FormsPlot_Init(formsPlot1);

            #region 이전 코드 주석처리
            //formsPlot1.Plot.Add.Palette = new ScottPlot.Palettes.Penumbra();
            //// change figure colors
            //formsPlot1.Plot.FigureBackground.Color = ScottPlot.Color.FromHex("#181818");
            //formsPlot1.Plot.DataBackground.Color = ScottPlot.Color.FromHex("#1f1f1f");

            //// change axis and grid colors
            //formsPlot1.Plot.Axes.Color(ScottPlot.Color.FromHex("#d7d7d7"));
            //formsPlot1.Plot.Grid.MajorLineColor = ScottPlot.Color.FromHex("#404040");

            //// change legend colors
            //formsPlot1.Plot.Legend.BackgroundColor = ScottPlot.Color.FromHex("#404040");
            //formsPlot1.Plot.Legend.FontColor = ScottPlot.Color.FromHex("#d7d7d7");
            //formsPlot1.Plot.Legend.OutlineColor = ScottPlot.Color.FromHex("#d7d7d7");
            #endregion

            /* 주석날짜: 2024-09-20 | 주석내용: 채널별 데이터 그리기 | 주석자: 이재웅
             * 
             * xs : x축 List<DateTime>[] 형식의 데이터
             * ys : y축 List<float>[] 형식의 데이터
             * myScatter : x,y 좌표를 가지는 ScottPlot.Plottables.Scatter 형식의 데이터
             * 
             * 명시적선언時: List<DateTime>[] myPlotDataX
             * 명시적선언時: List<float>[] myPlotDataY
             * 명시적선언時: ScottPlot.Plottables.Scatter myScatter
             */

            for (int i = 0; i < this.myPlots.Length; i++)
            {
                var xs = myPlotDataX[i];
                if (xs == null || xs.Any() == false) continue;
                var ys = myPlotDataY[i];
                var myScatter = formsPlot1.Plot.Add.Scatter(xs, ys);

                //if (i > 2) break;
                //채널정보에서 색상을 가져온다.
                var chinfo = this.dS1.channel.Where(t => t.idx == i + 1).FirstOrDefault();    //idx에 실제 채널정보가 들어있으니 -1 해야함
                if (chinfo != null)
                {
                    var linecolor = Color.FromArgb(chinfo.cc);
                    myScatter.LineColor = new ScottPlot.Color(linecolor.R, linecolor.G, linecolor.B);
                }
                else myScatter.LineColor = ScottPlot.Colors.Red;
                myScatter.Color = myScatter.LineColor;
                /* 작성자: 이재웅, 작성일: 2024-11-27, 내용: 변경한 셀이름으로 차트범례 표시 */
                myScatter.LegendText = chinfo.ItemArray[1].ToString(); //$"CH{i + 1}";
                this.myPlots[i] = myScatter;    //visible 변경 및 데이터 접근을 위해 변수에 따로 저장해둔다.
            }
            formsPlot1.Plot.Axes.DateTimeTicksBottom();

            //1번항목을 가져와서 x,y축 range 값 설정
            var plot = this.myPlots.Where(t => t != null).FirstOrDefault();
            if (plot != null)
            { // 파일이 있더라도 해당하는 데이터가 없을때 Error 발생
                var ymin = plot.Data.GetLimitsY().Min;
                var ymax = plot.Data.GetLimitsY().Max;
                var xmin = plot.Data.GetLimitsX().Min;
                var xmax = plot.Data.GetLimitsX().Max;
                var maxdate = DateTime.FromOADate(xmin);
                var mindate = DateTime.FromOADate(xmax);
            }
            this.lb_datatcnt.Text = $"[ {FileCount} Files / {RecordCount} Records ]";
            CrossHair = formsPlot1.Plot.Add.Crosshair(0, 0);
            CrossHair.TextColor = ScottPlot.Colors.Red;
            CrossHair.TextBackgroundColor = CrossHair.HorizontalLine.Color;

            //축설정
            formsPlot1.Plot.Axes.AutoScaleX();
            if (PUB.TREND.y_scale_auto)
                formsPlot1.Plot.Axes.AutoScaleY();
            else
                formsPlot1.Plot.Axes.SetLimitsY(PUB.TREND.graph_y_start, PUB.TREND.graph_y_end);

            formsPlot1.Plot.YLabel("VOLTAGE");


            //X축 바닥 여백 변경
            formsPlot1.Plot.Axes.Bottom.MinimumSize = PUB.TREND.graph_bottom_minsize;

            //십자선추가
            CrossHair = formsPlot1.Plot.Add.Crosshair(0, 0);
            CrossHair.TextColor = ScottPlot.Colors.White;
            CrossHair.TextBackgroundColor = CrossHair.HorizontalLine.Color;

            //X축 날짜시간형태로 표시
            formsPlot1.Plot.RenderManager.RenderStarting += (s1, e1) =>
            {
                ScottPlot.Tick[] ticks = formsPlot1.Plot.Axes.Bottom.TickGenerator.Ticks;
                for (int i = 0; i < ticks.Length; i++)
                {
                    DateTime dt = DateTime.FromOADate(ticks[i].Position);
                    string label = $"{dt:yy-MM-dd\nHH:mm:ss}";
                    ticks[i] = new ScottPlot.Tick(ticks[i].Position, label);
                }
            };

            // the Style object contains helper methods to style many items at once
            formsPlot1.Plot.Axes.Color(ScottPlot.Color.FromHex("#a0acb5"));
            formsPlot1.Refresh();

            if (RecordCount == 0)
            {
                string msg = $"[{sttdate.ToString("yyyy-MM-dd")} ~ {enddate.ToString("yyyy-MM-dd")}] : 데이터가 존재하지 않습니다.";
                PUB.log.Add(msg);
                UTIL.MsgE(msg);
                return;
            }

        }

        private void ParseChDataToChart(int idx, ChartListData chlist, DocumentElement.CHANNELDataTable DTCHList)
        { // 지정한 날짜구간 내의 파일들을 읽음 (채널데이터 → 차트데이터)

            //데이터가 없는 경우 빠져 나감
            if (chlist.ChannelList.Any() == false || chlist.FileList.Any() == false)
            {
                if (chlist.ChannelList.Any() || chlist.FileList.Any())
                    PUB.log.Add($"Load Skip Ch:{chlist.ChannelList.Count},file:{chlist.FileList.Count}");
                return;
            }

            //지정된 기간의 데이터를 취해야 하므로 날짜 정보를 가져온다.
            long SDtime = PUB.TREND.graph_time_start.ToFileTime();//  SttDate.ToFileTime();
            long EDtime = PUB.TREND.graph_time_end.ToFileTime();// EndDate.ToFileTime();

            //속해있는채널정보를 가져온다.

            //대상 파일 전체를 확인하여, 지정된 기간내라면 자료를 수집힌다.
            foreach (string file in chlist.FileList)
            {
                ////이파일명이 로딩되었다면 불러오지 않는다.
                if (LoadFileList.Contains(file) == true) continue;// TrendCtrl1.Files.IndexOf(file) == -1)
                else LoadFileList.Add(file); //불러온기록에 추가


                ////해당파일의 지정시간내의 데이터를 모두 읽어온다.
                using (var FS = new System.IO.FileStream(file, System.IO.FileMode.Open))
                using (var SR = new System.IO.StreamReader(FS, System.Text.Encoding.Default))
                    while (SR.Peek() != 0)
                    {
                        string Line = SR.ReadLine();
                        if (Line == null) break; //null값이 오는 경우가 있다, 오류 혹은 파일끝에서 발생되었음, 더이상 처리하지 않는다.
                        if (Line.Trim() == "") continue;  //빈 줄은 무시한다.

                        string[] buf = Line.Split('\t'); //각 데이터는 탭으로 분리 되어있다.
                        if (buf.Length < 8 || buf[1].ToUpper() == "TIME") continue; //제목줄 처리하지 않는다.

                        if (long.TryParse(buf[1], out long TIme) == false) continue;  //1번항목은 시간이고, 그 이후에 채널 데이터가 들어있다.

                        ////속해있는 채널정보를 모두 가져온다.
                        foreach (var c in chlist.ChannelList) ////채널목록을 이용하여 데이터를 가져온다.
                        {
                            //채널정보 확인
                            string GRP_NAME, GRP_TITLE;
                            Color GRP_COLOR;
                            if (LoadCHList.Contains(c) == false)
                            {
                                var CHDataRow = DTCHList.Where(t => t.IDX == c).FirstOrDefault();
                                if (CHDataRow == null) continue; //데이터가없다면 처리하지 않는다.
                                else LoadCHList.Add(c);
                                GRP_NAME = CHDataRow.GRPNAME;
                                GRP_TITLE = CHDataRow.TITLE;
                                GRP_COLOR = Color.FromArgb(CHDataRow.COLOR);
                            }
                            else
                            {
                                GRP_NAME = "";
                                GRP_TITLE = "";
                                GRP_COLOR = Color.Black;
                            }

                            //각 파일당 160개씩 저장되어있으므로 채널번호를 통해서 파일의 위치를 파악해야한다.
                            var chIndexFromFile = PUB.GetChannelIndexFromFileBuffer(c);

                            //숫자값에 문제가 있다면 처리하지 않는다.(0번버퍼는 항상 blank 이기 때문에 인덱스+1해야 데이터인덱스가 맞다)
                            if ((chIndexFromFile + 1) >= buf.Length || float.TryParse(buf[chIndexFromFile + 1], out float valueS) == false) continue;

                            //Decpos 데이터가 있다면 적용한다
                            if (PUB.CONFIG.datadiv != 0 && PUB.CONFIG.datadiv != 1)
                                valueS = (valueS / PUB.CONFIG.datadiv);

                            //데이터추가
                            //TrendCtrl1.AddData(TIme, c, valueS, GRP_NAME, GRP_TITLE, GRP_COLOR);
                            if (c > myPlotDataX.GetUpperBound(0))
                            {
                                Array.Resize(ref myPlotDataX, c);
                                Array.Resize(ref myPlotDataY, c);

                            }
                            if (myPlotDataX[c - 1] == null)
                            {
                                myPlotDataX[c - 1] = new List<DateTime>();
                                myPlotDataY[c - 1] = new List<float>();
                            }
                            myPlotDataX[c - 1].Add(DateTime.FromFileTime(TIme));
                            myPlotDataY[c - 1].Add(valueS);

                            RecordCount += 1;
                        }
                    }

            }
        }

        public void LinkLabel5_LinkClicked(System.Object sender, System.Windows.Forms.LinkLabelLinkClickedEventArgs e)
        { // 신규그룹생성
            addnewGroup();
        }
        private void SaveViewGroup()
        {
            string[] BackTables = new string[] { "VIEWGROUP" };
            foreach (string tabname in BackTables)
            {
                PUB.DS.Tables[tabname].AcceptChanges();
                var file = System.IO.Path.Combine(PUB.CONFIG.GetDatabasePath(), "Database", "Config", $"{tabname}.xml");
                var fi = new System.IO.FileInfo(file);
                if (fi.Directory.Exists == false) fi.Directory.Create();
                PUB.DS.Tables[tabname].WriteXml(fi.FullName, XmlWriteMode.IgnoreSchema);
            }
        }
        private void addnewGroup()
        {
            ////전체채널목록을 생성한다.
            var group_name = this.cmb_group.Text;
            var drGRP = PUB.DS.GRP.Where(t => t.TITLE.Equals(group_name)).FirstOrDefault();
            if (drGRP == null) return;

            //그룹에 속한 채널 데이터
            var chlist = PUB.DS.CHANNEL.Where(t => t.GIDX == drGRP.IDX).OrderBy(t => t.IDX);
            
            //var chlistdata = chlist.Where(t => t.ENABLE == 1).Select(t => t.IDX).ToList();
            //var chinfos = chlistdata.Select(t => new CChinfo { Show = false, TITLE = $"#{t:0000}", Idx = (ushort)t }).ToList();
            /* 작성자: 이재웅, 작성일: 2024-12-04, 작성내용: 채널선택 화면에 변경된 형식의 채널명 표시를 위해 [ex. #A001] */
            var chlistdata = chlist.Where(t => t.ENABLE == 1).Select(t => new { t.IDX, t.TITLE }).ToList();
            var chinfos = chlistdata.Select(t => new CChinfo { Show = false, TITLE = $"{t.TITLE}", Idx = (ushort)t.IDX}).ToList();

            List<int> chnolist = new List<int>();
            using (var f = new Frm_SelectCH(chinfos.ToArray()))
            if (f.ShowDialog() == DialogResult.OK)
            {
                foreach (ListViewItem item in f.CheckedListBox1.CheckedItems)
                    chnolist.Add(int.Parse(item.Tag.ToString()));
            }

            //선택된 채널 갯수 확인
            if (chnolist.Any() == false || chnolist.Count > PUB.CONFIG.MaxChCount)
            {
                UTIL.MsgE("채널 선택은 (최소 1개 부터 {PUB.CONFIG.MaxChCount}개)까지 가능합니다");
                return;
            }

            //최종저장 그룹명
            /* 작성자: 이재웅, 작성일: 2024-12-04, 작성내용: 전해조그룹 선택시 채널순으로 Sorting 되도록 형식 변경 [ex. 0001~1600] */
            var name = UTIL.InputBox($"그룹명을 입력하세요\n\n그룹생성({chnolist.Count}건)", $"GRP({chnolist.Min().ToString("D4")}...{chnolist.Max().ToString("D4")})");
            if (name.Item1 == false || name.Item2.isEmpty()) return;
            
            group_name = name.Item2.Replace("'", "");
            var value = string.Join(",", chnolist);
            short rlt_maxid = PUBC.AddviewGroup(group_name, value, this.cmb_group.Text);
            if (rlt_maxid != -1)
            {
                AddGroupRow("[사용자] " + group_name, rlt_maxid, value, this.cmb_group.Text);
                SaveViewGroup();
            }
        }
        public void LinkLabel4_LinkClicked(System.Object sender, System.Windows.Forms.LinkLabelLinkClickedEventArgs e)
        { // 선택그룹삭제
            DelteGroup();
        }
        private void DelteGroup()
        {
            var drv = this.bs_grp.Current as DataRowView;
            if (drv == null) return;
            var dr = drv.Row as DS1.groupRow;

            if (dr.cname.IndexOf("사용자") == -1)
            {
                UTIL.MsgE("사용자 그룹만 삭제가 가능합니다");
                return;
            }

            if (UTIL.MsgQ($"선택된 사용자 그룹을 삭제하시겠습니까?\n\n{dr.cname}") != DialogResult.Yes) return;

            if (PUBC.DeleteViewGroup(dr.idx, this.cmb_group.Text))
            {
                UTIL.MsgI("데이터가 삭제되었습니다");
            }
            else
            {
                UTIL.MsgI("삭제가 실패되었습니다");
            }
            drv.Delete();
            dS1.group.AcceptChanges();
            SaveViewGroup();
        }

        /// <summary>
        /// 우측 그리드뷰에 표시되는 커서 값을 표시한다．
        /// </summary>
        private void UpdateCursorValue()
        {
            //미사용채널데이터의 커서값 삭제
            this.dS1.channel.Where(t => t.IsuseNull() == false && t.use == false).ToList().ForEach(t =>
            {
                t.c1 = t.c2 = "";
            });

            UpdateCursorValueData(0);
            UpdateCursorValueData(1);

            dv_chlist.AutoResizeColumns();
        }

        void UpdateCursorValueData(int idx)
        {
            var c1 = this.CursorLine[idx];
            if (c1 != null)
            {
                var time = DateTime.FromOADate(c1.X);
                foreach (DS1.channelRow dr in this.dS1.channel.Rows)
                {
                    var chno = dr.idx;

                    //그래프 플롯이 준비되어있지 않는 경우
                    if (chno > myPlotDataX.Length || this.myPlots[chno - 1] == null)
                    {
                        if (idx == 0) dr.c1 = "ERR";
                        else dr.c2 = "ERR";
                        continue;
                    }

                    var plot = this.myPlots[chno - 1];
                    var times = this.myPlotDataX[chno - 1];

                    //해당 데이터 포인트에 값이 있다.
                    var samedate = times.Where(t => t == time).FirstOrDefault();
                    if (samedate != default(DateTime))
                    {
                        var index = times.IndexOf(samedate);
                        var volt1 = myPlotDataY[chno][index];
                        if (idx == 0) dr.c1 = $"{volt1:#0.00}";
                        else dr.c2 = $"{volt1:#0.00}"; ;
                        continue;
                    }

                    //가장자리 데이터 확인
                    var leftdata = times.Where(t => t < time).LastOrDefault();
                    var rightdata = times.Where(t => t > time).FirstOrDefault();
                    if (leftdata == null || rightdata == null)
                    {
                        if (idx == 0) dr.c1 = "ERR";
                        else dr.c2 = "ERR";
                        continue;
                    }

                    //시간내의 데이터를 찾는다
                    var indexL = times.IndexOf(leftdata);
                    var indexR = times.IndexOf(rightdata);
                    if (indexL == -1 || indexR == -1)
                    {
                        if (idx == 0) dr.c1 = $"ERR";
                        else dr.c2 = $"ERR";
                        continue;
                    }

                    var voltL = myPlotDataY[chno - 1][indexL];
                    var voltR = myPlotDataY[chno - 1][indexR];
                    /* 작성자: 이재웅, 작성일: 2024-09-26, 작성내용: volt 차이값 수식 변경 */
                    // [이전내용] :  var diffvolt = Math.Abs(voltL - voltR);
                    var diffvolt = (voltR - voltL);
                    if (diffvolt == 0)  //Y축 변화가 없다면 그 중간의 데이터는 동일하다
                    {
                        if (idx == 0) dr.c1 = $"{voltL:#0.00}";
                        else dr.c2 = $"{voltL:#0.00}";
                        continue;
                    }

                    //X축이 변화량의 어디쯤에 있는지 확인
                    var XTotal = (rightdata - leftdata).TotalSeconds;
                    var xPosition = (time - leftdata).TotalSeconds / XTotal;
                    var volt = voltL + (diffvolt * xPosition);
                    if (idx == 0) dr.c1 = $"{volt:#0.00}";
                    else dr.c2 = $"{volt:#0.00}";
                }
            }
        }

        private void Ch_AllOrNothing(bool sel)
        { /* 작성자: 이재웅, 작성일: 2024-09-26, 내용: 데이터그리드 데이터 All/Nothing 함수 */

            if (this.myPlots == null || this.myPlots.Any() == false) return;     //null check
            PUB.smsg("채널정보를 변경하는중");

            this.myPlots.ToList().ForEach(t => t.IsVisible = sel);
            this.dS1.channel.ToList().ForEach(t =>
            {
                t.use = sel;
                if (sel == false) { t.c1 = t.c2 = ""; }
            });
            this.dS1.channel.AcceptChanges();
            this.formsPlot1.Refresh();

            PUB.smsg("");
        }
        private void dv_chlist_CellContentClick(object sender, DataGridViewCellEventArgs e)
        { /* 작성자: 이재웅, 작성일: 2024-09-26, 내용: 데이터그리드 데이터 행 관련 처리 */

            if (e.RowIndex < 0)
            { // 데이터그리드 Title 행을 클릭
                if (e.ColumnIndex == 0)
                { // checkbox 컬럼을 클릭
                    selALL = !selALL;
                    Ch_AllOrNothing(selALL);
                    if (selALL) UpdateCursorValue();    // 전체표시 조건일때 실행
                    return;
                }
                else
                    return;
            }
            else // 데이터그리드 Data 행의 checkbox 컬럼을 클릭
                if (e.ColumnIndex != 0) return;


            this.dv_chlist.EndEdit();
            this.dv_chlist.SuspendLayout();

            var Drv = this.bs_Channel.Current as DataRowView;
            var dr = Drv.Row as DS1.channelRow;
            if (Drv == null) return;

            //해당채널의 표시상태를 반전한다.
            var ch = dr.idx;

            var plot = this.myPlots[ch - 1];
            dr.use = plot.IsVisible = !plot.IsVisible;

            if (dr.use == false)
            {
                dr.c1 = dr.c2 = string.Empty;
            }
            dr.EndEdit();

            this.formsPlot1.Refresh();
            UpdateCursorValue();

            this.dv_chlist.ResumeLayout();
        }
        private void dv_chlist_MouseLeave(object sender, EventArgs e)
        { /* 작성자: 이재웅, 작성일: 2024-09-26, 내용: 데이터그리드를 벗어나면 Cursors.Default 처리 */
            this.Cursor = Cursors.Default;
        }
        private void dv_chlist_MouseMove(object sender, MouseEventArgs e)
        { /* 작성자: 이재웅, 작성일: 2024-09-26, 내용: 첫번째 checkbox 컬럼일때 Cursors.Hand 처리 */
            // 마우스 위치의 HitTest 호출
            DataGridView.HitTestInfo hitTestInfo = dv_chlist.HitTest(e.X, e.Y);

            // 행과 열의 인덱스를 가져옴
            //int rowIndex = hitTestInfo.RowIndex;
            int columnIndex = hitTestInfo.ColumnIndex;
            if (columnIndex == 0)
                this.Cursor = Cursors.Hand;
            else
                this.Cursor = Cursors.Default;
        }

        public void DataGridView1_CellFormatting(object sender, System.Windows.Forms.DataGridViewCellFormattingEventArgs e)
        {
            var dr = this.dS1.channel.Rows[e.RowIndex] as DS1.channelRow;
            if (dr == null) return;
            this.dv_chlist.Rows[e.RowIndex].Cells[1].Style.BackColor = Color.FromArgb(dr.cc);
            if (dr.use) this.dv_chlist.Rows[e.RowIndex].DefaultCellStyle.BackColor = Color.White;
            else this.dv_chlist.Rows[e.RowIndex].DefaultCellStyle.BackColor = Color.LightGray;
        }

        public void ToolStripMenuItem1_Click(System.Object sender, System.EventArgs e)
        {
            addnewGroup();
        }
        public void ToolStripMenuItem2_Click(System.Object sender, System.EventArgs e)
        {
            DelteGroup();
        }


        public void PrintDocument1_PrintPage(System.Object sender, System.Drawing.Printing.PrintPageEventArgs e)
        {
            Graphics G = e.Graphics;

            var pagesize = new SizeF(PrintDocument1.DefaultPageSettings.PaperSize.Height, PrintDocument1.DefaultPageSettings.PaperSize.Width);

            ////리포트제목표시
            var title = "Historical Trend Display Report";
            var titlefont = new Font("나눔고딕", 30, FontStyle.Bold);
            var fontsize = G.MeasureString(title, titlefont);
            var Y = 50f;
            var X = (float)((pagesize.Width - fontsize.Width) / 2);
            G.DrawString(title, titlefont, Brushes.Black, X, Y);
            Y = Y + fontsize.Height + 70;

            int bottommargin = 50;
            var WindowRect = new RectangleF(30, Y, (float)(pagesize.Width * 0.7), pagesize.Height - Y - bottommargin);
            var ChartRect = new RectangleF(WindowRect.Left, WindowRect.Top, WindowRect.Width, WindowRect.Height);

            var 상호명 = PUB.CONFIG.sangho.Trim();
            var 상호font = new Font("나눔고딕", 18, FontStyle.Bold);
            var 상호size = G.MeasureString(상호명, 상호font);
            G.DrawString(상호명, 상호font, Brushes.Black, WindowRect.Left, WindowRect.Top - 상호size.Height - 10);

            title = "C1";
            fontsize = G.MeasureString(title, this.Font);

            var valuestr = "00.025";
            var valuefontsizew = G.MeasureString(valuestr, this.Font).Width * 1.1F;
            var valuefontsizeh = G.MeasureString(valuestr, this.Font).Height;

            var chtitle = new Rectangle(System.Convert.ToInt32(WindowRect.Left + WindowRect.Width + 20), System.Convert.ToInt32(WindowRect.Top), System.Convert.ToInt32(pagesize.Width - WindowRect.Width - 150), System.Convert.ToInt32(fontsize.Height * 2)); ////제목영역
            var ChRect = new RectangleF(chtitle.Left, chtitle.Top + chtitle.Height, chtitle.Width, pagesize.Height - (chtitle.Top + chtitle.Height) - bottommargin); ////본문영역
            var chinforect = new Rectangle(chtitle.Left, chtitle.Top, chtitle.Width, System.Convert.ToInt32(chtitle.Height + ChRect.Height)); ////총영역

            var chtitlewidth = ChRect.Width - (valuefontsizew * 2); ////채널정보를 표시하는 영역의 너비
            var chc1valueleft = chinforect.Left + chtitlewidth;
            var chc2valueleft = chc1valueleft + valuefontsizew;

            //차트영역에 이미지를 그린다.
            G.DrawRectangle(Pens.Black, ChartRect.X, ChartRect.Y, ChartRect.Width, ChartRect.Height);

            using (var img = this.formsPlot1.Plot.GetImage((int)ChartRect.Width, (int)ChartRect.Height))
                G.DrawImage(img.GetBitmap(), ChartRect.X, ChartRect.Y, ChartRect.Width, ChartRect.Height);


            var displayfont = new Font("나눔고딕", 10, FontStyle.Bold);
            var displayfont2 = new Font("나눔고딕", 10);
            ////채널정보칸 표시 (바로 이미지를 복제한다?) 제목표시
            G.DrawRectangle(Pens.Gray, chtitle);
            G.DrawString("Cell", displayfont, Brushes.Black, chtitle.Left + 5, (float)(chtitle.Top + (chtitle.Height - valuefontsizeh) / 2));
            G.DrawString("C1", displayfont, Brushes.Black, (float)(chc1valueleft + (valuefontsizew - fontsize.Width) / 2), (float)(chtitle.Top + (chtitle.Height - valuefontsizeh) / 2));
            G.DrawString("C2", displayfont, Brushes.Black, (float)(chc2valueleft + (valuefontsizew - fontsize.Width) / 2), (float)(chtitle.Top + (chtitle.Height - valuefontsizeh) / 2));

            ////세로줄그리기
            G.DrawLine(Pens.Gray, chc1valueleft, chinforect.Top, chc1valueleft, chinforect.Top + chinforect.Height);
            G.DrawLine(Pens.Gray, chc2valueleft, chinforect.Top, chc2valueleft, chinforect.Top + chinforect.Height);
            //G.DrawRectangle(Pens.Red, chinforect)

            ////체크된 채널의 목록값을 보여준다. 그리고 c1,c2값을 보여준다.
            var fsize = G.MeasureString("#11", this.Font);
            var gridheight = ChRect.Height / 25;
            var idx = (short)0;
            Y = ChRect.Top + 10;
            X = ChRect.Left + 10;

            var gridcnt = (int)(Math.Floor((decimal)(ChRect.Height / gridheight)));
            for (int i = 0; i <= gridcnt + 1; i++)
            {
                float newy = ChRect.Top + (i * gridheight);
                G.DrawLine(Pens.Gray, ChRect.Left, newy, ChRect.Left + ChRect.Width, newy);
            }

            foreach (var ch in this.dS1.channel.Where(t => t.use))
            {
                fsize = G.MeasureString(ch.cname, displayfont2);
                Y = (float)(ChRect.Top + (idx * gridheight) + ((gridheight - fsize.Height) / 2));

                ////지정역역의 색상을 표시함
                RectangleF ColorRect = new RectangleF(X - 5, Y, chc1valueleft - X - 5, fsize.Height);
                G.FillRectangle(new SolidBrush(Color.FromArgb(ch.cc)), ColorRect);
                G.DrawString(ch.cname, displayfont2, Brushes.Black, X, Y);
                G.DrawString(ch.c1, displayfont2, Brushes.Black, chc1valueleft + 5, Y);
                G.DrawString(ch.c2, displayfont2, Brushes.Black, chc2valueleft + 5, Y);
                idx++;
            }
            ChRect.Height = gridheight * (gridcnt + 0);
            G.DrawRectangle(Pens.Gray, chinforect.Left, chinforect.Top, chinforect.Width, chinforect.Height);

            displayfont2.Dispose();
            displayfont.Dispose();

            G.Dispose();
        }
        public void Button1_Click(System.Object sender, System.EventArgs e)
        {
            this.PrintDocument1.DefaultPageSettings.Landscape = true;
            try
            {
                this.PrintPreviewDialog1.ShowDialog();
            }
            catch (Exception ex) { UTIL.MsgE($"인쇄실패\n\n{ex.Message}"); }
        }

        public void DataGridView2_CellDoubleClick(object sender, DataGridViewCellEventArgs e)
        { // 불러오기
            if (this.bs_grp.Current == null)
            {
                UTIL.MsgE("선택된 그룹이 없습니다");
                return;
            }
            bt_run.PerformClick();
        }
        public void ToolStripMenuItem3_Click(System.Object sender, System.EventArgs e)
        {
            var drv = this.bs_grp.Current as DataRowView;
            if (drv == null) return;
            var dr = drv.Row as DS1.groupRow;
            MessageBox.Show($"Channel List\n\n{dr.value}");
        }
        public void bt_run_Click(System.Object sender, System.EventArgs e)
        {
            PUB.smsg("Loading");
            var bt = sender as Button;
            bt.Enabled = false;

            //GroupName = "";
            foreach (var ch in this.ChartListData)
                ch.Clear();

            //그룹선택확인
            var DRVGRP = this.bs_grp.Current as DataRowView;
            if (DRVGRP == null) return;
            var DRGRP = DRVGRP.Row as DS1.groupRow;

            /* 주석날짜: 2024-09-20 | 주석내용: VIEWGROUP.xml 파일내용 | 주석자: 이재웅
             * 
             * <VIEWGROUUP>
             *   <IDX>9</IDX>
             *   <GNAME>EL2500A</GNAME>
             *   <TITLE>GRP(1...10)</TITLE>
             *   <VAL>1,2,3,4,5,6,7,8,9,10</VAL>
             * </VIEWGROUUP>
             * 
             **************************************
             *
             * DRGRP.idx = 9
             * DRGRP.mame = "EL2500A"
             * DRGRP.cname = "[사용자] GRP(1...10)"
             * DRGRP.value = "1,2,3,4,5,6,7,8,9,10"
             */

            var CHLIST = DRGRP.value.Trim();// ["value"].ToString().Trim(); ////표시채널목록
            var CHArray = CHLIST.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);

            //채널번호를 채널그룹에 맞게 분리작업을 합니다.
            //채널데이터는 160개를 기준으로 1~10까지 분리되어 저장됩니다.
            //채널번호에 맞는 기준의 번호를 찾아야 합니다.
            foreach (var C in CHArray)
            {
                if (C.isEmpty() || C.IsNumeric() == false) continue;

                var ch = int.Parse(C);
                var ValueIdx = ch;// int.Parse(C);
                int quotient = ValueIdx / 160; // 몫
                int remainder = ValueIdx % 160; // 나머지
                if (remainder == 0) ValueIdx = quotient - 1;
                else ValueIdx = quotient;

                if (this.ChartListData[ValueIdx].ChannelList.Contains(ch) == false)
                    this.ChartListData[ValueIdx].ChannelList.Add(ch);
            }

            //우측채널목록갱신(한번만 하면되므로 채널정보가 바뀌었을때만 처리한다.)
            if (CHLIST != SelectedGRPChValue)
            {
                bt_cursor1.BackColor = Color.Empty;
                bt_cursor2.BackColor = Color.Empty;

                RefreshChannelList();
                SelectedGRPChValue = CHLIST;
            }

            using (var fd = new Frm_GraphSetup())
                if (fd.ShowDialog() == DialogResult.OK) ShowData();

            ////UpdateChannelInfo()
            bt.Enabled = true;
            PUB.smsg("");
        }

        public void Button2_Click_2(object sender, EventArgs e)
        {
            using (var f = new Frm_GraphSetup())
            {
                if (f.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                    ShowData(true);
            }
        }

        public void cmb_group_SelectedIndexChanged(System.Object sender, System.EventArgs e)
        {
            if (cmb_group.SelectedIndex < 0) return;

            //기존에 선택된 채널의 활성화를 모두 해제 한다.
            this.dS1.channel.ToList().ForEach(t => t.use = false);
            this.ChartListData.ToList().ForEach(t => t.Clear());

            SelectedGRPChValue = "";
            RefreshChannelList();

            string title = cmb_group.Text;
            this.bs_grp.Filter = "rname='" + title + "'";
            this.formsPlot1.Plot.Clear();
            this.formsPlot1.Refresh();
        }
        public void Button1_Click_1(System.Object sender, System.EventArgs e)
        {
            using (var sd = new SaveFileDialog())
            {
                sd.Filter = "png file|*.png";
                sd.FilterIndex = 0;
                sd.FileName = $"GraphCapture_{DateTime.Now.ToString("yyyyMMddHHmmssfff")}.png";
                sd.RestoreDirectory = true;
                if (sd.ShowDialog() == DialogResult.OK)
                {
                    var fn = sd.FileName;
                    this.formsPlot1.Plot.SavePng(fn, formsPlot1.Width, formsPlot1.Height);
                    var dlg = UTIL.MsgQ("생성된 파일을 확인할까요?");
                    if (dlg == DialogResult.Yes) UTIL.RunExplorer(fn);
                }
            }
        }
        public void ToolStripButton1_Click_1(object sender, EventArgs e)
        {
            string dir = PUB.CONFIG.GetDatabasePath() + "\\DataBase\\volt";
            UTIL.RunExplorer(dir);//.Shell("explorer " + dir, AppWinStyle.NormalFocus);
        }

        public void bt_cursor_Click(object sender, EventArgs e)
        {
            var bt = sender as Button;
            var idx = int.Parse(bt.Tag.ToString());
            CreateCursor(idx);
        }
        void CreateCursor(int idx)
        {
            if (this.myPlots == null) return;   /* 작성자: 이재웅, 작성일: 2024-09-25, 내용: 'myPlots == null' 조건일때 오류발생 */
            var plot = this.myPlots.Where(t => t != null).FirstOrDefault();
            if (plot == null) return;

            //화면에 보이는 영역의 데이터를 확인
            var viewx = plot.Axes.XAxis;
            var viewy = plot.Axes.YAxis;
            var xmin = viewx.Min;
            var xmax = viewx.Max;
            var datestep = (xmax - xmin) * 0.33f;

            //기존에 등록된 것이 있다면 그것을 제거하고 다시 추가한다.
            if (CursorLine[idx] != null) formsPlot1.Plot.Remove(CursorLine[idx]);

            var timedata = xmin + datestep * (idx + 1);
            var timeval = DateTime.FromOADate(timedata);
            var axLine = formsPlot1.Plot.Add.VerticalLine(timedata);
            axLine.Text = $"[C{idx + 1}] {timeval:yy-MM-dd\nHH:mm:ss}";
            axLine.LabelAlignment = ScottPlot.Alignment.UpperRight;
            axLine.IsDraggable = true;
            this.CursorLine[idx] = axLine;

            this.formsPlot1.Refresh();
            UpdateCursorValue();
        }

        // DataGridView의 모든 행의 column 데이터 지우기
        private void ClearColumnData(DataGridView dataGridView, int idx)
        {
            // DataGridView의 모든 행을 반복
            foreach (DataGridViewRow row in dataGridView.Rows)
            {
                // 해당 열의 데이터 지우기
                if (!row.IsNewRow) // 새 행이 아닌 경우에만
                {
                    row.Cells[idx + 2].Value = null; // 또는 string.Empty로 설정 가능
                }
            }
        }

        private void bt_Hide_Click(object sender, EventArgs e)
        {
            //기존에 등록된 것이 있다면 그것을 제거한다.
            if (CursorLine[0] != null)
            {
                formsPlot1.Plot.Remove(CursorLine[0]);
                CursorLine[0] = null;
                ClearColumnData(dv_chlist, 0);
            }
            if (CursorLine[1] != null)
            {
                formsPlot1.Plot.Remove(CursorLine[1]);
                CursorLine[1] = null;
                ClearColumnData(dv_chlist, 1);
            }
            this.formsPlot1.Refresh();
        }

        private void btConfig_Click(object sender, EventArgs e)
        {
            if (UTIL.ShowPropertyDialog(PUB.TREND) == DialogResult.OK)
            {
                PUB.TREND.Save();

                formsPlot1.Plot.Axes.Bottom.MinimumSize = PUB.TREND.graph_bottom_minsize;
                if (PUB.TREND.y_scale_auto)
                    formsPlot1.Plot.Axes.AutoScaleY();
                else
                    formsPlot1.Plot.Axes.SetLimitsY(PUB.TREND.graph_y_start, PUB.TREND.graph_y_end);

                formsPlot1.Refresh();
            }
        }

    }
}
