using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO.Ports;
using System.Linq;
using System.Text;

namespace ActuBoardAPI
{

    public class ActuBoardInterface : IActuBoard
    {

        public event Action<bool> OnConnectionChanged;
        public event Action<String> OnMessageReceived;
        
        // status event
        public event Action<StatusCode> OnStatusUpdate;

        private char EOL = '\r';
        private int _baud = 115200;
        private int _com = -1;

        private SerialPort _io;

		
		/**
		* connect to ActuBoard
		*/
        public int Connect()
        {

            // COM Port not set
            if (_com <= 0)
            {
                OnStatusUpdate?.Invoke(StatusCode.ComPortInvalid);
                return -1;
            }

            // start SerialPort connection
            Console.WriteLine("Connecting to port " + _com);
            _io = new SerialPort("COM" + _com)
            {
                BaudRate = _baud
            };
            OnStatusUpdate?.Invoke(StatusCode.ActuBoardIsConnecting);
            _io.Open();


            // if could not open SerialPort set error
            if (!_io.IsOpen)
            {
                //OnStatusUpdate(this, StatusCode.ActuBoardNotConnected);
                OnStatusUpdate?.Invoke(StatusCode.ActuBoardNotConnected);
                return -2;
            }

            OnConnectionChanged?.Invoke(true);
            //OnConnectionChanged?.Invoke(true); // default .NET
            OnStatusUpdate?.Invoke(StatusCode.ActuBoardConnected); // Unity .NET

            Console.WriteLine("Connected!");
            
            _io.DataReceived += _io_DataReceived;

            return 1;
        }

        private void _io_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            int count = _io.BytesToRead;
            byte[] msg = new byte[count];
            _io.Read(msg, 0, count);
            System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
            String smsg = enc.GetString(msg);

            if (smsg.Contains('\a'))
                OnStatusUpdate?.Invoke(StatusCode.HardwareCommandError);
            else
                OnStatusUpdate?.Invoke(StatusCode.NoError);

            OnMessageReceived?.Invoke(smsg);
        }


        public int Disconnect()
        {
            if (_io == null)
                return -1;
            if (!_io.IsOpen)
                return -2;

            _io.Close();

            OnConnectionChanged?.Invoke(false);
            return 1;
        }

        public bool IsConnected()
        {
            if (_io != null)
                return _io.IsOpen;
            else
                return false;
        }


		/**
		* enable or disable output of _every_ ActuBoard Hub at once
		* (e.g., useful as emergency switch)
		*/
        public void EnableOutput(bool enable)
        {
            if (enable)
                WriteCommand("O01" + EOL);
            else
                WriteCommand("O00" + EOL);
        }


        public int EnableBlinking(int cid)
        {
			WriteCommand("G" + Helper.IntToHex(cid) + "01" + EOL);
			return 0;
        }
		
        public int DisableBlinking(int cid)
        {
			WriteCommand("G" + Helper.IntToHex(cid) + "00" + EOL);
			return 0;
        }
		
        public int EnableBlinking(int[] cid)
        {
			// currently not needed, implemented soon
            throw new NotImplementedException();
        }
		
        public int DisableBlinking(int[] cid)
        {
			// currently not needed, implemented soon
            throw new NotImplementedException();
        }
        public void SendCustomCommand(string cmd)
        {
            WriteCommand(cmd + EOL);
        }


        public int SetChannel(int cid, int pwm)
        {
            SetChannel(Helper.IntToHex(cid), Helper.IntToHex(pwm));
            return 0;
        }

        public int SetChannel(string cid, string pwm)
        {
            WriteCommand("S" + cid + pwm + EOL);
            return 0;
        }

        public int GetChannel(int cid)
        {
            WriteCommand("R" + cid.ToString("X2") + EOL);
            return 0;
        }

        public int ListDevices()
        {
            WriteCommand("L" + EOL);
            return 0;
        }

        public int SetBlinkingCycle(int cycle)
        {
			WriteCommand("i" + Helper.IntToHex(cycle) + EOL);
			return 0;
        }

        public int SetBlinkingInterval(int interval)
        {
			WriteCommand("I" + Helper.IntToHex(interval) + EOL);
			return 0;
        }

        public int SetChannels(int[] cid, int[] pwm)
        {
            if (cid.Length > 0 && cid.Length == pwm.Length)
            {
                String channels = Helper.IntArrayToNumHexString(cid);
                String pwms = Helper.IntArrayToNumHexString(pwm, false);
                WriteCommand("n" + channels + pwms + EOL);
                return 0;
            }
            return -1;
        }

        public int SetChannels(int[] cid, int pwm)
        {
            if (cid.Length > 0)
            {
                String channels = Helper.IntArrayToNumHexString(cid);
                WriteCommand("m" + channels + Helper.IntToHex(pwm) + EOL);
                return 0;
            }
            return -1;
        }

        public void ToggleDebug()
        {
            WriteCommand("D" + EOL);
        }

        public int SetComPort(int comport)
        {
            if (comport > 0)
                _com = comport;
            else
                return -1;
            return _com;
        }


        private void WriteCommand(String command)
        {
            if (_io != null && _io.IsOpen)
            {
                Console.WriteLine("CMD: " + command);
                _io.Write(command);
            }
            else
            {
                Console.WriteLine("Could not send command (IO is null or closed): " + command);
            }
        }

    }
}
