using System;
using Microsoft.SPOT;
using System.IO.Ports;
using System.Threading;

namespace RemoteCam.Server
{
    public class XBee : IDisposable
    {
        private const int BufferSize = 100;
        private SerialPort port;
        private byte frameId = 0;

        public  delegate void EventHandler(object sender, EventArgs e);

        public event EventHandler DataReceived;

        public XBee(SerialPort port)
        {
            this.port = port;
        }

        public void Start()
        {
            port.Open();
            port.DataReceived += port_DataReceived;

            //byte[] data = new byte[200];
            //Send(data, 0, data.Length);
        }

        private byte GetNextFrameId()
        {
            if (frameId == 255)
                frameId = 1;
            else
                frameId++;
            return frameId;
        }

        void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            if (port.BytesToRead > 0)
                OnDataReceived(new EventArgs());
        }

        private void Reset()
        {
            bool done = false;
            while (!done)
            {
                port.DataReceived -= port_DataReceived;
                try
                {
                    byte[] data = { 0x08, GetNextFrameId(), (byte)'F', (byte)'R' };
                    byte[] frame = CreateAtCommandFrame(data);
                    port.Write(frame, 0, frame.Length);
                    byte[] result = ReceiveFrame();
                    while (port.BytesToRead > 0)
                    {
                        byte[] status = ReceiveFrame();
                    }
                    
                    done = true;
                }
                catch (Exception ex)
                { }
                finally
                {
                    port.DataReceived += port_DataReceived;
                }
            }
            Thread.Sleep(300);
        }

        public bool Send(byte[] buffer, int start, int length)
        {
            Debug.Print("Request of sending " + length);
            port.DataReceived -= port_DataReceived;
            try
            {
                int sent = 0;
                while (sent < length)
                {
                    int chunkSize = System.Math.Min(BufferSize, length - sent);
                    TXStatus status = SendChunk(buffer, start + sent, chunkSize);
                    sent += chunkSize;
                }
                Debug.Print("Done sending");
                return true;
            }
            finally
            {
                port.DataReceived += port_DataReceived;
            }
        }

        private void OnDataReceived(EventArgs e)
        {
            if (DataReceived != null)
                DataReceived(this, e);
        }

        public TXStatus SendChunk(byte[] buffer, int start, int length)
        {
            // Debug.Print("Sending chunk start " + start + " of length " + length);
            bool done = false;
            byte[] frame = CreateTransmitFrame(buffer, start, length);
            while (!done)
            {
                port.Write(frame, 0, frame.Length);
                // Thread.Sleep(20);
                // Debug.Print("Chunck sent");

                try
                {
                    byte[] confirmation = ReceiveFrame();
                    // should check the result etc.
                    done = confirmation[0] == 0x89 && confirmation[2] == 0x00;
                }
                catch (Exception ex)
                {
                    Debug.Print("Failed because " + ex.Message);
                    // Reset();
                }
            }
            // Debug.Print("Done chunk");
            return TXStatus.Success;
        }

        private byte[] CreateTransmitFrame(byte[] data, int start, int length)
        {
            byte[] frameData = new byte[length + 5];
            frameData[0] = 0x01; // TX
            frameData[1] = GetNextFrameId();  // Frame ID
            frameData[2] = 0x0; // Address MSB
            frameData[3] = 0x22; // Address LSB
            frameData[4] = 0x00;  // options
            Array.Copy(data, start, frameData, 5, length);

            byte[] result = new byte[frameData.Length + 4]; //
            // start delimiter
            result[0] = 0x7E;
            // length MSB, LSB
            result[1] = 0; // always sending less than 256 bytes
            result[2] = (byte)frameData.Length;
            Array.Copy(frameData, 0, result, 3, frameData.Length);
            result[result.Length - 1] = CalculateChecksum(frameData);
            return result;
        }

        private byte[] CreateAtCommandFrame(byte[] data)
        {
            byte[] result = new byte[data.Length + 4]; //
            // start delimiter
            result[0] = 0x7E;
            // length MSB, LSB
            result[1] = 0; // always sending less than 256 bytes
            result[2] = (byte)data.Length;
            Array.Copy(data, 0, result, 3, data.Length);
            result[result.Length - 1] = CalculateChecksum(data);
            return result;
        }

        private byte CalculateChecksum(byte[] data)
        {
            int checksum = data[0];
            for (int i = 1; i < data.Length; i++)
            {
                checksum += data[i];
                checksum &= 0xFF;
            }
            return (byte)(0xFF - (byte)checksum);
        }

        public void Dispose()
        {
            if (port != null)
            {
                port.Dispose();
                port.DataReceived -= port_DataReceived;
            }
        }

        public byte[] ReceiveFrame()
        {
            byte[] frameHeader = ReceiveBytes(3);
            int dataLength = frameHeader[2];
            byte[] data = ReceiveBytes(dataLength);
            // Debug.Print("Response " + data[0] + " frame " + data[1] + " value " + data[2]);
            byte[] crc = ReceiveBytes(1);
            return data;
        }

        public byte[] ReceiveBytes(int length)
        {
            byte[] buffer = new byte[length];
            int total = 0;
            while (total < buffer.Length)
            {
                int read = port.Read(buffer, total, length - total);
                total += read;
            }
            return buffer;
        }
    }


}
