using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Monoculus
{
    // Enums for different states
    public enum ClientState
    {
        None = 0x00,
        Connecting = 0x01,
        ProtoHandshake = 0x02,
        LoginPhase1 = 0x03,
        LoginPhase2 = 0x04,
        MessageHeader = 0x05,
        Message = 0x06,
        Tx = 0x07,
        Rx = 0x08,
        ClientsHeader = 0x09,
        Clients = 0x0A,
        NetworksHeader = 0x0B,
        Networks = 0x0C,
        SndFrameIn = 0x0D,
        KeepAlive = 0x0E,
        Disconnected = 0x0F,
        TxRequest = 0x10,
        TxWaiting = 0x11,
        TxApproved = 0x12,
        TxRejected = 0x13,
        TxComplete = 0x14,
        Ping = 0x15,
        BanlistHeader = 0x16,
        Banlist = 0x17,
        MutelistHeader = 0x18,
        Mutelist = 0x19,
        PttDown = 0x1A,
        PttUp = 0x1B,
        MessageInput = 0x1C,
        MessageSend = 0x1D,
        Abort = 0xFE,
        Idle = 0xFF
    }

    // Constants for markers
    public static class Markers
    {
        public const byte KeepAlive = 0x00;
        public const byte TxApprove = 0x01;
        public const byte Sound = 0x02;
        public const byte Clients = 0x03;
        public const byte Message = 0x04;
        public const byte Networks = 0x05;
        public const byte Ban = 0x08;
        public const byte Mute = 0x09;
        // Additional markers based on documentation
        public const byte DT_IDLE = 0x00;
        public const byte DT_DO_TX = 0x01;
        public const byte DT_VOICE_BUFFER = 0x02;
        public const byte DT_CLIENT_LIST = 0x03;
        public const byte DT_TEXT_MESSAGE = 0x04;
        public const byte DT_NET_NAMES = 0x05;
        public const byte DT_ADMIN_LIST = 0x06;
        public const byte DT_ACCESS_LIST = 0x07;
        public const byte DT_BLOCK_LIST = 0x08;
        public const byte DT_MUTE_LIST = 0x09;
        public const byte DT_ACCESS_MODE = 0x0A;
    }

    // Other constants
    public static class Constants
    {
        public const int KeepAliveTimeout = 1;
        public const int FRNProtocolVersion = 2022001;

        public const string FRNTypePCOnly = "PC Only";
        public const string FRNTypeCrosslink = "Crosslink";
        public const string FRNTypeParrot = "Parrot";

        public const string FRNMessageBroadcast = "A";
        public const string FRNMessagePrivate = "P";

        public const int FRNStatusOnline = 0;
        public const int FRNStatusAway = 1;
        public const int FRNStatusNA = 2;

        public const int FRNMuteOff = 0;
        public const int FRNMuteOn = 1;

        public const string FRNResultOK = "OK";
        public const string FRNResultNOK = "NOK";
        public const string FRNResultWrong = "WRONG";
    }

    /// <summary>
    /// Represents a Free Radio Network client.
    /// </summary>
    public class FRNClient
    {
        private TcpClient _tcpClient;
        private NetworkStream _networkStream;
        private readonly string _host;
        private readonly int _port;
        private ClientState _state;
        private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();

        // Client parameters
        private readonly string _callSign;
        private readonly string _name;
        private readonly string _email;
        private readonly string _password;
        private readonly string _network;
        private readonly string _country;
        private readonly string _city;
        private readonly string _locator;
        private readonly string _type;
        private readonly string _description;

        // Buffers
        private readonly List<byte> _inputBuffer = new List<byte>();
        private readonly StringBuilder _outputBuffer = new StringBuilder();

        // Last KeepAlive timestamp
        private DateTime _lastKeepAlive;

        // Event handlers (callbacks)
        public event Action OnConnect;
        public event Action OnDisconnect;
        public event Action OnLogin;
        public event Action<string> OnMessageReceived;
        public event Action<byte[], int> OnVoiceDataReceived;
        public event Action<List<ClientInfo>> OnClientListReceived;

        // Additional properties and flags
        private bool _isConnected;

        // Variables to track voice data collection
        private bool _isReceivingVoiceData = false;
        private MemoryStream _voiceDataStream = new MemoryStream();
        private int _currentActiveClientIndex = -1;
        private readonly object _voiceDataLock = new object();


        /// <summary>
        /// Initializes a new instance of the FRNClient class.
        /// </summary>
        public FRNClient(string host, int port, string callSign, string name, string email, string password, string network, string country, string city, string locator, string type, string description)
        {
            _host = host;
            _port = port;
            _callSign = callSign;
            _name = name;
            _email = email;
            _password = password;
            _network = network;
            _country = country ?? "N/A";
            _city = city ?? "N/A";
            _locator = locator ?? "N/A";
            _type = type ?? Constants.FRNTypePCOnly;
            _description = description ?? string.Empty;
            _state = ClientState.None;
        }

        /// <summary>
        /// Starts the client and connects to the server.
        /// </summary>
        public async Task RunAsync()
        {
            await ConnectAsync();

            if (_isConnected)
            {
                await ProcessAsync(_cancellationTokenSource.Token);
            }
        }

        /// <summary>
        /// Connects to the FRN server.
        /// </summary>
        private async Task ConnectAsync()
        {
            try
            {
                _tcpClient = new TcpClient();
                await _tcpClient.ConnectAsync(_host, _port);
                _networkStream = _tcpClient.GetStream();
                _state = ClientState.Connecting;
                _isConnected = true;

                OnConnect?.Invoke();
#if DEBUG
                Console.WriteLine("Connected to server.");
#endif
                // Start the login process
                await SendLoginAsync();
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Connection failed: {ex.Message}");
                _isConnected = false;
                _state = ClientState.Disconnected;

                OnDisconnect?.Invoke();
            }
        }

        /// <summary>
        /// Processes incoming and outgoing data.
        /// </summary>
        private async Task ProcessAsync(CancellationToken cancellationToken)
        {
            try
            {
                var readTask = ReadAsync(cancellationToken);
                var writeTask = WriteAsync(cancellationToken);
                var keepAliveTask = KeepAliveAsync(cancellationToken);

                await Task.WhenAll(readTask, writeTask, keepAliveTask);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Processing error: {ex.Message}");
            }
        }

        /// <summary>
        /// Reads data from the network stream.
        /// </summary>
        private async Task ReadAsync(CancellationToken cancellationToken)
        {
            var buffer = new byte[8192];

            while (_isConnected && !cancellationToken.IsCancellationRequested)
            {
                if (_networkStream.DataAvailable)
                {
                    int bytesRead = await _networkStream.ReadAsync(buffer, 0, buffer.Length, cancellationToken);
                    if (bytesRead == 0)
                    {
                        // Connection closed
                        _isConnected = false;
                        _state = ClientState.Disconnected;

                        OnDisconnect?.Invoke();
#if DEBUG
                        Console.WriteLine("Disconnected from server.");
#endif
                        break;
                    }

                    // Process incoming data
                    byte[] dataReceived = new byte[bytesRead];
                    Array.Copy(buffer, dataReceived, bytesRead);
#if DEBUG
                    Console.WriteLine($"Received raw data ({bytesRead} bytes): {BitConverter.ToString(dataReceived)}");
#endif
                    _inputBuffer.AddRange(dataReceived);
                    ParseIncomingData();
                }
                else
                {
                    await Task.Delay(100, cancellationToken);
                }
            }
        }

        /// <summary>
        /// Parses incoming data from the server.
        /// </summary>
        private void ParseIncomingData()
        {
            while (true)
            {
                if (_inputBuffer.Count < 1)
                {
                    // Not enough data
                    break;
                }

                if (_state == ClientState.LoginPhase1)
                {
                    // We're expecting a login response
                    int newlineIndex = _inputBuffer.LastIndexOf((byte)'\r');
                    if (newlineIndex != -1)
                    {
                        byte[] lineBytes = _inputBuffer.GetRange(0, newlineIndex + 1).ToArray();
                        _inputBuffer.RemoveRange(0, newlineIndex + 1);
                        string response = Encoding.ASCII.GetString(lineBytes).Trim();

                        HandleLoginPhase1(response);
                    }
                    else
                    {
                        // Wait for more data
                        break;
                    }
                }
                else
                {
                    byte dataType = _inputBuffer[0];

                    switch (dataType)
                    {
                        case Markers.DT_IDLE:
                            _inputBuffer.RemoveAt(0); // Remove DataType byte
                            HandleIdleState();
                            break;
                        case Markers.DT_DO_TX:
                            if (_inputBuffer.Count >= 1 + 2)
                            {
                                _inputBuffer.RemoveAt(0); // Remove DataType byte
                                HandleDoTx();
                            }
                            else
                            {
                                // Not enough data
                                return;
                            }
                            break;
                        case Markers.DT_VOICE_BUFFER:
                            if (_inputBuffer.Count >= 1 + 2 + 325)
                            {
                                _inputBuffer.RemoveAt(0); // Remove DataType byte
                                HandleVoiceBuffer();
                            }
                            else
                            {
                                // Not enough data
                                return;
                            }
                            break;
                        // Handle other data types similarly
                        default:
                            Console.WriteLine($"Unknown data type received: {dataType}");
                            _inputBuffer.RemoveAt(0); // Remove unknown DataType byte
                            break;
                    }
                }
            }
        }

        /// <summary>
        /// Handles the 'DT_VOICE_BUFFER' data type.
        /// </summary>
        private void HandleVoiceBuffer()
        {
            if (_inputBuffer.Count >= 2 + 325)
            {
                ushort clientIndex = (ushort)((_inputBuffer[0] << 8) | _inputBuffer[1]);
                _inputBuffer.RemoveRange(0, 2);

                byte[] voiceData = _inputBuffer.GetRange(0, 325).ToArray();
                _inputBuffer.RemoveRange(0, 325);
#if DEBUG
                Console.WriteLine($"Received voice data from client index: {clientIndex}");
#endif
                // Invoke the event handler to process the voice data
                OnVoiceDataReceived?.Invoke(voiceData, clientIndex);
            }
            else
            {
                // Not enough data
            }
        }

        /// <summary>
        /// Handles the 'DT_DO_TX' data type.
        /// </summary>
        private void HandleDoTx()
        {
            if (_inputBuffer.Count >= 2)
            {
                ushort clientIndex = (ushort)((_inputBuffer[0] << 8) | _inputBuffer[1]);
                _inputBuffer.RemoveRange(0, 2);
                Console.WriteLine($"Server indicates you may start sending voice data. Active client index: {clientIndex}");
                // Implement logic to start transmitting voice data
            }
            else
            {
                // Wait for more data
            }
        }

        /// <summary>
        /// Handles the 'DT_IDLE' data type.
        /// </summary>
        private void HandleIdleState()
        {
#if DEBUG
            Console.WriteLine("Server is idle. No new data.");
#endif

            lock (_voiceDataLock)
            {
                if (_isReceivingVoiceData)
                {
                    // Transmission ended
                    _isReceivingVoiceData = false;

                    // Reset active client index
                    _currentActiveClientIndex = -1;
                    _voiceDataStream.SetLength(0);
                }
            }
        }

        /// <summary>
        /// Parses incoming data for login response.
        /// </summary>
        private void HandleLoginPhase1(string response)
        {
            // Extract the server version number
            int index = 0;
            while (index < response.Length && char.IsDigit(response[index]))
            {
                index++;
            }

            string versionString = response.Substring(0, index);
            string xmlData = response.Substring(index);
#if DEBUG
            Console.WriteLine($"Server version: {versionString}");
#endif
            // Parse the XML-like data
            var xmlTags = new Dictionary<string, string>();

            int pos = 0;
            while (pos < xmlData.Length)
            {
                if (xmlData[pos] == '<')
                {
                    int tagStart = pos + 1;
                    int tagEnd = xmlData.IndexOf('>', tagStart);
                    if (tagEnd == -1)
                        break; // incomplete tag

                    string tagName = xmlData.Substring(tagStart, tagEnd - tagStart);

                    int closingTagStart = xmlData.IndexOf($"</{tagName}>", tagEnd + 1);
                    if (closingTagStart == -1)
                    {
                        // Some tags might be self-closing or empty; adjust as needed
                        closingTagStart = tagEnd;
                        // break; // incomplete data
                    }

                    string tagValue = xmlData.Substring(tagEnd + 1, closingTagStart - tagEnd - 1);

                    xmlTags[tagName] = tagValue;

                    pos = closingTagStart + tagName.Length + 3; // Move past the closing tag
                }
                else
                {
                    pos++;
                }
            }

            // Process the extracted tags
            if (xmlTags.TryGetValue("AL", out string accessLevel))
            {
                Console.WriteLine($"Access Level: {accessLevel}");

                if (accessLevel == "OK" || accessLevel == "OWNER" || accessLevel == "NETOWNER" || accessLevel == "ADMIN")
                {
                    _state = ClientState.LoginPhase2;
                    OnLogin?.Invoke();
#if DEBUG
                    Console.WriteLine("Login successful.");
#endif
                    _state = ClientState.Idle;

                    // Send 'RX0' to server as per documentation
                    SendAsync("RX0\r\n").Wait();
                }
                else
                {
                    Console.WriteLine("Login failed or access blocked.");
                    _state = ClientState.Abort;
                    _isConnected = false;
                    OnDisconnect?.Invoke();
                }
            }
            else
            {
                Console.WriteLine("Failed to parse access level.");
                _state = ClientState.Abort;
                _isConnected = false;
                OnDisconnect?.Invoke();
            }
        }

        /// <summary>
        /// Writes data to the network stream.
        /// </summary>
        private async Task WriteAsync(CancellationToken cancellationToken)
        {
            while (_isConnected && !cancellationToken.IsCancellationRequested)
            {
                if (_outputBuffer.Length > 0)
                {
                    string dataToSend;
                    lock (_outputBuffer)
                    {
                        dataToSend = _outputBuffer.ToString();
                        _outputBuffer.Clear();
                    }

                    byte[] data = Encoding.ASCII.GetBytes(dataToSend);
#if DEBUG
                    Console.WriteLine($"Sending raw data ({data.Length} bytes): {BitConverter.ToString(data)}");
#endif
                    await _networkStream.WriteAsync(data, 0, data.Length, cancellationToken);
                    await _networkStream.FlushAsync(cancellationToken);
                }
                else
                {
                    await Task.Delay(100, cancellationToken);
                }
            }
        }
        /// <summary>
        /// Sends periodic keep-alive messages to the server.
        /// </summary>
        private async Task KeepAliveAsync(CancellationToken cancellationToken)
        {
            while (_isConnected && !cancellationToken.IsCancellationRequested)
            {
                if ((DateTime.Now - _lastKeepAlive).TotalSeconds > Constants.KeepAliveTimeout)
                {
                    await SendKeepAliveAsync();
                }

                await Task.Delay(500, cancellationToken);
            }
        }

        /// <summary>
        /// Sends the initial login message to the server.
        /// </summary>
        private async Task SendLoginAsync()
        {
            string loginMessage = BuildLoginMessage();
            await SendAsync(loginMessage);

            _state = ClientState.LoginPhase1;
        }

        /// <summary>
        /// Builds the login message.
        /// </summary>
        private string BuildLoginMessage()
        {
            var sb = new StringBuilder();
            sb.Append("CT:");
            sb.Append($"<VX>{Constants.FRNProtocolVersion}</VX>");
            sb.Append($"<EA>{_email}</EA>");
            sb.Append($"<PW>{_password}</PW>");
            sb.Append($"<ON>{_callSign}</ON>");
            sb.Append($"<CL>0</CL>");
            sb.Append($"<BC>{_type}</BC>"); // Adjusted as per documentation
            sb.Append($"<DS>{_description}</DS>");
            sb.Append($"<NN>{_country}</NN>");
            sb.Append($"<CT>{_city} - {_locator}</CT>");
            sb.Append($"<NT>{_network}</NT>");
            sb.Append("\r\n");
#if DEBUG
            Console.WriteLine($"Built login message: {sb}");
#endif
            return sb.ToString();
        }

        /// <summary>
        /// Sends a keep-alive message to the server.
        /// </summary>
        private async Task SendKeepAliveAsync()
        {
            await SendAsync("P\r\n");
            _lastKeepAlive = DateTime.Now;
#if DEBUG
            Console.WriteLine("Sent keep-alive to server.");
#endif
        }

        /// <summary>
        /// Sends data to the server.
        /// </summary>
        private async Task SendAsync(string data)
        {
            lock (_outputBuffer)
            {
                _outputBuffer.Append(data);
            }
        }

        /// <summary>
        /// Disconnects the client.
        /// </summary>
        public void Disconnect()
        {
            _isConnected = false;
            _cancellationTokenSource.Cancel();
            _networkStream?.Close();
            _tcpClient?.Close();
            _state = ClientState.Disconnected;

            OnDisconnect?.Invoke();
            Console.WriteLine("Client disconnected.");
        }

        // Other methods to handle sending messages, changing status, etc.

        /// <summary>
        /// Sends a text message to the server.
        /// </summary>
        public async Task SendMessageAsync(string recipientId, string message)
        {
            string messageToSend = $"TM:<ID>{recipientId}</ID><MS>{message}</MS>\r\n";
            await SendAsync(messageToSend);
            Console.WriteLine($"Sent message to {recipientId}: {message}");
        }

        /// <summary>
        /// Sends a status update to the server.
        /// </summary>
        public async Task SendStatusAsync(int status)
        {
            string statusMessage = $"ST:{status}\r\n";
            await SendAsync(statusMessage);
            Console.WriteLine($"Sent status update: {status}");
        }

        /// <summary>
        /// Requests to start transmitting voice data.
        /// </summary>
        public async Task RequestTransmitAsync()
        {
            await SendAsync("TX0\r\n");
            Console.WriteLine("Requested to start transmitting voice data.");
        }
    }

    /// <summary>
    /// Represents client information received from the server.
    /// </summary>
    public class ClientInfo
    {
        public string Status { get; set; }
        public string Muted { get; set; }
        public string Country { get; set; }
        public string City { get; set; }
        public string BandAndChannel { get; set; }
        public string ClientType { get; set; }
        public string CallsignAndUser { get; set; }
        public string ID { get; set; }
        public string Description { get; set; }
    }

    // Extension method to find index of a byte in a List<byte>
    public static class Extensions
    {
        public static int IndexOf(this List<byte> buffer, byte value)
        {
            for (int i = 0; i < buffer.Count; i++)
            {
                if (buffer[i] == value)
                    return i;
            }
            return -1;
        }
    }
}
