using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using Inspectron.HawkEye.UDPB;
using Newtonsoft.Json;

namespace Inspectron.HawkEye.Protocol
{
    public class ImageClientTCP : IDisposable
    {
        private static readonly object connectionLock = new object();
        private readonly IPEndPoint _endpoint;
        private readonly IPAddress _adapter;

        private TcpListener _imageSocket;
        private TcpClient _commandSocket;
        private bool _isConnected = true;

        public ImageClientTCP(IPEndPoint endpoint,IPAddress adapter)
        {
            _endpoint = endpoint;
            _adapter = adapter;
 
        }

        public void Dispose()
        {
            _isConnected = false;
            _commandSocket.Dispose();
        }
        byte[] _commandBuffer = new byte[1500];
        private TcpClient _lastClient;
        public bool SupportsCalibration { get; set; }
        public void Connect()
        {
            lock (connectionLock)
            {
                _commandSocket=new TcpClient(new IPEndPoint(_adapter, 0));
                Console.WriteLine($"Bind on {_adapter?.ToString()}");
                
                _commandSocket.Connect(_endpoint);
                var port = UDPBSocket.FindFreePort(_adapter);
                Console.WriteLine("Connected");

                _imageSocket = new TcpListener(_adapter, port);
                _imageSocket.Start();

                Console.WriteLine("TCP started");

                var bytesPort = BitConverter.GetBytes(port);
                _commandSocket.Client.SendData(new[]
                    {(byte) ECommand.Connect, bytesPort[0], bytesPort[1], bytesPort[2], bytesPort[3]});

                var receivedLen = _commandSocket.Client.Receive(_commandBuffer);
                Console.WriteLine("received answer length:"+receivedLen);
                var b = new byte[receivedLen - 1];
                Array.Copy(_commandBuffer, 1, b, 0, b.Length);
                var settingsString = Encoding.UTF8.GetString(b);
                SettingsReceived(JsonConvert.DeserializeObject<CameraSettings>(settingsString));
                if (SupportsCalibration)
                {
                    
                    _commandSocket.Client.SendData(new[] {(byte) ECommand.GetCalibration});
                    receivedLen = _commandSocket.Client.Receive(_commandBuffer);
                    if (_commandBuffer[0] == (byte) ECommand.OK)
                    {
                        Console.WriteLine($"calibration received {receivedLen} bytes");
                        var c = new byte[receivedLen - 1];
                        Array.Copy(_commandBuffer, 1, c, 0, c.Length);
                        var calibrationString = Encoding.UTF8.GetString(c);
                        CalibrationReceived(JsonConvert.DeserializeObject<List<Dictionary<double,double>>>(calibrationString));

                    }
                    else
                    {
                        Console.WriteLine("no calibration received");
                    }
                }
                

                var th = new Thread(ReceiveLoop);
                th.Start();
            }
        }

        public event Action<byte[]> ImageReceived = delegate { };
        public event Action<CameraSettings> SettingsReceived = delegate { };
        public event Action<List<Dictionary<double,double>>> CalibrationReceived = delegate { };


        public void Trigger()
        {
            _commandSocket.Client.SendData(new[] {(byte) ECommand.Trigger});
        }

        public void ApplySettings(CameraSettings cameraSettings)
        {
            var data = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(cameraSettings));
            var databytes = new byte[1000];
            databytes[0] = (byte) ECommand.Settings;
            Array.Copy(data, 0, databytes, 1, data.Length);

            _commandSocket.Client.SendData(databytes);
            byte[] ok = new byte[1];
            _commandSocket.Client.Receive(ok);
        }

        public void StartContinuous()
        {
            _commandSocket.Client.SendData(new[] {(byte) ECommand.StartContinuous});
        }

        public void StopContinuous()
        {
            _commandSocket.Client.SendData(new[] {(byte) ECommand.StopContinuous});
        }

        public void SaveSettingsOnCamera()
        {
            _commandSocket.Client.SendData(new[] {(byte) ECommand.SaveSettings});
        }

        private byte[] _imageBuffer = new byte[10*1024*1024];
        private void ReceiveLoop()
        {
            while (_isConnected)
            {
                _lastClient = _imageSocket.AcceptTcpClient();
                while (true)
                {
                    int received=0;
                    
                    try
                    {
                        _lastClient.Client.Receive(_imageBuffer, 0, 1,
                            SocketFlags.None);
                        Process(_imageBuffer);
                    }
                    catch
                    {
                        break;
                    }

                    
                }
                

                
            }
        }

        public void SaveCalibration(List<Dictionary<double,double>> calibration)
        {
            var data = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(calibration));
            var databytes = new byte[1500];
            databytes[0] = (byte)ECommand.SaveCalibration;
            Array.Copy(data, 0, databytes, 1, data.Length);

            _commandSocket.Client.SendData(databytes);
            byte[] ok = new byte[1];
            _commandSocket.Client.Receive(ok);
        }
        
        private void Process(byte[] data)
        {
            switch ((EData) data[0])
            {
                case EData.Image:
                    _lastClient.Client.Receive(_imageBuffer, 1, 4,
                        SocketFlags.None);
                    var imageSize = BitConverter.ToInt32(_imageBuffer,1);
                    var received = 0;
                    do
                    {
                        received += _lastClient.Client.Receive(_imageBuffer, 5+ received, imageSize- received,
                            SocketFlags.None);
                    } while (received < imageSize);

                    var b = new byte[imageSize];
                    Array.Copy(data, 5, b, 0, b.Length);
                    ImageReceived(b);
                    break;
                
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }
    }
}