using CareFusion.Mosaic.Converters.Wwks2.Messages;
using CareFusion.Mosaic.Interfaces.Converters;
using CareFusion.Mosaic.Interfaces.Messages.Input;
using CareFusion.Mosaic.Interfaces.Types.Input;
using CareFusion.Mosaic.Interfaces.Types.Packs;
using StorageSystem.Simulator.Factories;
using StorageSystem.Simulator.Interfaces;
using StorageSystemSimulator.Helper;
using System;
using System.Collections.Generic;
using System.Linq;

namespace StorageSystemSimulator.Cores
{
    public delegate void InputResponseReceivedEventHandler(object sender, StockInputResponse inputResponse);
    public delegate void InfeedInputRequestReceivedEventHandler(object sender, InfeedInputRequest infeedInputRequest);
    public delegate void InfeedInputResponseSentEventHandler(object sender, InfeedInputResponse infeedInputResponse);
    public delegate void InfeedInputPlacePackRequestSentEventHandler(object sender, InfeedInputPlacePackRequest infeedInputPlacePackRequest);
    public delegate void InfeedInputPlacePackResponseReceivedEventHandler(object sender, InfeedInputPlacePackResponse infeedInputPlacePackResponse);
    public delegate void InfeedInputMessageSentEventHandler(object sender, InfeedInputMessage infeedInputMessage);

    public class SimulatorInputCore
    {
        private int subscriberID;
        private List<IConverterStream> converterStreamList;
        private StorageSystemStock stock;
        private bool infeedStockInputWaitForInputResponse = false;

        public SimulatorInputCore()
        {
            _inputProcessTracker = new InputProcessTracker();
        }

        public void Initialize(List<IConverterStream> converterStreamList, StorageSystemStock stock)
        {
            this.converterStreamList = converterStreamList;
            this.stock = stock;
        }

        public void ScanPack(string tenantId, string stockLocationId, bool setPickingIndicator, bool isDeliveryInput, List<RobotPack> packs, string componentId = "", string componentName = "")
        {
            int inputProcessId = _inputProcessTracker.AddInputProcess(_infeedInputRequest?.ID);

            foreach (IConverterStream stream in this.converterStreamList)
            {
                if (string.IsNullOrEmpty(tenantId))
                {
                    if (!string.IsNullOrEmpty(stream.TenantID))
                    {
                        continue;
                    }
                }
                else if (tenantId != stream.TenantID)
                {
                    continue;
                }

                var inputRequest = new StockInputRequest(stream)
                {
                    ID = MessageId.Next,
                    Source = this.subscriberID, // default
                    Destination = int.Parse(stream.Destination),
                    SetPickingIndicator = setPickingIndicator,
                    IsDeliveryInput = isDeliveryInput,
                    ComponentId = componentId,
                    ComponentName = componentName
                };

                this._inputProcessTracker.AddRequestToInputProcess(inputProcessId, inputRequest.ID);

                foreach (RobotPack pack in packs)
                {
                    pack.TenantID = tenantId;

                    if (!string.IsNullOrEmpty(stockLocationId))
                    {
                        pack.StockLocationID = stockLocationId;
                    }

                    if (int.TryParse(pack.MachineLocation, out var machineLocation))
                    {
                        inputRequest.Source = machineLocation;
                    }

                    pack.Guid = Guid.NewGuid();

                    inputRequest.Packs.Add(pack);
                }

                stream.Write(inputRequest);
            }
        }

        public void SendInputMessage(string tenantId, StockInputResponse inputResponse, List<RobotPack> inputRequestPackList, bool isLoaded, string componentId = "", string componentName = "")
        {
            _inputProcessTracker.RemoveInputProcess(inputResponse.ID);

            if (isLoaded)
            {
                this.stock.LoadInput(inputResponse);
            }

            var inputRequestPackListIsEmpty = inputRequestPackList == null || inputRequestPackList.Count == 0;

            foreach (IConverterStream stream in this.converterStreamList)
            {
                if (tenantId != stream.TenantID)
                {
                    continue;
                }

                var inputMessage = new StockInputMessage(stream)
                {
                    IsNewDelivery = inputResponse.IsDeliveryInput,
                    ComponentId = componentId,
                    ComponentName = componentName
                };

                inputMessage.AdoptHeader(inputResponse);

                inputMessage.Articles.AddRange(inputResponse.Articles.ToArray());

                inputMessage.Packs.AddRange(inputResponse.Packs.ToArray());

                for (int i = 0; i < inputMessage.Packs.Count; i++)
                {
                    if (!inputRequestPackListIsEmpty)
                    {
                        inputMessage.Packs[i].SerialNumber = inputRequestPackList[i].SerialNumber;
                        inputMessage.Packs[i].Depth = inputRequestPackList[i].Depth;
                        inputMessage.Packs[i].Width = inputRequestPackList[i].Width;
                        inputMessage.Packs[i].Height = inputRequestPackList[i].Height;
                        inputMessage.Packs[i].Shape = inputRequestPackList[i].Shape; //IT cannot define the shape.
                        inputMessage.Packs[i].MachineLocation = inputRequestPackList[i].MachineLocation; //IT cannot define the Machine location.
                        inputMessage.Packs[i].TenantID = inputRequestPackList[i].TenantID; //IT cannot define the tenant.
                        inputMessage.Packs[i].ExpiryDateSource = inputRequestPackList[i].ExpiryDateSource;
                        inputMessage.Packs[i].Guid = inputRequestPackList[i].Guid;
                    }

                    inputMessage.Handlings.Add(inputMessage.Packs[i], isLoaded ? StockInputHandlingType.Completed : StockInputHandlingType.Aborted);
                }

                stream.Write(inputMessage);
            }
        }

        /// <summary>
        /// Sends a StockInputMessage specialized for <strong>StockDeliveries</strong>.
        /// </summary>
        public void SendInputMessage(string tenantId, StockDelivery stockDelivery, StockDeliveryItem stockDeliveryItem, bool isLoaded)
        {

            foreach (IConverterStream stream in this.converterStreamList)
            {
                if (tenantId != stream.TenantID)
                {
                    continue;
                }

                var inputMessage = new StockInputMessage(stream)
                {
                    IsNewDelivery = true,
                    TenantID = tenantId,

                };

                IRobotArticleFactory robotArticleFactory = new RobotArticleFactory();
                inputMessage.Articles.Add(robotArticleFactory.Create(stockDeliveryItem));

                IRobotPackFactory robotPackFactory = new RobotPackFactory();
                inputMessage.Packs.Add(robotPackFactory.Create(stockDelivery, stockDeliveryItem));


                for (int i = 0; i < inputMessage.Packs.Count; i++)
                {
                    inputMessage.Handlings.Add(inputMessage.Packs[i], isLoaded ? StockInputHandlingType.Completed : StockInputHandlingType.Aborted);
                }

                stream.Write(inputMessage);
            }
        }

        /// <summary>
        /// Sends an input message when user has selected to transfer a packet to another machine location.
        /// </summary>
        /// <param name="tenantId">The tenant identifier.</param>
        /// <param name="inputResponse">The input response.</param>
        /// <param name="inputRequestPackList">The input request pack list.</param>
        public void SendInputMessageOnTransfer(string tenantId, StockInputResponse inputResponse, List<RobotPack> inputRequestPackList)
        {
            var inputRequestPackListIsEmpty = inputRequestPackList?.FirstOrDefault() == null;

            foreach (IConverterStream stream in this.converterStreamList)
            {
                if (tenantId != stream.TenantID)
                {
                    continue;
                }

                var inputMessage = new StockInputMessage(stream)
                {
                    IsNewDelivery = inputResponse.IsDeliveryInput
                };

                inputMessage.AdoptHeader(inputResponse);

                inputMessage.Articles.AddRange(inputResponse.Articles.ToArray());

                inputMessage.Packs.AddRange(inputResponse.Packs.ToArray());

                for (int i = 0; i < inputMessage.Packs.Count; i++)
                {
                    if (!inputRequestPackListIsEmpty)
                    {
                        inputMessage.Packs[i].Depth = inputRequestPackList[i].Depth;
                        inputMessage.Packs[i].Width = inputRequestPackList[i].Width;
                        inputMessage.Packs[i].Height = inputRequestPackList[i].Height;
                        inputMessage.Packs[i].Shape = inputRequestPackList[i].Shape; //IT cannot define the shape.
                        inputMessage.Packs[i].MachineLocation = inputRequestPackList[i].MachineLocation; //IT cannot define the Machine location.
                        inputMessage.Packs[i].TenantID = inputRequestPackList[i].TenantID; //IT cannot define the tenant.
                        inputMessage.Packs[i].ExpiryDateSource = inputRequestPackList[i].ExpiryDateSource;
                        inputMessage.Packs[i].Guid = inputRequestPackList[i].Guid;
                    }

                    inputMessage.Handlings.Add(inputMessage.Packs[i], StockInputHandlingType.Completed);
                }

                stream.Write(inputMessage);
            }
        }

        public void ProcessInputResponse(StockInputResponse inputResponse)
        {
            _inputProcessTracker.TrackInputResponse(inputResponse);

            if (infeedStockInputWaitForInputResponse)
            {
                if (AutoSendPackPlaceRequest)
                {
                    SendInfeedInputPlacePackRequest();
                }
                infeedStockInputWaitForInputResponse = false;
            }
            else
            {
                this.DoInputResponseReceived(inputResponse);
            }
        }

        public void SetSubscriberID(int subscriberID)
        {
            this.subscriberID = subscriberID;
        }

        private void DoInputResponseReceived(StockInputResponse inputResponse)
        {
            if (InputResponseReceived == null)
                return;

            if (_inputProcessTracker.IsInputProcessHandled(inputResponse.ID))
                return;

            this.InputResponseReceived(this, inputResponse);
        }

        public event InputResponseReceivedEventHandler InputResponseReceived;

        #region Infeed Input

        private InfeedInputRequest _infeedInputRequest = null;

        public void ProcessInfeedInputRequest(InfeedInputRequest infeedInputRequest)
        {
            _infeedInputRequest = infeedInputRequest;
            this.DoInfeedInputRequestReceived(infeedInputRequest);
            var infeedInputResponse = new InfeedInputResponse();
            infeedInputResponse.AdoptHeader(infeedInputRequest);
            infeedInputResponse.InfeedNumber = infeedInputRequest.InfeedNumber;
            infeedInputResponse.ExternalOrderNumber = infeedInputRequest.ExternalOrderNumber;
            infeedInputResponse.RejectReason = InfeedInputRejectReason.None;
            infeedInputResponse.RejectReasonText = string.Empty;

            switch (AcceptInfeedRequestResult)
            {
                case InfeedRequestResultType.NoReply:
                    {
                        return;
                    }
                case InfeedRequestResultType.Accept:
                    {
                        infeedInputResponse.Status = InfeedInputState.InQueue;
                        infeedInputResponse.Packs.AddRange(infeedInputRequest.Packs);
                        // verify mandatory parameters
                        if ((infeedInputRequest.Packs.Count == 0) ||
                            (infeedInputRequest.Packs[0].Width <= 0) ||
                            (infeedInputRequest.Packs[0].Height <= 0) ||
                            (infeedInputRequest.Packs[0].Depth <= 0))
                        {
                            infeedInputResponse.Status = InfeedInputState.RejectedByMachine;
                            infeedInputResponse.RejectReason = InfeedInputRejectReason.UnknownPackDimensions;
                            infeedInputResponse.RejectReasonText = "Invalid or missing pack dimensions.";

                        }
                        else if (string.IsNullOrEmpty(infeedInputRequest.Packs[0].ScanCode))
                        {
                            infeedInputResponse.Status = InfeedInputState.RejectedByMachine;
                            infeedInputResponse.RejectReason = InfeedInputRejectReason.Rejected;
                            infeedInputResponse.RejectReasonText = "No pack scan code defined.";
                        }

                        break;
                    }
                case InfeedRequestResultType.Reject:
                    {
                        infeedInputResponse.Status = this.InfeedInputRequestState;
                        infeedInputResponse.RejectReason = this.InfeedInputRequestRejectReason;
                        infeedInputResponse.RejectReasonText = this.InfeedRejectReasonText;
                        break;
                    }
            }

            infeedInputResponse.ConverterStream.Write(infeedInputResponse);
            this.DoInfeedInputResponseSent(infeedInputResponse);

            if (infeedInputResponse.Status != InfeedInputState.InQueue) return;

            infeedStockInputWaitForInputResponse = true;
            ScanPack(InputTenant, InputStockLocation, false, false, infeedInputRequest.Packs);
        }

        public void SendInfeedInputPlacePackRequest()
        {
            if (_infeedInputRequest is null) return;
            _inputRequestPackList = new List<RobotPack>();
            _inputRequestPackList.AddRange(_infeedInputRequest.Packs);
            var infeedInputPlacePackRequest = new InfeedInputPlacePackRequest();
            infeedInputPlacePackRequest.AdoptHeader(_infeedInputRequest);
            infeedInputPlacePackRequest.InfeedNumber = _infeedInputRequest.InfeedNumber;
            _infeedInputRequest.ConverterStream.Write(infeedInputPlacePackRequest);
            DoInfeedInputPlacePackRequestSent(infeedInputPlacePackRequest);
        }

        public void AbortInfeedInput()
        {
            if (_infeedInputRequest is null) return;
            var itSystemMessage = new InfeedInputMessage();
            itSystemMessage.AdoptHeader(_infeedInputRequest);
            itSystemMessage.InfeedNumber = _infeedInputRequest.InfeedNumber;
            itSystemMessage.Status = InfeedInputState.RejectedByMachine;
            itSystemMessage.RejectReason = InfeedInputRejectReason.TaskCancelled;
            itSystemMessage.RejectReasonText = "Storage System aborts input.";
            _infeedInputRequest.ConverterStream.Write(itSystemMessage);
            DoInfeedInputMessageSent(itSystemMessage);
        }

        public void ProcessInfeedInputResponse(InfeedInputResponse infeedInputReponse)
        {

        }

        public void ProcessInfeedInputPlacePackResponse(InfeedInputPlacePackResponse infeedInputReponse)
        {
            DoInfeedInputPlacePackResponseReceived(infeedInputReponse);
            var itSystemMessage = new InfeedInputMessage();
            itSystemMessage.AdoptHeader(infeedInputReponse);
            itSystemMessage.InfeedNumber = infeedInputReponse.InfeedNumber;
            switch (infeedInputReponse.Status)
            {
                case InfeedInputPlacePackState.PacksPlaced:
                    {
                        switch (InfeedInputMessageResult)
                        {
                            case InfeedInputMessageResultType.NoMessage:
                                {
                                    return;
                                }
                            case InfeedInputMessageResultType.Successful:
                                {
                                    infeedStockInputWaitForInputResponse = true;
                                    var response = _inputProcessTracker.GetInputResponseByInfeedInputId(infeedInputReponse.ID);
                                    itSystemMessage.Articles.AddRange(response.Articles);
                                    itSystemMessage.Packs.AddRange(_inputRequestPackList);
                                    this.SendInputMessage(InputTenant, response, _inputRequestPackList, true);

                                    itSystemMessage.Status = InfeedInputState.Finished;
                                    break;
                                }
                            case InfeedInputMessageResultType.RejectedByMachine:
                                {
                                    itSystemMessage.Status = InfeedInputState.RejectedByItSystem;
                                    itSystemMessage.RejectReason = InfeedInpuFailureReason;
                                    break;
                                }
                        }
                        break;
                    }
                default:
                    {
                        itSystemMessage.Status = InfeedInputState.RejectedByItSystem;
                        itSystemMessage.RejectReason = InfeedInputRejectReason.Rejected;
                        itSystemMessage.RejectReasonText = "IT System aborts input.";
                        break;
                    }
            }

            infeedInputReponse.ConverterStream.Write(itSystemMessage);
            DoInfeedInputMessageSent(itSystemMessage);
        }

        private void DoInfeedInputRequestReceived(InfeedInputRequest infeedInputRequest)
        {
            if (this.InfeedInputRequestReceived != null)
            {
                this.InfeedInputRequestReceived(this, infeedInputRequest);
            }
        }

        private void DoInfeedInputResponseSent(InfeedInputResponse infeedInputResponse)
        {
            if (this.InfeedInputResponseSent != null)
            {
                this.InfeedInputResponseSent(this, infeedInputResponse);
            }
        }

        private void DoInfeedInputPlacePackRequestSent(InfeedInputPlacePackRequest infeedInputPlacePackRequest)
        {
            if (this.InfeedInputPlacePackRequestSent != null)
            {
                this.InfeedInputPlacePackRequestSent(this, infeedInputPlacePackRequest);
            }
        }

        private void DoInfeedInputPlacePackResponseReceived(InfeedInputPlacePackResponse infeedInputPlacePackResponse)
        {
            if (this.InfeedInputPlacePackResponseReceived != null)
            {
                this.InfeedInputPlacePackResponseReceived(this, infeedInputPlacePackResponse);
            }
        }

        private void DoInfeedInputMessageSent(InfeedInputMessage infeedInputMessage)
        {
            if (this.InfeedInputMessageSent != null)
            {
                this.InfeedInputMessageSent(this, infeedInputMessage);
            }
        }

        public event InfeedInputRequestReceivedEventHandler InfeedInputRequestReceived;
        public event InfeedInputResponseSentEventHandler InfeedInputResponseSent;
        public event InfeedInputPlacePackRequestSentEventHandler InfeedInputPlacePackRequestSent;
        public event InfeedInputPlacePackResponseReceivedEventHandler InfeedInputPlacePackResponseReceived;
        public event InfeedInputMessageSentEventHandler InfeedInputMessageSent;

        public InfeedRequestResultType AcceptInfeedRequestResult { get; set; } = InfeedRequestResultType.Accept;
        public bool AutoSendPackPlaceRequest { get; set; } = true;
        public InfeedInputState InfeedInputRequestState { get; set; }
        public InfeedInputRejectReason InfeedInputRequestRejectReason { get; set; }
        public string InfeedRejectReasonText { get; set; }
        public InfeedInputMessageResultType InfeedInputMessageResult { get; set; } = InfeedInputMessageResultType.Successful;
        public InfeedInputRejectReason InfeedInpuFailureReason { get; set; }
        public string InputTenant { get; set; }
        public string InputStockLocation { get; set; }
        private List<RobotPack> _inputRequestPackList;
        private readonly InputProcessTracker _inputProcessTracker;

        public enum InfeedRequestResultType
        {
            NoReply,
            Accept,
            Reject
        }
        public enum InfeedInputMessageResultType
        {
            NoMessage,
            Successful,
            RejectedByMachine
        }

        #endregion Infeed Input
    }
}
