using Avalonia.Threading;
using AvaloniaApplication1.ViewModels;
using DaireApplication.DataBase;
using DaireApplication.ViewModels;
using DaireApplication.Views;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace DaireApplication.Loops
{
    public class SerialRequest
    {
        public enum RequestType { Read, Write }
        public RequestType Type { get; set; }
        public byte[] Data { get; set; }
        public int ExpectedLength { get; set; }
        public TaskCompletionSource<byte[]> Completion { get; set; } = new();
    }

        public class serialThreadLoop
        {
            private static readonly ConcurrentQueue<SerialRequest> writeQueue = new();
            private static readonly ConcurrentQueue<SerialRequest> readQueue = new();
            private static bool keepSendingRunning = false;

            public static Task<byte[]> EnqueueWrite(byte[] data, int expectedLength)
            {
                var req = new SerialRequest { Type = SerialRequest.RequestType.Write, Data = data, ExpectedLength = expectedLength };
                writeQueue.Enqueue(req);
                return req.Completion.Task;
            }
            public static Task<byte[]> EnqueueRead(byte[] data, int expectedLength)
            {
                var req = new SerialRequest { Type = SerialRequest.RequestType.Read, Data = data, ExpectedLength = expectedLength };
                readQueue.Enqueue(req);
                return req.Completion.Task;
            }

            public static async void SendViaSerial(MainWindow _mainWindow)
            {
                try
                {
                    //Debug.WriteLine("SendViaSerial started");
                    // Start the keepSending background task if not already running
                    if (!keepSendingRunning)
                    {
                        //Debug.WriteLine("Starting KeepSendingLoop");
                        keepSendingRunning = true;
                        _ = Task.Run(() => KeepSendingLoop(_mainWindow));
                    }

                    List<float> values = new List<float>();
                    while (_mainWindow.serialThreadRunning)
                    {
                        //Debug.WriteLine($"SendViaSerial loop iteration - Port status: {(_mainWindow._port?.IsOpen ?? false)}");
                        if (_mainWindow._port != null && _mainWindow._port.IsOpen)
                        {
                            if (_mainWindow.reSendHolding)
                            {
                                values.Clear();
                                _mainWindow._configrations = _mainWindow._config.ReadConfigrations();
                                values.Add(_mainWindow.holdingRegister.resetError);
                                values.Add(_mainWindow.holdingRegister.hvOut);
                                values.Add(_mainWindow.holdingRegister.lvOut);
                                values.Add(_mainWindow.holdingRegister.motor);
                                values.Add(_mainWindow.holdingRegister.setTemp1);
                                values.Add(_mainWindow.holdingRegister.setTemp2);
                                values.Add(_mainWindow.holdingRegister.setTemp3);
                                values.Add(_mainWindow.holdingRegister.setTemp4);
                                values.Add((int)(_mainWindow._configrations[0].i_neut * 10));
                                values.Add((int)(_mainWindow._configrations[0].i_mot1 * 10));
                                values.Add((int)(_mainWindow._configrations[0].i_mot2 * 10));
                                foreach (var item in _mainWindow._configrations)
                                {
                                    values.Add(item.Max * 10);
                                    values.Add(item.Min * 10);
                                    values.Add(ConvertToDecimal(item.H_out.ToList()));
                                    values.Add(ConvertToDecimal(item.FC_out.ToList()));
                                    values.Add(ConvertToDecimal(item.SC_out.ToList()));
                                    values.Add(item.FC_Threshold * 10);
                                    values.Add(item.HeatConRange * 10);
                                    values.Add(item.kp);
                                    values.Add(item.ki);
                                    values.Add(item.kd);
                                    values.Add(item.kl);
                                }
                                _mainWindow._port.DiscardInBuffer();
                                _mainWindow._port.DiscardOutBuffer();
                                byte[] requstConfig = await _mainWindow._modBusMaster.WriteMultipleRegisters(0, values.ToArray());
                                await EnqueueWrite(requstConfig, 7);
                                _mainWindow.reSendHolding = false;
                            }
                            if (_mainWindow.sendConfig)
                            {
                                values.Clear();
                                _mainWindow._configrations = _mainWindow._config.ReadConfigrations();
                                if (!_mainWindow.dontResetOutPuts)
                                {
                                    values.Add(0);
                                    values.Add(0);
                                    values.Add(0);
                                    values.Add(-10000);
                                    values.Add(-10000);
                                    values.Add(-10000);
                                    values.Add(-10000);
                                }
                                values.Add((int)(_mainWindow._configrations[0].i_neut * 10));
                                values.Add((int)(_mainWindow._configrations[0].i_mot1 * 10));
                                values.Add((int)(_mainWindow._configrations[0].i_mot2 * 10));
                                foreach (var item in _mainWindow._configrations)
                                {
                                    values.Add(item.Max * 10);
                                    values.Add(item.Min * 10);
                                    values.Add(ConvertToDecimal(item.H_out.ToList()));
                                    values.Add(ConvertToDecimal(item.FC_out.ToList()));
                                    values.Add(ConvertToDecimal(item.SC_out.ToList()));
                                    values.Add(item.FC_Threshold * 10);
                                    values.Add(item.HeatConRange * 10);
                                    values.Add(item.kp);
                                    values.Add(item.ki);
                                    values.Add(item.kd);
                                    values.Add(item.kl);
                                }
                          
                                byte[] requstConfig = await _mainWindow._modBusMaster.WriteMultipleRegisters(_mainWindow.dontResetOutPuts ? (ushort)8 : (ushort)1, values.ToArray());
                                await EnqueueWrite(requstConfig, 7);
                                _mainWindow.sendConfig = false;
                                _mainWindow.dontResetOutPuts = false;
                            }
                            if (_mainWindow.restBoard)
                            {
                                _mainWindow.holdingRegister.hvOut = 0;
                                _mainWindow.holdingRegister.lvOut = 0;
                                _mainWindow.holdingRegister.motor = 0;
                                _mainWindow.holdingRegister.setTemp1 = -10000;
                                _mainWindow.holdingRegister.setTemp2 = -10000;
                                _mainWindow.holdingRegister.setTemp3 = -10000;
                                _mainWindow.holdingRegister.setTemp4 = -10000;
                            
                                // Send reset data directly without using the async pattern
                                List<float> resetValues = new List<float>();
                                resetValues.Add(_mainWindow.holdingRegister.resetError);
                                resetValues.Add(_mainWindow.holdingRegister.hvOut);
                                resetValues.Add(_mainWindow.holdingRegister.lvOut);
                                resetValues.Add(_mainWindow.holdingRegister.motor);
                                resetValues.Add(_mainWindow.holdingRegister.setTemp1);
                                resetValues.Add(_mainWindow.holdingRegister.setTemp2);
                                resetValues.Add(_mainWindow.holdingRegister.setTemp3);
                                resetValues.Add(_mainWindow.holdingRegister.setTemp4);
                            
                                byte[] resetRequest = await _mainWindow._modBusMaster.WriteMultipleRegisters(0, resetValues.ToArray());
                                await EnqueueWrite(resetRequest, 7);
                            
                                _mainWindow.restBoard = false;
                            }
                            // Check if there's a pending write request
                            if (_mainWindow._writeCompletionSource?.Task.Status == TaskStatus.WaitingForActivation)
                            {
                                List<float> writingValues = new List<float>();
                                writingValues.Add(_mainWindow.holdingRegister.resetError);
                                writingValues.Add(_mainWindow.holdingRegister.hvOut);
                                writingValues.Add(_mainWindow.holdingRegister.lvOut);
                                writingValues.Add(_mainWindow.holdingRegister.motor);
                                writingValues.Add(_mainWindow.holdingRegister.setTemp1);
                                writingValues.Add(_mainWindow.holdingRegister.setTemp2);
                                writingValues.Add(_mainWindow.holdingRegister.setTemp3);
                                writingValues.Add(_mainWindow.holdingRegister.setTemp4);
                                byte[] requstConfig = await _mainWindow._modBusMaster.WriteMultipleRegisters(0, writingValues.ToArray());
                                var result = await EnqueueWrite(requstConfig, 7);
                                // Signal completion
                                bool success = result.Length != 1 || result[0] != 0xFF;
                                _mainWindow.SetWriteComplete(success);
                            }
                            var requstReadingInputs = await _mainWindow._modBusMaster.ReadInputRegisters(0, 18);
                            await EnqueueRead(requstReadingInputs, 41);
                        }
                    }
                }
                catch (Exception ex)
                {

                }
            
            }

            private static async Task KeepSendingLoop(MainWindow _mainWindow)
            {
                //Debug.WriteLine("KeepSendingLoop started");
                var startTime = DateTime.Now;
                int intervalMs = _mainWindow.screenData.sendingTime;
                int requestCount = 0;

                while (_mainWindow.serialThreadRunning)
                {
                    try 
                    {
                        _mainWindow.screenData = _mainWindow._screeen.ReadScreens()[0];
                        //Debug.WriteLine($"KeepSendingLoop iteration - Port status: {(_mainWindow._port?.IsOpen ?? false)}, SendingTime: {_mainWindow.screenData.sendingTime}ms");

                        // Skip if port is not valid
                        if (_mainWindow._port == null || !_mainWindow._port.IsOpen)
                        {
                            //Debug.WriteLine("Port not valid, waiting...");
                            await Task.Delay(100); // Wait a bit before checking again
                            continue;
                        }

                        var scheduledTime = startTime.AddMilliseconds(requestCount * intervalMs);
                        var now = DateTime.Now;
                        var waitTime = (scheduledTime - now).TotalMilliseconds;
                        if (waitTime > 0)
                            await Task.Delay((int)waitTime);

                        SerialRequest req = null;
                        if (!writeQueue.TryPeek(out req))
                            readQueue.TryPeek(out req);

                        if (req != null)
                        {
                            var requestStart = DateTime.Now;
                            var response = await keepSendingScenario(_mainWindow, req.Data, req.ExpectedLength,_mainWindow.screenData.sendingTime);
                            var requestEnd = DateTime.Now;
                            var requestElapsed = (requestEnd - requestStart).TotalMilliseconds;
                            if (response.Length != 1 || response[0] != 0xFF)
                            {
                                //Debug.WriteLine($"SUCCESS: Total time for request: {requestElapsed} ms");
                                if (req.Type == SerialRequest.RequestType.Read)
                                {
                                    _mainWindow.inputesResponse = response;
                                }
                                req.Completion.SetResult(response);
                            }
                            else // failed after retries or timeout
                            {
                                //Debug.WriteLine($"FAILED: Total time for request: {requestElapsed} ms");
                                req.Completion.SetResult(response); // set failure result
                            }
                            if (req.Type == SerialRequest.RequestType.Write)
                                writeQueue.TryDequeue(out _);
                            else
                                readQueue.TryDequeue(out _);
                        }

                        requestCount++;
                        // If we are behind schedule, catch up
                        if ((DateTime.Now - startTime).TotalMilliseconds > requestCount * intervalMs)
                            requestCount = (int)((DateTime.Now - startTime).TotalMilliseconds / intervalMs);
                    }
                    catch (Exception ex)
                    {
                        //Debug.WriteLine($"Error in KeepSendingLoop: {ex.Message}");
                        await Task.Delay(100); // Wait a bit before retrying
                    }
                }
            }

            private static async Task<byte[]> keepSendingScenario(MainWindow _mainWindow, byte[] request, int expectedLength, int interval)
            {
                // Enforce minimum interval between packets
                using var cts = new CancellationTokenSource();
                int SENDING_INTERVAL = interval; // Minimum interval between sends

                var now = DateTime.Now;
                var elapsed = (now - _mainWindow._lastPacketSendTime).TotalMilliseconds;
                if (elapsed < _mainWindow.screenData.sendingTime)
                {
                    await Task.Delay((int)(SENDING_INTERVAL - elapsed));
                }
                _mainWindow._lastPacketSendTime = DateTime.Now;

                //Debug.WriteLine("keepSending started");

                // Calculate 4 character delay based on baud rate
                double fourCharDelay = (1.0 / _mainWindow.screenData.boundRate) * 44000;
                int noResponseRetryCount = 0;
                const int MAX_NO_RESPONSE_RETRIES = 3; // Try 3 times, once per second
                const int NO_RESPONSE_TIMEOUT = 1000; // 1 second between no-response retries
                const int TOTAL_INVALID_RETRY_TIME = 3000; // 3 seconds total for invalid responses

                var startTime = DateTime.Now;
                var lastSendTime = DateTime.Now;
                var lastValidResponseTime = DateTime.Now;
                bool hadValidResponse = false;

                while ((DateTime.Now - startTime).TotalMilliseconds < TOTAL_INVALID_RETRY_TIME)
                {
                    // Calculate time since last send
                    var timeSinceLastSend = (DateTime.Now - lastSendTime).TotalMilliseconds;
                    var timeSinceLastValidResponse = (DateTime.Now - lastValidResponseTime).TotalMilliseconds;

                    // Check for communication timeout
                    if (timeSinceLastValidResponse >= TOTAL_INVALID_RETRY_TIME && !hadValidResponse)
                    {
                        //Debug.WriteLine("No valid response received within 3 seconds");
                        if (!MainWindow.errors.Any(x => x.Condition == Error.GridCondition.NoBoardCom))
                        {
                            MainWindow.errors.Add(new Error
                            {
                                errorDate = DateTime.Now,
                                Condition = Error.GridCondition.NoBoardCom
                            });
                        }
                    }

                    // For no response case, check frequently for response
                    if (noResponseRetryCount > 0 && timeSinceLastSend < NO_RESPONSE_TIMEOUT)
                    {
                        // Check for response every 10ms
                        if (_mainWindow._port != null && _mainWindow._port.IsOpen)
                        {
                            if (_mainWindow._port.BytesToRead >= expectedLength)
                            {
                                // Wait 4-char delay when we have enough bytes
                                await Task.Delay((int)fourCharDelay);
                                var response = await TryReadOnce(_mainWindow, expectedLength, cts.Token);
                                //Debug.WriteLine($"Response received during wait after {timeSinceLastSend}ms");

                                if (response.Length == expectedLength && IsValidCrc(response))
                                {
                                    hadValidResponse = true;
                                    lastValidResponseTime = DateTime.Now; // Reset valid response timer
                                    var err = MainWindow.errors.FirstOrDefault(x => x.Condition == Error.GridCondition.NoBoardCom);
                                    if (err != null) err.isDeleted = true;

                                    // For valid response, still respect minimum interval
                                    //if (timeSinceLastSend < SENDING_INTERVAL)
                                    //{
                                    //    var remainingTime = SENDING_INTERVAL - timeSinceLastSend;
                                    //    Debug.WriteLine($"Valid response received before {SENDING_INTERVAL}ms, waiting {remainingTime}ms");
                                    //    await Task.Delay((int)remainingTime);
                                    //}
                                    return response;
                                }

                                // For any response (even invalid), increment retry count and continue immediately
                                noResponseRetryCount++;
                                if (noResponseRetryCount >= MAX_NO_RESPONSE_RETRIES)
                                {
                                    //Debug.WriteLine("Max retries exceeded");
                                    if (!MainWindow.errors.Any(x => x.Condition == Error.GridCondition.NoBoardCom))
                                    {
                                        MainWindow.errors.Add(new Error
                                        {
                                            errorDate = DateTime.Now,
                                            Condition = Error.GridCondition.NoBoardCom
                                        });
                                    }
                                    return new byte[] { 0xFF };
                                }

                                // Break wait and send next retry immediately
                                lastSendTime = DateTime.Now.AddMilliseconds(-NO_RESPONSE_TIMEOUT); // Force immediate retry
                                break;
                            }
                            /*await Task.Delay(10);*/ // Small delay between checks
                            continue;
                        }
                    }

                    // For invalid response case, wait minimum interval between retries
                    if (timeSinceLastSend < SENDING_INTERVAL)
                    {
                        await Task.Delay(1); // Minimal delay to prevent tight loop
                        continue;
                    }

                    // Send the request
                    _mainWindow._port.Write(request, 0, request.Length);
                    lastSendTime = DateTime.Now;
                    //Debug.WriteLine($"Sent request at {lastSendTime:HH:mm:ss.fffffff}");

                    // Wait 4-char delay after sending
                    await Task.Delay((int)fourCharDelay);

                    // Try to read response
                    if (_mainWindow._port != null && _mainWindow._port.IsOpen && _mainWindow._port.BytesToRead >= expectedLength)
                    {
                        // Wait another 4-char delay when we have enough bytes
                        await Task.Delay((int)fourCharDelay);
                        var response = await TryReadOnce(_mainWindow, expectedLength, cts.Token);

                        // Handle valid response
                        if (response.Length >= expectedLength && IsValidCrc(response))
                        {
                            hadValidResponse = true;
                            lastValidResponseTime = DateTime.Now; // Reset valid response timer
                            var err = MainWindow.errors.FirstOrDefault(x => x.Condition == Error.GridCondition.NoBoardCom);
                            if (err != null) err.isDeleted = true;
                            return response;
                        }
                    }

                    // Handle invalid/no response
                    noResponseRetryCount++;
                    //Debug.WriteLine($"Invalid/No response, attempt {noResponseRetryCount} of {MAX_NO_RESPONSE_RETRIES}");
                    if (noResponseRetryCount >= MAX_NO_RESPONSE_RETRIES)
                    {
                        //Debug.WriteLine("Max retries exceeded");
                        if (!MainWindow.errors.Any(x => x.Condition == Error.GridCondition.NoBoardCom))
                        {
                            MainWindow.errors.Add(new Error
                            {
                                errorDate = DateTime.Now,
                                Condition = Error.GridCondition.NoBoardCom
                            });
                        }
                        return new byte[] { 0xFF };
                    }
                }

                return new byte[] { 0xFF };
            }

            private static async Task<byte[]> TryReadOnce(MainWindow _mainWindow, int expectedLength, CancellationToken token)
            {
                var buffer = new List<byte>();
                var startTime = DateTime.UtcNow;
                var timeout = 3000;
                while ((DateTime.UtcNow - startTime).TotalMilliseconds < timeout)
                {
                    token.ThrowIfCancellationRequested();
                    if (_mainWindow._port != null && _mainWindow._port.IsOpen)
                    {
                        int available = _mainWindow._port.BytesToRead;
                        if (available > 0)
                        {
                            byte[] temp = new byte[available];
                            _mainWindow._port.Read(temp, 0, available);
                            buffer.AddRange(temp);
                            if (buffer.Count >= 3)
                            {
                                var response = buffer.ToArray();
                                if (response.Length < expectedLength)
                                {
                                    //Debug.WriteLine($"Invalid response length: got {response.Length}, expected {expectedLength}");
                                    return new byte[] { 0xFF };
                                }
                                if (!IsValidCrc(response))
                                {
                                    //Debug.WriteLine("Invalid CRC in response");
                                    return new byte[] { 0xFF };
                                }
                                var err = MainWindow.errors.FirstOrDefault(x => x.Condition == Error.GridCondition.NoBoardCom);
                                if (err != null) err.isDeleted = true;
                                return response;
                            }
                        }
                    }
                    await Task.Delay(1);
                }
                //Debug.WriteLine("Response timeout");
                return new byte[] { 0xFF };
            }

            private static bool IsValidCrc(byte[] data)
            {
                if (data.Length < 3) return false;
                ushort calculated = ComputeCRC(data.AsSpan(0, data.Length - 2));
                ushort received = (ushort)(data[^2] | (data[^1] << 8));
                return calculated == received;
            }
            private static ushort ComputeCRC(ReadOnlySpan<byte> data)
            {
                ushort crc = 0xFFFF;
                foreach (var b in data)
                {
                    crc ^= b;
                    for (int i = 0; i < 8; i++)
                    {
                        if ((crc & 0x0001) != 0)
                        {
                            crc >>= 1;
                            crc ^= 0xA001;
                        }
                        else
                        {
                            crc >>= 1;
                        }
                    }
                }
                return crc;
            }
            static int ConvertToDecimal(List<int> bitPositions)
            {
                int result = 0;
                if (bitPositions.Count() == 1 && bitPositions[0] == -1)
                {
                    return result;
                }
                foreach (var pos in bitPositions)
                {
                    if (pos is >= 0 and < 16)
                        result |= 1 << pos;
                }
                return result;
            }
        }
}
