using GraduatedCylinder;
using GraduatedCylinder.Geo;
using GraduatedCylinder.Geo.Gps;
using GraduatedCylinder.Geo.Gps.Nmea;
using Nmea.Core0183;
using Shouldly;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;

namespace XiaoZhiSharp.Kernels.Gis
{
    /* --NMEA0183协议--RMC,RMC,GGA,GSA,GSV,PVTG,GLL代表意义--
    |----------------------------------------------------------------------------------------------------|
    |NMEA0183 常用语句类型说明                                                                           |
    |----------------------------------------------------------------------------------------------------|
    |语句类型 |          	全称                      	  |  含义说明                                    |
    |RMC	  |  Recommended Minimum Specific GNSS Data	  |  推荐最小定位信息                            |
    |         |                                           |   - 包含最基本的位置、速度、时间和日期信息   |
    |         |                                           |   - 最常用的定位数据语句                     |
    |---------|-------------------------------------------|----------------------------------------------|
    |GGA      |  Global Positioning System Fix Data	      |  GPS定位信息                                 |
    |         |                                           |   - 包含详细的定位质量信息                   |
    |         |                                           |   - 经纬度、海拔高度、GPS质量指示、卫星数量等|
    |---------|-------------------------------------------|----------------------------------------------|
    |GSA	  |  GNSS DOP and Active Satellites	          |  卫星精度与活动卫星                          |
    |         |                                           |   - 显示当前使用的卫星和精度因子(DOP)        |
    |         |                                           |   - 定位模式（2D/3D）、PDOP、HDOP、VDOP值    |
    |---------|-------------------------------------------|----------------------------------------------|
    |GSV	  |  GNSS Satellites in View	              |      可见卫星信息                            |
    |         |                                           |   - 列出所有可见卫星的详细信息               |
    |         |                                           |   - 卫星编号、仰角、方位角、信噪比(SNR)等    |
    |---------|-------------------------------------------|----------------------------------------------|
    |VTG	  |  Course Over Ground and Ground Speed	  |      地面速度与航向                          |
    |         |                                           |   - 对地速度和对地航向信息                   |
    |         |                                           |   - 真实航向、磁航向、速度（节、公里/小时）  |
    |---------|-------------------------------------------|----------------------------------------------|
    |GLL	  |  Geographic Position - Latitude/Longitude |	地理经纬度位置                               |
    |         |                                           |   - 纯粹的经纬度坐标信息                     |
    |         |                                           |   - 包含定位状态（有效/无效）和时间戳        |
    |----------------------------------------------------------------------------------------------------|
    */

    public class SharpGISNmeaParser
    {
        /// <summary>
        /// 实时GPS数据采集方法 - 从COM4串口读取GPS数据并记录到日志文件
        /// </summary>
        public static void LiveGpsOnCOM4()
        {
            // GPS日志文件路径
            string fileName = @"C:\Gps\Test.glog";

            // 如果日志文件已存在，则删除旧文件以确保重新开始记录
            if (File.Exists(fileName))
            {
                File.Delete(fileName);
            }

            // 创建NMEA串口数据提供者，指定COM4端口
            // NMEA (National Marine Electronics Association) 0183是GPS设备通信标准协议
            IProvideSentences sentences = new NmeaSerialPort(
                portName: "COM4",
                baudRate: 4800,
                parity: System.IO.Ports.Parity.None,
                dataBits: 8,
                stopBits: System.IO.Ports.StopBits.One);

            // 包装数据提供者，添加日志记录功能，所有接收到的GPS语句将被记录到指定文件
            sentences = new SentenceLogger(sentences, fileName);

            // 创建GPS单元实例，用于解析NMEA语句并生成GPS数据事件
            GpsUnit gps = new(sentences);

            // 注册位置变化事件处理程序
            // 当GPS设备报告新的位置数据时触发此事件
            gps.LocationChanged += Gps_LocationChanged;

            // 启用GPS数据采集，开始监听串口数据并触发事件
            gps.IsEnabled = true;

            // 数据采集循环 - 保持GPS运行10秒（100次 × 100毫秒）
            for (int i = 0; i < 100; i++)
            {
                Thread.Sleep(100);  // 每次休眠100毫秒
            }

            // 禁用GPS数据采集，停止监听数据
            gps.IsEnabled = false;

            // 验证日志文件是否成功创建
            // 如果文件不存在，说明GPS数据记录过程中出现问题
            if (!File.Exists(fileName))
            {
                throw new FileNotFoundException("Expected test to create a log file.", fileName);
            }
        }

        private static void Gps_LocationChanged(LocationChangedEventArgs args)
        {
            // 记录当前系统时间和GPS消息时间戳
            Trace.TraceInformation("Received: {0:u}: Message Timestamp: {1:u}", DateTime.Now, args.Time);

            // 输出位置信息：纬度、经度、海拔高度（转换为英尺显示，保留1位小数）
            Trace.TraceInformation("    Lat: {0} Long: {1} Alt: {2}",
                                  args.Position.Latitude,           // 纬度坐标
                                  args.Position.Longitude,          // 经度坐标
                                  args.Position.Altitude.ToString(LengthUnit.Foot, 1));  // 海拔高度，单位：英尺

            // 输出航向和速度信息
            Trace.TraceInformation("    Heading: {0} at Speed: {1}",
                                  args.Heading,                     // 航向角度
                                  args.Speed.ToString(SpeedUnit.MilesPerHour));  // 速度，单位：英里/小时

            Trace.TraceInformation("");  // 输出空行分隔不同的位置更新
        }

        /// <summary>
        /// 以最快速度回放GPS日志文件，用于测试GPS数据处理性能和事件触发机制
        /// 该方法会模拟GPS数据播放，验证在高速数据输入情况下的处理能力
        /// </summary>
        public static void PlaybackLogAsFastAsPossible()
        {
            // 记录接收到的事件数量
            int eventCount = 0;

            // 指定要回放的GPS日志文件路径
            // @前缀表示逐字字符串，避免转义字符处理
            string fileName = @".\Configs\Sample1.gpslog";

            // 创建SentenceLog实例用于解析和回放GPS日志文件
            // PlaybackRate.AsFastAsPossible表示以最大速度回放，不按实际时间间隔
            SentenceLog sentences = new(fileName, SentenceLog.PlaybackRate.AsFastAsPossible);
            sentences.SentenceReceived += Sentences_SentenceReceived;

            // 创建GPS单元实例，传入日志回放器作为数据源
            GpsUnit gps = new(sentences);

            // 注册位置改变事件处理程序
            // 使用lambda表达式，每次事件触发时增加事件计数器
            // _表示忽略事件参数，因为我们只需要计数
            gps.LocationChanged += _ => eventCount++;
            gps.LocationChanged += Gps_LocationChanged;

            // 记录测试开始时间
            DateTime startTime = DateTime.Now;

            // 启用GPS单元，开始接收和处理数据
            // 这将启动数据流并开始触发LocationChanged事件
            gps.IsEnabled = true;

            // 循环等待直到日志回放完成
            // PlaybackComplete属性指示是否所有日志数据都已处理完毕
            while (!sentences.PlaybackComplete)
            {
                // 每次循环暂停50毫秒，避免过度占用CPU资源
                // 这给了系统处理事件和更新状态的时间
                Thread.Sleep(50);
            }

            // 回放完成后禁用GPS单元，停止数据处理
            gps.IsEnabled = false;

            // 计算整个回放过程的持续时间
            TimeSpan duration = DateTime.Now - startTime;

            // 断言验证：期望接收到18个位置改变事件
            // 这是基于测试数据文件的预期行为验证
            eventCount.ShouldBe(18);

            // 断言验证：整个回放过程应该在1秒内完成
            // 这确保了"最快速度"回放的性能要求
            duration.ShouldBeLessThan(new TimeSpan(0, 0, 1));
        }

        private static readonly GpsParser _parser = new();
        private static readonly List<int> _activeSatellitePrns = new();
        private static readonly IProvideSentences _nmeaProvider;
        private static readonly Dictionary<int, SatelliteInfo> _satellites = new();

        public static GpsFixType CurrentFixType { get; private set; }
        public static GeoPosition CurrentLocation { get; private set; }
        public static double PositionDop { get; private set; }

        public static IEnumerable<SatelliteInfo> Satellites => _satellites.Where(skv => _activeSatellitePrns.Contains(skv.Key)).Select(skv => skv.Value);

        public static double VerticalDop { get; private set; }
        public static DateTimeOffset CurrentTime { get; private set; }
        public static bool HasLocation { get; private set; }
        public static Heading CurrentHeading { get; private set; }
        public static double HorizontalDop { get; private set; }

        public static Speed MinimumSpeedForHeadingUpdate { get; set; }
        public static Speed CurrentSpeed { get; private set; }

        private static void Sentences_SentenceReceived(Sentence st)
        {
            Message? message = _parser.Parse(st);
            if (message == null)
            {
                return;
            }
            //NB set all values then raise all notifications
            if (message.Value is IProvideSatelliteInfo info)
            {
                foreach (SatelliteInfo satellite in info.Satellites)
                {
                    _satellites[satellite.Prn] = satellite;
                }
            }
            if (message.Value is IProvideActiveSatellites active)
            {
                _activeSatellitePrns.Clear();
                foreach (int prn in active.ActiveSatellitePrns.Where(prn => prn != 0))
                {
                    _activeSatellitePrns.Add(prn);
                }
            }
            if (message.Value is IProvideFixType fixType)
            {
                CurrentFixType = fixType.CurrentFix;
            }
            if (message.Value is IProvideDilutionOfPrecision dop)
            {
                PositionDop = dop.PositionDop;
                HorizontalDop = dop.HorizontalDop;
                VerticalDop = dop.VerticalDop;
            }
            if (message.Value is IProvideTime time)
            {
                CurrentTime = time.CurrentTime;
            }
            if (message.Value is IProvideTrajectory trajectory)
            {
                //NB heading and speed are correlated
                CurrentSpeed = trajectory.CurrentSpeed;
                // NB don't update heading when speed is near zero
                if (CurrentSpeed > MinimumSpeedForHeadingUpdate)
                {
                    CurrentHeading = trajectory.CurrentHeading;
                }
            }
            if (message.Value is IProvideGeoPosition position)
            {
                GeoPosition newLocation = position.CurrentLocation;
                CurrentLocation =
                    newLocation.Altitude == Length.Unknown ?
                        new GeoPosition(newLocation.Latitude,
                                        newLocation.Longitude,
                                        CurrentLocation.Altitude) :
                        newLocation;
                HasLocation = true;
            }

            var sats = Satellites;

            var satelliteType = AnalysisSatelliteType(message.Sentence.Id);

            Console.WriteLine($"NMEA句子标识符：{message.Sentence.Id}");
            Console.WriteLine($"卫星类型：{satelliteType}，卫星数量：{_satellites.Count}，使用卫星数量;{sats.Count()}");
        }

        /// <summary>
        /// 根据NMEA句子标识符分析卫星类型
        /// </summary>
        /// <param name="sentenceId">NMEA句子标识符</param>
        /// <returns></returns>
        private static SatelliteType AnalysisSatelliteType(string sentenceId)
        {
            SatelliteType satelliteType = SatelliteType.GPS;
            switch (sentenceId)
            {
                case "$GP":
                    satelliteType = SatelliteType.GPS;
                    break;
                case "$GL":
                    satelliteType = SatelliteType.GLONASS;
                    break;
                case "$GA":
                    satelliteType = SatelliteType.Galileo;
                    break;
                case "$GN":
                    satelliteType = SatelliteType.GNSS;
                    break;
                case "$BD":
                    satelliteType = SatelliteType.BDS;
                    break;

            }

            return satelliteType;
        }
    }

    public enum SatelliteType
    {
        /// <summary>
        /// Global Positioning System（全球定位系统）
        /// </summary>
        GPS,
        /// <summary>
        /// Global Navigation Satellite System（全球导航卫星系统）
        /// </summary>
        GLONASS,
        /// <summary>
        /// European Global Satellite Navigation System（伽利略卫星导航系统）
        /// </summary>
        Galileo,
        /// <summary>
        /// Combined GPS, GLONASS, Galileo, etc.（组合导航系统）
        /// </summary>
        GNSS,
        /// <summary>
        /// BeiDou Navigation Satellite System（北斗卫星导航系统）
        /// </summary>
        BDS
    }
}
