using COMM;
using System.Net.NetworkInformation;
using System.Net;
using System.Management;
using System.Data;
using AR;
using System.Media;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System;
using System.IO;
using Newtonsoft.Json;

#if SPEECH
using Microsoft.Speech.Synthesis;
#endif

using System.Threading.Tasks;
using System.Data.SqlClient;
using System.Linq;
using AGVNavigationCore.Models;
using AGVNavigationCore.Controls;
using System.Collections.Generic;
using System.Drawing;

namespace Project
{

    public static class PUB
    {
        //public static Device.CFlag flag;
        public static bool bShutdown = false;
        public static bool Automodeonreboot = false;
        public static bool AutRebootAlreay = false;
        public static bool DriveSpeed = false;
        public static AGVNavigationCore.Controls.UnifiedAGVCanvas _mapCanvas;
        //public static List<MapNode> _mapNodes;

        /// <summary>
        /// 다음 작업 명령 (PickOn/PickOff)
        /// </summary>
        public static ENIGProtocol.AGVCommandHE NextWorkCmd = ENIGProtocol.AGVCommandHE.Stop;

        /// <summary>
        /// 가상 AGV (시뮬레이션용)
        /// </summary>
        public static VirtualAGV _virtualAGV;

        #region "Hardware"

        /// <summary>
        /// 읽기/쓰기용이며 구동모터 와 비상정지가 연결됨(USB-ATMEGA 2560)
        /// </summary>
        //public static arDev.FakePLC PLC;

        ///// <summary>
        ///// 읽기전용이며 Z축 모터와 외부 버튼이 연결됨(USB-ATMEGA 2560)
        ///// </summary>
        //public static Device.PLC2 plcS;

        /// <summary>
        /// XBEE 통신(USB-TTL)
        /// </summary>
        public static Device.Xbee XBE;

        /// <summary>
        /// 배터리 잔량 확인(COM2  : RS232C - TTL)
        /// </summary>
        // public static arDevice.BMS bms;

        /// <summary>
        /// RFID READER (COM1 : RS232C)
        /// </summary>
        public static arDev.Narumi AGV;

        public static arDev.BMS BMS;

        #endregion

        /// <summary>
        /// 디버그모니터용 소켓(데이터를 전송만 한다)
        /// </summary>
        public static Device.Socket sock_debug;
#if SPEECH
        private static SpeechSynthesizer voice;
#endif
        public static SoundPlayer mplayer;

        [DllImport("winmm.dll")]
        private static extern int waveOutSetVolume(IntPtr hwo, uint dwVolume);

        public static void SetVolume(int volume)
        {
            // volume은 0~100 사이의 값
            uint vol = (uint)((volume / 100.0) * 0xFFFF);
            waveOutSetVolume(IntPtr.Zero, vol);
            setting.musicvol = volume;
        }

        public static bool CheckPassword()
        {
            var f = new Dialog.fPassword();
            if (f.ShowDialog() != System.Windows.Forms.DialogResult.OK) return false;
            var pass = f.tbInput.Text.Trim();
            var passok = DateTime.Now.ToString("ddMM");
            if (pass.Equals(passok)) return true;
            else return false;
        }

        /// <summary>
        /// 스피커를 통한 음성을 출력합니다
        /// 출력되는 음성은 기본로그에도 자동 포함됩니다
        /// </summary>
        /// <param name="m"></param>
        /// <param name="force"></param>
        /// <param name="addlog">로그에도 출력 합니다</param>
        public static void Speak(string m, Boolean force = false, bool addlog = true, string logcate = "")
        {
            if (force == false && PUB.setting.Enable_Speak == false)
            {
                Console.WriteLine("speech disabled");
                return;
            }
#if SPEECH
            if (force)
                voice.SpeakAsyncCancelAll();
            if (voice.State == SynthesizerState.Ready)
                voice.SpeakAsync(m);
            if (addlog)
            {
                if (logcate.isEmpty()) logcate = "SPEAK";
                PUB.log.Add(logcate, m);
            }
#else
            PUB.log.Add($"스피치컴파일상수OFF");
#endif
        }

        /// <summary>
        /// 콜을 받을 수 있는 상황인가?
        /// </summary>
        /// <returns></returns>
        public static bool IsCanCALL()
        {
            //콜가능여부도 전송한다
            if (VAR.BOOL[eVarBool.FLAG_AUTORUN] &&
                VAR.BOOL[eVarBool.FLAG_CHARGEONM] == false &&
                PUB.BMS.BMSInformation.rsoc > PUB.setting.ChargeEmergencyLevel &&
                PUB.sm.RunStep != StateMachine.ERunStep.BUFFER_OUT &&
                VAR.BOOL[eVarBool.WAIT_COVER_DOWN] == false &&
                VAR.BOOL[eVarBool.WAIT_COVER_UP] == false)
            {
                return true;
            }
            else return false;
        }

        public static CounterSetting counter;

        /// <summary>
        /// popup message window
        /// </summary>
        public static MessageWindow popup;

        /// <summary>
        /// database manager
        /// </summary>
        public static Manager.DatabaseManager dbm;

        /// <summary>
        /// 설정정보
        /// </summary>
        public static CSetting setting;

        /// <summary>
        /// 시스템로그
        /// </summary>
        public static AR.Log log, logagv, logplc, logbms, logxbee,logdebug;

        public static Boolean bPlayMusic = false;


        /// <summary>
        /// 사용자 인풋 감지 시간
        /// </summary>
        public static DateTime LastInputTime = DateTime.Now;

        public static CResult Result;

        public static string PatchVersion { get; set; }

        /// <summary>
        /// 상태머신
        /// </summary>
        public static StateMachine.StateMachine sm;    //상태머신분리 190529

        public static System.IO.DirectoryInfo path;


        public static void initCore()
        {
            //setting
            setting = new CSetting();
            setting.Load();

            //counter setting
            counter = new CounterSetting();
            counter.Load();

            //log
            log = new AR.Log();
            log.Textenc = System.Text.Encoding.UTF8;

            logagv = new AR.Log();
            logagv.Textenc = System.Text.Encoding.UTF8;

            logplc = new AR.Log();
            logplc.Textenc = System.Text.Encoding.UTF8;

            logbms = new AR.Log();
            logbms.Textenc = System.Text.Encoding.UTF8;

            logxbee = new AR.Log();
            logxbee.Textenc = System.Text.Encoding.UTF8;

            logdebug = new Log();
            logdebug.Textenc = System.Text.Encoding.UTF8;

            logagv.FileNameFormat = "{yyyyMMdd}_agv";
            logplc.FileNameFormat = "{yyyyMMdd}_plc";
            logbms.FileNameFormat = "{yyyyMMdd}_bms";
            logxbee.FileNameFormat = "{yyyyMMdd}_cal";
            logdebug.FileNameFormat = "{yyyyMMdd}_dgb";

            //popupmessage
            popup = new MessageWindow();

            Result = new CResult();
            Result.Clear();
        }

        public static void init()
        {

            Result = new CResult();

            //state machine
            sm = new StateMachine.StateMachine();

            path = new System.IO.DirectoryInfo(UTIL.CurrentPath);



            mplayer = new SoundPlayer();
            if (PUB.setting.musicfile.isEmpty() == false)
                if (System.IO.File.Exists(PUB.setting.musicfile))
                {
                    PUB.mplayer.SoundLocation = PUB.setting.musicfile;
                    SetVolume(PUB.setting.musicvol);
                }
#if SPEECH
            voice = new SpeechSynthesizer();
            try
            {
                // 한국어 음성을 찾아서 설정
                var koreanVoice = voice.GetInstalledVoices()
                    .Where(v => v.VoiceInfo.Culture.Name.StartsWith("ko"))
                    .FirstOrDefault();

                if (koreanVoice != null)
                {
                    voice.SelectVoice(koreanVoice.VoiceInfo.Name);
                }
                else
                {
                    // 한국어 음성이 없으면 기본 음성 사용
                    var defaultVoice = voice.GetInstalledVoices().FirstOrDefault();
                    if (defaultVoice != null)
                    {
                        voice.SelectVoice(defaultVoice.VoiceInfo.Name);
                    }
                }
            }
            catch
            {
                // 음성 설정 실패 시 기본값 사용
            }
            voice.SetOutputToDefaultAudioDevice();
#endif
            var file_version = System.IO.Path.Combine(UTIL.CurrentPath, "version.txt");
            if (System.IO.File.Exists(file_version))
            {
                PUB.PatchVersion = System.IO.File.ReadAllText(file_version, System.Text.Encoding.UTF8);
            }
            else PUB.PatchVersion = string.Empty;
        }



        public static Boolean CheckManualChargeMode(bool Prompt = true)
        {
            if (VAR.BOOL[eVarBool.FLAG_CHARGEONM] == true)
            {
                string msg = "수동 충전 상태이므로 진행 할 수 없습니다";
                PUB.Speak(msg);
                if (Prompt)
                    UTIL.MsgE(msg);
                return false;
            }
            else return true;
        }

        public static double GetFreeSpace(string driveletter)
        {
            try
            {
                var di = new System.IO.DriveInfo(driveletter);
                var freespace = di.TotalFreeSpace;
                var totalspace = di.TotalSize;
                var freeSpaceRate = (freespace * 1.0 / totalspace) * 100.0;
                return freeSpaceRate;
            }
            catch
            {
                return 100.0;
            }
        }

        public static double GetFreeSpace()
        {
            double retval = 100.0;
            string savePath1 = path.FullName;
            if (savePath1 != "" && System.IO.Directory.Exists(savePath1))
            {
                //이폴더를 사용
                if (savePath1.StartsWith("\\") == false)
                {
                    //남은잔량을 체크한다.
                    retval = GetFreeSpace(savePath1.Substring(0, 1));
                    //if (freespace1 >= Pub.setting.AutoDeleteThreshold) return savePath1;
                }
            }
            return retval;
        }

        /// <summary>
        /// 장치에 오류가 있는지?
        /// </summary>
        /// <returns></returns>
        public static Boolean HasHWError()
        {
            //if (PUB.PLC.IsValid == false) return true;
            if (PUB.AGV.IsOpen == false) return true;
            if (PUB.XBE.IsOpen == false) return true;
            return false;
        }

        //public static void AddStatusSQL(StateMachine.eSMStep status, string remark, DateTime wdate)
        //{
        //    VAR.TIME[eVarTime.StatusReporttime] = DateTime.Now;
        //    try
        //    {
        //        var state = 0;
        //        if (status == StateMachine.eSMStep.RUN) state = 1;
        //        else if (status == StateMachine.eSMStep.ERROR || status == StateMachine.eSMStep.EMERGENCY) state = 2;
        //        if (string.IsNullOrEmpty(remark)) remark = status.ToString();
        //        var mcid = PUB.setting.MCID;
        //        var path = System.IO.Path.Combine(Util.CurrentPath, "Status");
        //        var file = System.IO.Path.Combine(path, $"{DateTime.Now.ToString("HHmmssfff")}_{status}.sql");
        //        var sql = "insert into MCMonitor_Rawdata(Model,status,remark,ip,mac,time) values('{0}','{1}','{2}','{3}','{4}','{5}')";
        //        sql = string.Format(sql, mcid, state, remark, IP, MAC, wdate.ToString("yyyy-MM-dd HH:mm:ss"));
        //        System.IO.File.WriteAllText(file, sql, System.Text.Encoding.Default);

        //        //만들어진지 3분이 지난 파일은 삭제한다.
        //        //var di = new System.IO.DirectoryInfo(path);
        //        //var fi = di.GetFiles("*.sql", System.IO.SearchOption.TopDirectoryOnly).Where(t => t.LastWriteTime < DateTime.Now.AddMinutes(-3)).FirstOrDefault();
        //        //if (fi != null) fi.Delete();
        //    }
        //    catch (Exception ex)
        //    {
        //        PUB.log.AddE(ex.Message);
        //    }
        //}

        public static void AddEEDB(string remark)
        {
            var step = PUB.sm.Step.ToString();
            var rtep = PUB.sm.RunStep.ToString();
            var mcid = PUB.setting.MCID;
            var path = System.IO.Path.Combine(UTIL.CurrentPath, "Status");
            var file = System.IO.Path.Combine(path, $"{DateTime.Now.ToString("HHmmssfff")}_{step}_{rtep}.sql");
            var wdate = DateTime.Now;
            try
            {
                var sql = "insert into AGV_History(mcid,step,runstep,remark,ip,wdate) values('{0}','{1}','{2}','{3}','{4}','{5}')";
                sql = string.Format(sql, mcid, step, rtep, remark, IP, wdate.ToString("yyyy-MM-dd HH:mm:ss"));
                System.IO.File.WriteAllText(file, sql, System.Text.Encoding.Default);

                //만들어진지 3분이 지난 파일은 삭제한다.
                //var di = new System.IO.DirectoryInfo(path);
                //var fi = di.GetFiles("*.sql", System.IO.SearchOption.TopDirectoryOnly).Where(t => t.LastWriteTime < DateTime.Now.AddMinutes(-3)).FirstOrDefault();
                //if (fi != null) fi.Delete();
            }
            catch (Exception ex)
            {
                PUB.log.AddE(ex.Message);
            }
        }

        public static string GetResultCodeMessage(eResult err)
        {
            return err.ToString().ToUpper();
        }


        public static string GetErrorMessage(eECode err, params object[] values)
        {
            switch (err)
            {
                case eECode.NOTALLOWUP:
                    return "상차 허용 위치가 아닙니다";
                case eECode.AGVCONN:
                    return Lang.AGV연결실패;
                case eECode.PLCCONN:
                    return Lang.PLC통신실패;
                case eECode.MESSAGE_ERROR:
                    if (values.Length > 0) return values[0].ToString();
                    return "Message Error";

                default:
                    return err.ToString();
            }
        }
        public static string IP { get; set; }
        public static string MAC { get; set; }

        /// <summary>
        /// 프로그램 사용기록 추가
        /// </summary>
        /// <param name="prgmName"></param>
        /// <param name="develop"></param>
        /// <param name="prgmVersion"></param>
        public static void CheckNRegister5(string prgmName, string develop, string prgmVersion,string mcid, string remark)
        {
            if (System.Diagnostics.Debugger.IsAttached) mcid += $"*";
            if (prgmName.Length > 50) prgmName = prgmName.Substring(0, 50); //길이제한
            var task = Task.Factory.StartNew(() =>
            {
                try
                {
                    string ip = "";
                    string mac = "";
                    //      string prgmName = Application.ProductName;

                    var nif = NetworkInterface.GetAllNetworkInterfaces();
                    var host = Dns.GetHostEntry(Dns.GetHostName());
                    string fullname = System.Net.Dns.GetHostEntry("").HostName;
                    foreach (IPAddress r in host.AddressList)
                    {
                        string str = r.ToString();

                        if (str != "" && str.Substring(0, 3) == "10.")
                        {
                            ip = str;
                            break;
                        }
                    }

                    string rtn = string.Empty;
                    ObjectQuery oq = new System.Management.ObjectQuery("SELECT * FROM Win32_NetworkAdapterConfiguration WHERE IPEnabled='TRUE'");
                    ManagementObjectSearcher query1 = new ManagementObjectSearcher(oq);
                    foreach (ManagementObject mo in query1.Get())
                    {
                        string[] address = (string[])mo["IPAddress"];
                        if (address[0] == ip && mo["MACAddress"] != null)
                        {
                            mac = mo["MACAddress"].ToString();
                            break;
                        }
                    }

                    if (ip == "" || mac == "") return;

                    var conn = new SqlConnection(AGV4.Properties.Settings.Default.CS);// "Data Source=10.131.15.18;Initial Catalog=EE;Persist Security Info=True;User ID=eeuser;Password=Amkor123!");
                    conn.Open();
                    string ProcName = "AddPrgmUser5";
                    var cmd = new SqlCommand(ProcName, conn)
                    {
                        CommandType = CommandType.StoredProcedure
                    };

                    SqlParameter param = cmd.Parameters.Add("@mac", SqlDbType.NVarChar, 50);
                    param.Value = mac;

                    param = cmd.Parameters.Add("@ip", SqlDbType.NVarChar, 50);
                    param.Value = ip;

                    param = cmd.Parameters.Add("@pgrm", SqlDbType.NVarChar, 50);
                    param.Value = prgmName;

                    param = cmd.Parameters.Add("@develop", SqlDbType.NVarChar, 50);
                    param.Value = develop;

                    param = cmd.Parameters.Add("@pgver", SqlDbType.NVarChar, 50);
                    param.Value = prgmVersion;

                    param = cmd.Parameters.Add("@prgmLogin", SqlDbType.VarChar, 20);
                    param.Value = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");

                    param = cmd.Parameters.Add("@account", SqlDbType.NVarChar, 50);
                    param.Value = System.Environment.UserName;

                    param = cmd.Parameters.Add("@hostname", SqlDbType.NVarChar, 100);
                    param.Value = fullname;

                    param = cmd.Parameters.Add("@mcid", SqlDbType.VarChar, 10);
                    param.Value = mcid;

                    param = cmd.Parameters.Add("@remark", SqlDbType.VarChar, 255);
                    param.Value = remark;

                    var cnt = cmd.ExecuteNonQuery();
                    conn.Close();

                    IP = ip;
                    MAC = mac;
                }
                catch (Exception ex)
                {
                    PUB.log.AddE(ex.Message);
                }
            });
        }
        public static string SelectSerialPort()
        {
            var f = new System.Windows.Forms.Form();
            f.WindowState = System.Windows.Forms.FormWindowState.Normal;
            f.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
            f.Size = new System.Drawing.Size(800, 400);
            f.MaximizeBox = false;
            f.MinimizeBox = false;
            f.Text = "Select Port";
            f.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
            var lst = new System.Windows.Forms.ListBox();
            lst.Font = new System.Drawing.Font("Consolas", 15, System.Drawing.FontStyle.Bold);
            lst.Dock = System.Windows.Forms.DockStyle.Fill;
            lst.DoubleClick += (s1, e1) =>
            {
                if (lst.SelectedItem != null) f.DialogResult = System.Windows.Forms.DialogResult.OK;
            };
            using (var searcher = new ManagementObjectSearcher("SELECT * FROM WIN32_SerialPort"))
            {
                var portnames = System.IO.Ports.SerialPort.GetPortNames().OrderBy(t => t);
                var ports = searcher.Get().Cast<ManagementBaseObject>().ToList();
                foreach (var port in portnames)
                {
                    var desc = "";
                    var portInfo = ports.Where(t => t["DeviceId"].ToString() == port).FirstOrDefault();
                    if (portInfo != null) desc = portInfo["Caption"].ToString();
                    lst.Items.Add(string.Format("{0} - {1}", port, desc));
                }
            }
            f.Controls.Add(lst);
            if (f.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                var name = lst.SelectedItem.ToString().Split('-');
                return name[0].Trim();
            }
            else return string.Empty;
        }

        public static void SystemReboot(UInt16 timeout, bool setautomode = false)
        {
            System.Diagnostics.ProcessStartInfo si = new System.Diagnostics.ProcessStartInfo();
            si.FileName = @"c:\windows\system32\shutdown.exe";
            si.Arguments = "-r -t " + timeout.ToString();
            si.UseShellExecute = false;
            System.Diagnostics.Process prc = new System.Diagnostics.Process();
            prc.StartInfo = si;

            PUB.AGV.AGVMoveStop("user reboot");

            if (setautomode)
            {
                PUB.Automodeonreboot = true;
                PUB.setting.SetAutoModeOn = true;
                PUB.setting.AUtoRebootLastTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
                PUB.setting.Save();
            }


            prc.Start();

        }
        public static void SystemShutdown(UInt16 timeout)
        {
            //System.Text.StringBuilder sb = new StringBuilder();
            //sb.AppendLine("@echo off");
            //sb.AppendLine("echo System OFF After " + timeout.ToString() + "Seconds");
            //sb.AppendLine("shutdown -s -t " + timeout.ToString());
            //string bfile = AppDomain.CurrentDomain.BaseDirectory + "Shutdown.bat";
            //System.IO.File.WriteAllText(bfile, string.Format(sb.ToString(), AppDomain.CurrentDomain.BaseDirectory), System.Text.Encoding.Default);

            System.Diagnostics.ProcessStartInfo si = new System.Diagnostics.ProcessStartInfo();
            si.FileName = @"c:\windows\system32\shutdown.exe";
            si.Arguments = "-s -t " + timeout.ToString();
            si.UseShellExecute = false;
            System.Diagnostics.Process prc = new System.Diagnostics.Process();
            prc.StartInfo = si;
            prc.Start();
        }

        public static void ChangeUIPopup(System.Windows.Forms.NumericUpDown valueCtl)
        {
            var value = valueCtl.Value.ToString();
            var f = UTIL.InputBox("input", value);// new Dialog.fInput(value);
            if (f.Item1)
            {
                var val = decimal.Parse(f.Item2);
                if (val < valueCtl.Minimum)
                {
                    UTIL.MsgE(string.Format("최소 입력값은 {0} 입니다.", valueCtl.Minimum));
                    val = valueCtl.Minimum;
                }
                if (val > valueCtl.Maximum)
                {
                    UTIL.MsgE(string.Format("최대 입력값은 {0} 입니다.", valueCtl.Maximum));
                    val = valueCtl.Maximum;
                }
                valueCtl.Value = val;
            }

        }

        #region VirtualAGV 실제 데이터 동기화
        public static MapNode FindByNodeID(string nodeidx)
        {
            var _mapNodes = PUB._mapCanvas.Nodes;
            if (_mapNodes == null || _mapNodes.Any() == false) return null;
            if (nodeidx.isEmpty()) return null;
            return _mapNodes.Where(t => t.Id.Equals(nodeidx)).FirstOrDefault();
        }
        public static MapNode FindByRFID(string rfidValue)
        {
            var _mapNodes = PUB._mapCanvas.Nodes;
            if (_mapNodes == null || _mapNodes.Any() == false) return null;
            if (rfidValue.isEmpty()) return null;
            return _mapNodes.Where(t => t.RfidId.Equals(rfidValue)).FirstOrDefault();
        }



        /// <summary>
        /// 모든 로그를 플러시 합니다.
        /// </summary>
        public static void LogFlush()
        {
            PUB.log.Flush();
            PUB.logagv.Flush();
            PUB.logplc.Flush();
            PUB.logbms.Flush();
            PUB.logxbee.Flush();
            PUB.logdebug.Flush();
        }

        /// <summary>
        /// AGV 방향 업데이트
        /// </summary>
        /// <param name="direction">새로운 방향</param>
        public static void UpdateAGVDirection(AgvDirection direction)
        {

            if (_virtualAGV == null) return;
            if (_virtualAGV.CurrentDirection != direction)
            {
                PUB.log.Add($"[PUB] AGV Direction Change {_virtualAGV.CurrentDirection}->{direction}");
                _virtualAGV.CurrentDirection = direction;
                RefreshAGVCanvas();
            }
        }

        /// <summary>
        /// AGV 상태 업데이트
        /// </summary>
        /// <param name="state">새로운 상태</param>
        public static void UpdateAGVState(AGVState state)
        {
            if (_virtualAGV == null) return;

            if (_virtualAGV.CurrentState != state)
            {
                PUB.log.Add($"[PUB] AGV State Change {_virtualAGV.CurrentState}->{state}");
                _virtualAGV.CurrentState = state;
                RefreshAGVCanvas();
            }

        }

        /// <summary>
        /// AGV 배터리 레벨 업데이트
        /// </summary>
        /// <param name="batteryLevel">배터리 레벨 (0.0 ~ 100.0)</param>
        public static void UpdateAGVBattery(float batteryLevel)
        {
            if (_virtualAGV == null) return;

            _virtualAGV.SetBatteryLevel(batteryLevel);
            RefreshAGVCanvas();
        }

        /// <summary>
        /// 맵 캔버스 강제 갱신 (AGV 위치 표시 업데이트)
        /// </summary>
        public static void RefreshAGVCanvas()
        {
            if (_mapCanvas != null && _mapCanvas.IsHandleCreated)
            {
                _mapCanvas.Invalidate();
            }
        }


        #region "Starting Position Persistence"

        private static string LastPosFilePath => Path.Combine(UTIL.CurrentPath, "Data", "last_pos.json");

        public static void SaveLastPosition()
        {
            if (_virtualAGV == null || _virtualAGV.CurrentNode == null) return;


            try
            {
                var data = new LastPositionData
                {
                    NodeId = _virtualAGV.CurrentNode.Id,
                    Direction = _virtualAGV.CurrentDirection,
                    Turn = _virtualAGV.Turn,
                    SaveTime = DateTime.Now,
                    AGV_Motor = PUB.AGV.data.Direction,
                    AGV_Speed = PUB.AGV.data.Speed,
                    AGV_TagString = PUB.AGV.data.TagString,
                    PrevNode = _virtualAGV.PrevNode?.Id ?? string.Empty,
                    PrevDirection = _virtualAGV.PrevDirection,
                    StartNode = _virtualAGV.StartNode?.Id ?? string.Empty,
                    TargetNode = _virtualAGV.TargetNode?.Id ?? string.Empty,
                    AGV_Turn = PUB.AGV.TurnInformation,
                };

                var fi = new System.IO.FileInfo(LastPosFilePath);
                if (fi.Directory.Exists == false) fi.Directory.Create();
                string json = JsonConvert.SerializeObject(data, Formatting.Indented);
                File.WriteAllText(LastPosFilePath, json);
            }
            catch (Exception ex)
            {
                log.AddE($"[PUB] Failed to save last position: {ex.Message}");
            }
        }

        public static LastPositionData LoadLastPosition()
        {
            try
            {
                if (!File.Exists(LastPosFilePath)) return null;

                string json = File.ReadAllText(LastPosFilePath);
                return JsonConvert.DeserializeObject<LastPositionData>(json);
            }
            catch (Exception ex)
            {
                log.AddE($"[PUB] Failed to load last position: {ex.Message}");
                return null;
            }
        }

        #endregion

        #endregion

    }
}
