using EVCB_OCPP.Packet.Messages;
using EVCB_OCPP.Packet.Messages.Basic;
using EVCB_OCPP20.Packet.Features;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NLog;
using OCPPServer.Protocol;
using System;
using I20Confirmation = EVCB_OCPP20.Packet.Messages.IConfirmation;
using I20Request = EVCB_OCPP20.Packet.Messages.IRequest;

namespace EVCB_OCPP.WSServer.Message
{
    /// <summary>
    /// 實現 OCPP20 基本傳送規範,
    /// 1.訊息 基本格式,將訊息包裝成 Call 、CallResult、CallError 三種格式
    /// 2.OCPP 定義的傳送規則:交易相關的訊息必須依照時序性傳送,一個傳完才能接著送下一個(忽略規則 由Center System定義)
    /// </summary>
    internal class OCPP20MessageHandler
    {
        static protected ILogger logger = NLog.LogManager.GetCurrentClassLogger();

        #region 傳送 or 解析訊息需要欄位
        private const int INDEX_MESSAGEID = 0;
        private const int INDEX_UNIQUEID = 1;
        internal const int TYPENUMBER_CALL = 2;
        private const int INDEX_CALL_ACTION = 2;
        private const int INDEX_CALL_PAYLOAD = 3;

        internal const int TYPENUMBER_CALLRESULT = 3;
        private const int INDEX_CALLRESULT_PAYLOAD = 2;

        internal const int TYPENUMBER_CALLERROR = 4;
        private const int INDEX_CALLERROR_ERRORCODE = 2;
        private const int INDEX_CALLERROR_DESCRIPTION = 3;
        private const int INDEX_CALLERROR_PAYLOAD = 4;

        private const string CALL_FORMAT = "[2,\"{0}\",\"{1}\",{2}]";
        private const string CALLRESULT_FORMAT = "[3,\"{0}\",{1}]";
        private const string CALLERROR_FORMAT = "[4,\"{0}\",\"{1}\",\"{2}\",{3}]";
        private const string DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'";
        private const string DATE_FORMAT_WITH_MS = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
        #endregion




        /// <summary>
        /// 將收到的封包做基本的拆解分成 Call 、CallResult、CallError
        /// </summary>
        /// <param name="client"></param>
        /// <param name="data"></param>
        /// <returns></returns>
        internal MessageResult AnalysisReceiveData(ClientData client, string data)
        {
            MessageResult result = new MessageResult();
            try
            {

                var msg = Parse(data);
                if (msg != null)
                {

                    result.UUID = msg.Id;
                    switch (msg.TypeId)
                    {
                        case TYPENUMBER_CALL:
                            {
                                //只有CallMessage 才有在RawData有Action
                                BasicMessageResult baseResult = UnPackPayloadbyCall(msg.Action, msg.Payload.ToString());
                                Actions action = Actions.None;
                                Enum.TryParse<Actions>(msg.Action, out action);
                                result.Action = msg.Action;
                                if (baseResult.Request20 != null)
                                {
                                    if (baseResult.Request20.Validate())
                                    {
                                        result.Id = TYPENUMBER_CALL;
                                        result.Message = baseResult.Request20;

                                    }
                                    else
                                    {

                                        string replyMsg = BasicMessageHandler.GenerateCallError(msg.Id, OCPPErrorCodes.OccurenceConstraintViolation.ToString(),
                                             OCPPErrorDescription.OccurenceConstraintViolation);
                                        result.Id = TYPENUMBER_CALL;
                                        result.Message = baseResult.Request20;
                                        result.Success = false;
                                        result.CallErrorMsg = replyMsg;
                                        result.Exception = new Exception("Validation Failed");

                                    }
                                }
                                else
                                {

                                    string replyMsg = BasicMessageHandler.GenerateCallError(msg.Id, OCPPErrorCodes.OccurenceConstraintViolation, OCPPErrorDescription.OccurenceConstraintViolation);
                                    result.Id = TYPENUMBER_CALL;
                                    result.Message = baseResult.Request20;
                                    result.Success = false;
                                    result.CallErrorMsg = replyMsg;
                                    result.Exception = baseResult.Exception;
                                }

                            }
                            break;
                        case TYPENUMBER_CALLRESULT:
                            {
                                BasicMessageResult baseResult = UnPackPayloadbyCallResult(client.queue20, msg.Id, msg.Payload.ToString());

                                if (baseResult.Confirmation20 != null)
                                {

                                    if (baseResult.Confirmation20.Validate())
                                    {
                                        result.Id = TYPENUMBER_CALLRESULT;
                                        result.Message = baseResult.Confirmation20;
                                        result.Action = baseResult.Confirmation20.GetRequest().Action;
                                        //return data
                                    }
                                    else
                                    {
                                        string replyMsg = BasicMessageHandler.GenerateCallError(msg.Id, OCPPErrorCodes.OccurenceConstraintViolation.ToString(),
                                      OCPPErrorDescription.OccurenceConstraintViolation);
                                        result.Id = TYPENUMBER_CALLRESULT;
                                        result.Message = baseResult.Confirmation20;
                                        result.Success = false;
                                        result.CallErrorMsg = replyMsg;
                                        result.Exception = new Exception("Validate Failed");
                                    }
                                }
                                else
                                {
                                    string replyMsg = BasicMessageHandler.GenerateCallError(msg.Id, OCPPErrorCodes.OccurenceConstraintViolation.ToString(),
                                   OCPPErrorDescription.OccurenceConstraintViolation);
                                    result.Id = TYPENUMBER_CALLRESULT;
                                    result.Message = baseResult.Confirmation20;
                                    result.Success = false;
                                    result.CallErrorMsg = replyMsg;
                                    result.Exception = baseResult.Exception;
                                }

                            }
                            break;
                        case TYPENUMBER_CALLERROR:
                            {
                                result.Id = TYPENUMBER_CALLERROR;
                                // var sentRequest = UnPackPayloadbyCallError(client.queue, msg.Id);

                                //if (sentRequest != null)
                                //{
                                //    I20Request request = sentRequest as I20Request;
                                //    result.Action = request.Action;

                                //    result.Message = sentRequest;
                                //    result.ReceivedErrorCode = string.Format("ErrorMsg {0}:{1}", ((CallErrorMessage)msg).ErrorCode, ((CallErrorMessage)msg).ErrorDescription);
                                //}


                            }
                            break;
                        default:
                            break;

                    }

                    // if (msg != null) Console.WriteLine(string.Format("Receieved Message : {0}", msg.ToString()));

                }
            }
            catch (Exception ex)
            {
                if (string.IsNullOrEmpty(result.UUID))
                {
                    result.UUID = data.Substring(4, 39);
                    result.UUID = result.UUID.Split(new string[] { "\"," }, StringSplitOptions.None)[0];
                }
                result.Success = false;
                result.Exception = ex;
            }
            return result;
        }

        #region 解析收到的訊息
        /// <summary>
        /// Parse data to OCPP Basic Message
        /// </summary>
        /// <param name="message"></param>
        /// <returns></returns>
        private BaseMessage Parse(string message)
        {
            try
            {
                if (message.StartsWith("[4,\""))
                {
                    message = message.Replace('{', '"');
                    message = message.Replace('}', '"');
                }
                var array = JsonConvert.DeserializeObject<JArray>(message);
                BaseMessage msg = null;
                switch ((int)array[INDEX_MESSAGEID])
                {
                    case TYPENUMBER_CALL:
                        {
                            CallMessage call = new CallMessage();
                            call.Action = array[INDEX_CALL_ACTION].ToString();
                            call.Payload = array[INDEX_CALL_PAYLOAD].ToString().Replace("\r\n", "");
                            msg = call;
                        }
                        break;
                    case TYPENUMBER_CALLRESULT:
                        {
                            CallResultMessage callResult = new CallResultMessage();
                            callResult.Payload = array[INDEX_CALLRESULT_PAYLOAD].ToString().Replace("\r\n", "");
                            msg = callResult;
                        }
                        break;
                    case TYPENUMBER_CALLERROR:
                        {
                            CallErrorMessage callError = new CallErrorMessage();
                            callError.ErrorCode = array[INDEX_CALLERROR_ERRORCODE].ToString();
                            callError.ErrorDescription = array[INDEX_CALLERROR_DESCRIPTION].ToString();
                            callError.ErrorDetails = array[INDEX_CALLERROR_PAYLOAD].ToString().Replace("\r\n", "");
                            msg = callError;
                        }
                        break;
                    default:
                        throw new Exception("Message Type notSupported");


                }
                msg.Id = array[INDEX_UNIQUEID].ToString();
                return msg;
            }
            catch (Exception ex)
            {
                throw new Exception(string.Format("Parse Error=> {0} Problem: {0}", message, ex.Message));

            }



        }


        private BasicMessageResult UnPackPayloadbyCall(string action, string payload)
        {

            BasicMessageResult result = new BasicMessageResult();
            try
            {

                result.Request20 = (I20Request)(JsonConvert.DeserializeObject(payload, Type.GetType("EVCB_OCPP20.Packet.Messages." + action + "Request,EVCB_OCPP20.Packet")));

            }
            catch (Exception ex)
            {
                result.Exception = ex;
                logger.Error(string.Format("[{0}]UnPackPayloadbyCall Ex: {1}", action, ex.Message), "UnPack");

            }

            return result;


        }

        private BasicMessageResult UnPackPayloadbyCallResult(EVCB_OCPP20.Packet.Messages.Basic.Queue requestQueue, string uniqueId, string payload)
        {
            BasicMessageResult result = new BasicMessageResult();
            try
            {
                I20Request request = requestQueue.RestoreRequest(uniqueId);
                I20Confirmation confrim = JsonConvert.DeserializeObject(payload, Type.GetType("EVCB_OCPP20.Packet.Messages." + request.Action + "Response,EVCB_OCPP20.Packet")) as I20Confirmation;
                confrim.SetRequest(request);
                result.Confirmation20 = confrim;

            }
            catch (Exception ex)
            {
                result.Exception = ex;
                logger.Error(string.Format("UnPackPayloadbyCallResult Data:[{0},{1}] Ex: {2}", uniqueId, payload, ex.ToString()), "UnPack");

            }
            return result;

        }



        #endregion



    }
}