using System;
using System.Collections.Generic;
using System.IO.Ports;
using System.Linq;
using System.Threading;

namespace GatewayCommon
{
    public class GatewayControl : System.IDisposable
    {
        public delegate void StatusChangeHandler(bool alive);
        public delegate void CommandReceiveHandler(string command, string answer, string parameters, DateTime receiveDate);
        public delegate void CommandSendHandler(string command, DateTime sendDate);
        public delegate void IncommingCallHandler(string phoneNumber, DateTime incommingDate);
        public delegate void IncommingMessageHandler(string phoneNumber, string message, DateTime incommingDate);
        public delegate string PinRequestHandler();

        public event StatusChangeHandler StatusChange;
        public event CommandReceiveHandler CommandReceive;
        public event CommandSendHandler CommandSend;
        public event IncommingCallHandler IncommingCall;
        public event IncommingMessageHandler IncommingMessage;
        public event PinRequestHandler SetPin;

        private enum FindCommandEnum
        {
            NotFound,
            SuccessCommand,
            ErrorCommand
        }
        private enum CallAnswerEnum
        {
            BUSY,
            NO_CARRIER,
            NO_ANSWER,
            NO_DIALTONE,
            NONE
        }
        private struct ReceiveCommand
        {
            public string command, answer, parameter;
            public DateTime receiveDate;
        }
        private struct SendingCommand
        {
            public string command;
            public DateTime sendingDate;
        }

        private string receiveDataTemp = string.Empty;

        private bool isAlive = false;
        private Queue<ReceiveCommand> queueReceive;
        private Queue<SendingCommand> queueSending;
        private Thread commandService;
        private DeviceControl device;

        public bool IsAlive
        {
            get
            {
                return isAlive;
            }
        }

        public GatewayControl(string devicePort)
        {
            device = new DeviceControl(devicePort);
            this.Open();
        }
        public GatewayControl(SerialPort devicePort)
        {
            device = new DeviceControl(devicePort);
            this.Open();
        }
        ~GatewayControl()
        {
            this.Close();
        }
        public void Dispose()
        {
            this.Close();
        }
        public void Open()
        {
            this.queueReceive = new Queue<ReceiveCommand>();
            this.queueSending = new Queue<SendingCommand>();
            this.commandService = new Thread(new ThreadStart(CommandService));
            commandService.Start();
            this.device.CommandReceived += new DeviceControl.DeviceEventHandler(ReceivedData);

            if (device.IsAlive)
            {
                isAlive = true;
                if (this.TestCommunication())
                {
                    int i = 10;
                    while (this.GatewayPreSetting() == false && i >= 0)
                    {
                        i--;
                        Thread.Sleep(200);
                    }

                    if (StatusChange != null)
                        StatusChange(true);
                }
                else
                {
                    isAlive = false;
                }
            }
            isAlive = true;
        }
        public void Close()
        {
            if (StatusChange != null)
                StatusChange(false);
            isAlive = false;
            if (device.IsAlive)
                device.Close();
            if (commandService != null)
                if (commandService.IsAlive)
                    commandService.Abort();
        }

        #region Device communication methods

        private void ReceivedData(string incommingData)
        {
            string newData = incommingData;
            newData = newData.Replace("\0", string.Empty);
            newData = newData.Replace("\r\nRING\r\n", string.Empty);
            newData = newData.Replace("\r\n+CLIP: ", "NEWCALL\r\r\n");
            newData = newData.Replace("\r\n+CMT: ", "NEWSMS\r\r\n");
            newData = receiveDataTemp + newData;
            receiveDataTemp = string.Empty;
            if (!string.IsNullOrEmpty(newData))
            {
                if (newData.Split(new string[] { "\r\r\n" }, StringSplitOptions.RemoveEmptyEntries)[0] == "NEWSMS")
                {
                    newData = newData.Replace("\r\n", "\r\r\n");
                    newData = newData.Replace("\r\r\r\n", "\r\r\n");
                    newData = newData.Remove(newData.Length - 2, 1);
                }
                newData = newData.Replace("\r\n\r\n", "\r\r\n");

                if (newData[newData.Length - 1].ToString() == "\n" || (newData[newData.Length - 1].ToString() == " " && newData[newData.Length - 2].ToString() == ">"))
                {
                    receiveDataTemp = string.Empty;

                    foreach (string dataItem in newData.Replace("\r\r\n", "\r").Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries))
                    {
                        if (!string.IsNullOrEmpty(dataItem))
                        {
                            string[] receivedData = dataItem.Split(new string[] { "\r" }, StringSplitOptions.RemoveEmptyEntries);
                            switch (receivedData[0])
                            {
                                case "NORMAL POWER DOWN":
                                    if (StatusChange != null)
                                        StatusChange(false);
                                    isAlive = false;
                                    break;
                                case "NEWSMS":
                                    if (IncommingMessage != null)
                                        IncommingMessage(receivedData[1].Replace("\"", string.Empty).Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)[0], receivedData[2], DateTime.Now);
                                    break;
                                case "NEWCALL":
                                    device.SendData("ATH");
                                    if (IncommingCall != null)
                                        IncommingCall(receivedData[1].Replace("\"", string.Empty).Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)[0], DateTime.Now);
                                    break;
                                case "AT+CPIN?":
                                    if (receivedData[1].Contains("SIM PIN"))
                                    {
                                        if (SetPin != null)
                                        {
                                            string pin = SetPin();
                                            while (false == this.SendCommand("AT+CPIN=\"" + pin.ToString() + "\""))
                                            {
                                                Thread.Sleep(200);
                                            }
                                        }
                                    }
                                    break;
                                default:
                                    if (receivedData.Count() == 2)
                                    {
                                        AddReceiveDataToQueue(receivedData[0], receivedData[1]);
                                    }
                                    if (receivedData.Count() == 3)
                                    {
                                        AddReceiveDataToQueue(receivedData[0], receivedData[2], receivedData[1]);
                                    }
                                    break;
                            }
                        }
                    }
                }
                else
                {
                    receiveDataTemp += newData;
                }
            }
        }
        private void CommandService()
        {
            int waitSeconds = 0;
            int maxWait = 200;
            int commandsForFullSpeed = 5;

            while (true)
            {
                if (queueSending.Count > 0)
                {
                    if (device.IsAlive)
                    {
                        SendingCommand sc = queueSending.Dequeue();
                        if (!device.SendData(sc.command))
                            queueSending.Enqueue(sc);
                        else
                        {
                            if (CommandSend != null)
                                CommandSend(sc.command, sc.sendingDate);
                        }
                    }
                }
                if (queueSending.Count < commandsForFullSpeed)
                    waitSeconds = maxWait - queueSending.Count * (maxWait / commandsForFullSpeed);
                else
                    waitSeconds = 0;
                Thread.Sleep(waitSeconds);
            }
        }
        public bool SendCommand(string command)
        {
            return this.SendCommand(command, "\r");
        }
        public bool SendCommand(string command, string endChar)
        {
            if (!string.IsNullOrEmpty(command) /*&& command.ToUpper().Contains("AT")*/ && IsAlive)
            {
                SendingCommand sc = new SendingCommand();
                sc.command = command;
                sc.command += endChar;
                sc.sendingDate = DateTime.Now;
                queueSending.Enqueue(sc);
                if (CommandSend != null)
                    CommandSend(sc.command, sc.sendingDate);
                return true;
            }
            return false;
        }
        public bool SendCommand(string command, string[] successAnswer)
        {
            return this.SendCommand(command, "\r", successAnswer);
        }
        public bool SendCommand(string command, string endChar, string[] successAnswer)
        {
            if (!string.IsNullOrEmpty(command) /*&& command.ToUpper().Contains("AT")*/ && IsAlive)
            {
                SendingCommand sc = new SendingCommand();
                sc.command = command;
                sc.command += endChar;
                sc.sendingDate = DateTime.Now;
                queueSending.Enqueue(sc);
                if (CommandSend != null)
                    CommandSend(sc.command, sc.sendingDate);
                if (successAnswer == null)
                    return true;
                else
                {
                    switch (FindCommandAnswer(command, successAnswer))
                    {
                        case FindCommandEnum.SuccessCommand:
                            return true;
                        case FindCommandEnum.ErrorCommand:
                            return false;
                        case FindCommandEnum.NotFound:
                            return false;
                    }
                }
            }
            return false;
        }
        private bool GatewayPreSetting()
        {
            if (device.IsAlive)
            {
                if (!this.SendCommand("AT+CNMI=1,2,0,0,0", new string[] { "OK" }))
                    return false;
                if (!this.SendCommand("AT+CLIP=1", new string[] { "OK" }))
                    return false;
                if (!this.SendCommand("AT+CMGF=1", new string[] { "OK" }))
                    return false;
                if (!this.SendCommand("AT+CPIN?"))
                    return false;
                return true;
            }
            return false;
        }
        private void AddReceiveDataToQueue(string command, string answer)
        {
            AddReceiveDataToQueue(command, answer, string.Empty);
        }
        private void AddReceiveDataToQueue(string command, string answer, string parameter)
        {
            ReceiveCommand rc = new ReceiveCommand();
            rc.command = command;
            rc.answer = answer;
            if (!string.IsNullOrEmpty(parameter))
                rc.parameter = parameter;
            rc.receiveDate = DateTime.Now;
            queueReceive.Enqueue(rc);
            if (CommandReceive != null)
                CommandReceive(rc.command, rc.answer, rc.parameter, rc.receiveDate);
        }

        #endregion

        #region GMS command methods

        public bool SendSMS(string phoneNumber, string smsMessage, bool approve)
        {
            if (phoneNumber.Length == 9)
                phoneNumber = "+420" + phoneNumber;
            if (phoneNumber.Length == 12)
                phoneNumber = "+" + phoneNumber;

            if (approve)
            {
                for (double i = 0; i < Convert.ToDouble(smsMessage.Length) / 160.0; i = i + 1.0)
                {
                    if ((i + 1.0) * 160.0 < smsMessage.Length)
                    {
                        this.SendCommand("AT+CMGS=\"" + phoneNumber + "\"", string.Empty);
                        while (this.IsAnswerReceived(">") == false) ;
                        this.SendCommand(smsMessage.Substring(int.Parse(i.ToString()) * 160, 160), "\n" + ((char)(26)).ToString());
                    }
                    else
                    {
                        this.SendCommand("AT+CMGS=\"" + phoneNumber + "\"", string.Empty);
                        while (this.IsAnswerReceived(">") == false) ;
                        this.SendCommand(smsMessage.Substring(int.Parse(i.ToString()) * 160, smsMessage.Length - int.Parse(i.ToString()) * 160), "\n" + ((char)(26)).ToString());
                    }
                }

            }
            else
            {
                for (double i = 0; i < Convert.ToDouble(smsMessage.Length) / 160.0; i = i + 1.0)
                {
                    if ((i + 1.0) * 160.0 < smsMessage.Length)
                    {
                        this.SendCommand("AT+CMGS=\"" + phoneNumber + "\"", string.Empty);
                        while (this.IsAnswerReceived(">") == false) ;
                        this.SendCommand(smsMessage.Substring(int.Parse(i.ToString()) * 160, 160), "\n" + ((char)(26)).ToString());
                    }
                    else
                    {
                        this.SendCommand("AT+CMGS=\"" + phoneNumber + "\"", string.Empty);
                        while (this.IsAnswerReceived(">") == false) ;
                        this.SendCommand(smsMessage.Substring(int.Parse(i.ToString()) * 160, smsMessage.Length - int.Parse(i.ToString()) * 160), "\n" + ((char)(26)).ToString());
                    }
                }
            }

            return true;
        }
        public bool RingThePhone(string phoneNumber, bool approve)
        {
            long longPhoneNumber;
            if (!string.IsNullOrEmpty(phoneNumber) && long.TryParse(phoneNumber, out longPhoneNumber))
            {
                if (phoneNumber.Length == 9)
                    phoneNumber = "+420" + phoneNumber;
                if (phoneNumber.Length == 12)
                    phoneNumber = "+" + phoneNumber;

                if (approve)
                {
                    if (this.SendCommand("ATD" + phoneNumber + ";", new string[] { "OK", "NO ANSWER" }))
                    {
                        Thread.Sleep(10000);
                        switch (GetCallAnswer())
                        {
                            case CallAnswerEnum.BUSY:
                                return false;
                            case CallAnswerEnum.NO_ANSWER:
                                if (this.SendCommand("ATH", new string[] { "OK" }))
                                    return true;
                                break;
                            case CallAnswerEnum.NO_CARRIER:
                                return false;
                            case CallAnswerEnum.NO_DIALTONE:
                                return false;
                            default:
                                if (this.SendCommand("ATH", new string[] { "OK" }))
                                    return true;
                                break;
                        }
                    }
                }
                else
                {
                    this.SendCommand("ATD" + phoneNumber + ";", new string[] { "OK", "NO ANSWER", "BUSY", "NO CARRIER", "NO DIALTONE" });
                    Thread.Sleep(10000);
                    this.SendCommand("ATH", new string[] { "OK" });
                    return true;
                }
            }
            return false;
        }
        public string[] GetGatewayInformation()
        {
            string[] information = { string.Empty, string.Empty, string.Empty, string.Empty };
            this.SendCommand("AT+CGMI");
            information[0] = this.GetParameter("AT+CGMI", new string[] { "OK" });
            //Thread.Sleep(500);
            this.SendCommand("AT+CGMM");
            information[1] = this.GetParameter("AT+CGMM", new string[] { "OK" });
            //Thread.Sleep(500);
            this.SendCommand("AT+CGMR");
            information[2] = this.GetParameter("AT+CGMR", new string[] { "OK" });
            //Thread.Sleep(500);
            this.SendCommand("AT+CGSN");
            information[3] = this.GetParameter("AT+CGSN", new string[] { "OK" });
            return information;
        }
        public int GetSignalStatus()
        {
            this.SendCommand("AT+CSQ");
            return -113 + int.Parse(this.GetParameter("AT+CSQ", new string[] { "OK" }).Replace("+CSQ:", " ").Trim(' ').Split(',')[0]) * 2;
        }
        public int GetBatteryStatus()
        {
            this.SendCommand("AT+CBC");
            return int.Parse(this.GetParameter("AT+CBC", new string[] { "OK" }).Replace("+CBC", " ").Trim(' ').Split(',')[1]);
        }
        public bool GetPowerStatus()
        {
            this.SendCommand("AT+CBC");
            if (this.GetParameter("AT+CBC", new string[] { "OK" }).Replace("+CBC:", " ").Trim(' ').Split(',')[0].Trim(' ') == "1")
                return true;
            return false;
        }
        public bool TestCommunication()
        {
            if (this.SendCommand("AT", new string[] { "OK" }))
                return true;
            return false;
        }

        #endregion

        #region AT result get methods

        private string GetParameter(string command, string[] successAnswer)
        {
            int i = 10;
            while (i >= 0)
            {
                if (queueReceive.Count > 0)
                {
                    for (int j = 0; j < queueReceive.Count; j++)
                    {
                        if (queueReceive.Count > 0)
                        {
                            ReceiveCommand receiveCommand = queueReceive.Dequeue();
                            if (command.ToUpper() == receiveCommand.command.ToUpper())
                            {
                                foreach (string item in successAnswer)
                                {
                                    if (item == receiveCommand.answer.ToUpper())
                                        return receiveCommand.parameter;
                                }
                            }
                            else
                                queueReceive.Enqueue(receiveCommand);
                        }
                    }
                }
                Thread.Sleep(500);
                i--;
            }
            return string.Empty;
        }
        private bool IsAnswerReceived(string command)
        {
            if (queueReceive.Count > 0)
            {
                for (int j = 0; j < queueReceive.Count; j++)
                {
                    if (queueReceive.Count > 0)
                    {
                        ReceiveCommand receiveCommand = queueReceive.Dequeue();
                        if (command.Replace(" ", string.Empty) == receiveCommand.answer.Replace(" ", string.Empty))
                            return true;
                        else
                            queueReceive.Enqueue(receiveCommand);
                    }
                }
            }
            return false;
        }
        private FindCommandEnum FindCommandAnswer(string command, string[] successAnswer)
        {
            int i = 10;
            while (i >= 0)
            {
                if (queueReceive.Count > 0)
                {
                    for (int j = 0; j < queueReceive.Count; j++)
                    {
                        if (queueReceive.Count > 0)
                        {
                            ReceiveCommand receiveCommand = queueReceive.Dequeue();
                            if (receiveCommand.command != null)
                            {
                                if (command.ToUpper() == receiveCommand.command.ToUpper())
                                {
                                    foreach (string item in successAnswer)
                                    {
                                        if (item == receiveCommand.answer.ToUpper())
                                            return FindCommandEnum.SuccessCommand;
                                    }
                                }
                            }
                            queueReceive.Enqueue(receiveCommand);
                            return FindCommandEnum.ErrorCommand;
                        }
                    }
                }
                i--;
                Thread.Sleep(200);
            }
            return FindCommandEnum.NotFound;
        }
        private CallAnswerEnum GetCallAnswer()
        {
            int i = 1;
            while (i >= 0)
            {
                if (queueReceive.Count > 0)
                {
                    for (int j = 0; j < queueReceive.Count; j++)
                    {
                        if (queueReceive.Count > 0)
                        {
                            ReceiveCommand receiveCommand = queueReceive.Dequeue();
                            if (receiveCommand.answer != null)
                            {
                                switch (receiveCommand.answer.Trim(' '))
                                {
                                    case "BUSY":
                                        return CallAnswerEnum.BUSY;
                                    case "NO ANSWER":
                                        return CallAnswerEnum.NO_ANSWER;
                                    case "NO CARRIER":
                                        return CallAnswerEnum.NO_CARRIER;
                                    case "NO DIALTONE":
                                        return CallAnswerEnum.NO_DIALTONE;
                                }
                            }
                            queueReceive.Enqueue(receiveCommand);
                        }
                    }
                }
                Thread.Sleep(500);
                i--;
            }
            return CallAnswerEnum.NONE;
        }

        #endregion

    }
}
