using CareFusion.Mosaic.Converters.Wwks2.Types;
using CareFusion.Mosaic.Interfaces.Converters;
using CareFusion.Mosaic.Interfaces.Messages;
using CareFusion.Mosaic.Interfaces.Messages.Input;
using CareFusion.Mosaic.Interfaces.Types.Packs;
using CareFusion.Mosaic.Interfaces.Types.Stock;
using Rowa.Mosaic.Shared.Converters;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Xml.Serialization;

namespace CareFusion.Mosaic.Converters.Wwks2.Messages.Input
{
    /// <summary>
    /// Class which represents the WWKS 2.0 InfeedInputMessage message envelope.
    /// </summary>
    [XmlType(TypeName = "WWKS")]
    [ExcludeFromCodeCoverage]
    public class InfeedInputMessageEnvelope : EnvelopeBase, IMessageConversion
    {
        [XmlElement]
        public InfeedInputMessage InfeedInputMessage { get; set; }

        /// <summary>
        /// Translates this object instance into a Mosaic message.
        /// </summary>
        /// <param name="converterStream">The converter stream instance which request the message conversion.</param>
        /// <returns>
        /// The Mosaic message representation of this object.
        /// </returns>
        public MosaicMessage ToMosaicMessage(IConverterStream converterStream)
        {
            return this.InfeedInputMessage.ToMosaicMessage(converterStream);
        }
    }

    /// <summary>
    /// Class which represents the WWKS 2.0 InitiateInputMessage message.
    /// <see cref="https://portalrowa.carefusion.com/Unternehmen/Entwicklung/TechCom/Writing/P-010-044-B-I-388-DEU.docx" />
    /// </summary>
    public class InfeedInputMessage : MessageBase, IMessageConversion
    {
        #region Enums

        private enum InfeedInputStatus
        {
            Aborted,
            Completed
        }

        private enum Reasons
        {
            NotAllowed,
            ExpiryDateRequired,
            InvalidExpiryDate,
            StockLocationRequired,
            FridgeRequired,
            BatchNumberRequired,
            MeasurementError,
            PackAcknowledged,
            InputBroken,
            NoSpaceInMachine,
            NoSpaceOnBelt,
            NoPackPlaced,
            TimeoutWaitingForPack,
            UnknownTenant,
            UnknownStockLocation,
            TaskCancelled,
            EmergencyStop,
            InternalError
        }

        #endregion

        #region Properties

        [XmlElement]
        public Details Details { get; set; }

        [XmlElement]
        public Article[] Article { get; set; }

        #endregion

        /// <summary>
        /// Initializes a new instance of the <see cref="InfeedInputMessage"/> class.
        /// </summary>
        [ExcludeFromCodeCoverage]
        public InfeedInputMessage()
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="InfeedInputMessage"/> class.
        /// </summary>
        /// <param name="message">The message to use for initialization.</param>
        public InfeedInputMessage(MosaicMessage message)
        {
            var msg = (Interfaces.Messages.Input.InfeedInputMessage)message;

            this.Id = msg.ID;
            this.Destination = msg.Destination;
            this.Source = msg.Source;
            this.Details = new Types.Details()
            {
                InfeedNumber = msg.InfeedNumber.ToString()
            };

            if (msg.Status == Interfaces.Messages.Input.InfeedInputState.Finished)
            {
                this.Details.Status = InfeedInputStatus.Completed.ToString();
            }
            else
            {
                this.Details.Status = InfeedInputStatus.Aborted.ToString();
                this.Details.Reason = GetRejectReason(msg.RejectReason);
                this.Details.Description = msg.RejectReasonText;
            }

            if (msg.Articles.Count == 0)
            {
                return;
            }

            int packIndex = 0;
            this.Article = new Article[msg.Articles.Count];

            for (int i = 0; i < this.Article.Length; ++i)
            {
                this.Article[i] = new Article()
                {
                    Id = TextConverter.EscapeInvalidXmlChars(msg.Articles[i].Code),
                    Name = TextConverter.EscapeInvalidXmlChars(msg.Articles[i].Name),
                    PackagingUnit = TextConverter.EscapeInvalidXmlChars(msg.Articles[i].PackagingUnit),
                    DosageForm = TextConverter.EscapeInvalidXmlChars(msg.Articles[i].DosageForm),
                    MaxSubItemQuantity = msg.Articles[i].MaxSubItemQuantity.ToString()
                };

                if ((string.IsNullOrEmpty(this.Article[i].Id)) &&
                    (string.IsNullOrEmpty(this.Article[i].Name)) &&
                    (string.IsNullOrEmpty(this.Article[i].PackagingUnit)) &&
                    (string.IsNullOrEmpty(this.Article[i].DosageForm)) &&
                    (msg.Articles[i].MaxSubItemQuantity == 0))
                {
                    this.Article[i].Id = null;
                    this.Article[i].Name = null;
                    this.Article[i].PackagingUnit = null;
                    this.Article[i].DosageForm = null;
                    this.Article[i].MaxSubItemQuantity = null;
                }

                foreach (var pack in msg.Packs)
                {
                    if (pack.RobotArticleID == msg.Articles[i].ID)
                    {
                        if (this.Article[i].Pack == null)
                        {
                            this.Article[i].Pack = new List<Types.Pack>();
                        }

                        this.Article[i].Pack.Add(new Types.Pack()
                        {
                            Index = packIndex.ToString(),
                            Id = ((ulong)pack.ID).ToString(),
                            DeliveryNumber = TextConverter.EscapeInvalidXmlChars(pack.DeliveryNumber),
                            BatchNumber = TextConverter.EscapeInvalidXmlChars(pack.BatchNumber),
                            ExternalId = TextConverter.EscapeInvalidXmlChars(pack.ExternalID),
                            SerialNumber = TextConverter.EscapeInvalidXmlChars(pack.SerialNumber),
                            ExpiryDate = TypeConverter.ConvertDateNull(pack.ExpiryDate),
                            ExpiryDateSource= pack.ExpiryDateSource.ToString(),
                            StockInDate = TypeConverter.ConvertDateNull(msg.Packs[i].StockInDate),
                            ScanCode = TextConverter.EscapeInvalidXmlChars(msg.Packs[i].ScanCode),
                            SubItemQuantity = pack.SubItemQuantity.ToString(),
                            Depth = pack.Depth.ToString(),
                            Width = pack.Width.ToString(),
                            Height = pack.Height.ToString(),
                            Shape = pack.Shape.ToString(),
                            StockLocationId = TextConverter.EscapeInvalidXmlChars(pack.StockLocationID),
                            MachineLocation = TextConverter.EscapeInvalidXmlChars(pack.MachineLocation),
                            State = ((pack.IsAvailable == false) || (pack.IsBlocked == true)) ? "NotAvailable" : "Available",
                            GUID = pack.Guid
                        });

                        packIndex++;
                    }
                }
            }
        }

        /// <summary>
        /// Translates this object instance into a Mosaic message.
        /// </summary>
        /// <param name="converterStream">The converter stream instance which request the message conversion.</param>
        /// <returns>
        /// The Mosaic message representation of this object.
        /// </returns>
        public MosaicMessage ToMosaicMessage(IConverterStream converterStream)
        {
            var message = new Interfaces.Messages.Input.InfeedInputMessage(converterStream);

            message.ID = this.Id;
            message.Destination = this.Destination;
            message.Source = this.Source;
            message.Status = InfeedInputState.None;
            message.RejectReason = GetRejectReason();

            if (this.Details != null)
            {
                message.InfeedNumber = TypeConverter.ConvertInt(this.Details.InfeedNumber);

                if (string.Compare(this.Details.Status, InfeedInputStatus.Completed.ToString(), true) == 0)
                    message.Status = InfeedInputState.Finished;
                else
                    message.Status = InfeedInputState.RejectedByMachine;

                if (!string.IsNullOrEmpty(this.Details.Description))
                    message.RejectReasonText = this.Details.Description;
            }

            if (this.Article != null)
            {
                foreach (var article in this.Article)
                {
                    message.Articles.Add(new Interfaces.Types.Articles.RobotArticle()
                    {
                        Code = TextConverter.UnescapeInvalidXmlChars(article.Id),
                        Name = TextConverter.UnescapeInvalidXmlChars(article.Name),
                        PackagingUnit = TextConverter.UnescapeInvalidXmlChars(article.PackagingUnit),
                        DosageForm = TextConverter.UnescapeInvalidXmlChars(article.DosageForm),
                        MaxSubItemQuantity = TypeConverter.ConvertInt(article.MaxSubItemQuantity)
                    });

                    if (article.Pack != null)
                    {
                        foreach (var pack in article.Pack)
                        {
                            var msgPack = new Interfaces.Types.Packs.RobotPack()
                            {
                                ID = (long)TypeConverter.ConvertULong(pack.Id),
                                DeliveryNumber = TextConverter.UnescapeInvalidXmlChars(pack.DeliveryNumber),
                                BatchNumber = TextConverter.UnescapeInvalidXmlChars(pack.BatchNumber),
                                ExternalID = TextConverter.EscapeInvalidXmlChars(pack.ExternalId),
                                SerialNumber = TextConverter.EscapeInvalidXmlChars(pack.SerialNumber),
                                ExpiryDate = TypeConverter.ConvertDate(pack.ExpiryDate),
                                ExpiryDateSource = TypeConverter.ConvertEnum<ExpiryDateSource>(pack.ExpiryDateSource, Interfaces.Types.Stock.ExpiryDateSource.Unknown),
                                StockInDate = TypeConverter.ConvertDate(pack.StockInDate),
                                ScanCode = TextConverter.UnescapeInvalidXmlChars(pack.ScanCode),
                                SubItemQuantity = TypeConverter.ConvertInt(pack.SubItemQuantity),
                                Depth = TypeConverter.ConvertInt(pack.Depth),
                                Width = TypeConverter.ConvertInt(pack.Width),
                                Height = TypeConverter.ConvertInt(pack.Height),
                                Shape = TypeConverter.ConvertEnum<PackShape>(pack.Shape, PackShape.Cuboid),
                                StockLocationID = string.IsNullOrEmpty(pack.StockLocationId) ? string.Empty : TextConverter.UnescapeInvalidXmlChars(pack.StockLocationId),
                                MachineLocation = string.IsNullOrEmpty(pack.MachineLocation) ? string.Empty : TextConverter.UnescapeInvalidXmlChars(pack.MachineLocation),
                                IsAvailable = string.Compare(pack.State, "Available") == 0,
                                Guid = pack.GUID
                            };

                            message.Packs.Add(msgPack);
                        }
                    }
                }
            }

            return message;
        }

        /// <summary>
        /// Translates the Mosaic internal reject reason into a TIOSysMan compatible one.
        /// </summary>
        /// <param name="rejectReason">The reject reason to translate.</param>
        /// <returns>
        /// TIOSysMan compatible reject reason as string.
        /// </returns>
        private string GetRejectReason(InfeedInputRejectReason rejectReason)
        {
            switch (rejectReason)
            {
                case InfeedInputRejectReason.None: return string.Empty;
                case InfeedInputRejectReason.Rejected: return Reasons.NotAllowed.ToString();
                case InfeedInputRejectReason.RejectedNoExpiryDate: return Reasons.ExpiryDateRequired.ToString();
                case InfeedInputRejectReason.RejectedInvalidExpiryDate: return Reasons.InvalidExpiryDate.ToString();
                case InfeedInputRejectReason.RejectedNoBatchNumber: return Reasons.BatchNumberRequired.ToString();
                case InfeedInputRejectReason.RejectedNoStockLocation: return Reasons.StockLocationRequired.ToString();
                case InfeedInputRejectReason.RejectedInvalidStockLocation: return Reasons.UnknownStockLocation.ToString();
                case InfeedInputRejectReason.FridgeMissing: return Reasons.FridgeRequired.ToString();                
                case InfeedInputRejectReason.PackAcknowledged: return Reasons.PackAcknowledged.ToString();
                case InfeedInputRejectReason.InputBroken: return Reasons.InputBroken.ToString();
                case InfeedInputRejectReason.NoSpaceInMachine: return Reasons.NoSpaceInMachine.ToString();
                case InfeedInputRejectReason.NoSpaceOnBelt: return Reasons.NoSpaceOnBelt.ToString();
                case InfeedInputRejectReason.NoPackPlaced: return Reasons.NoPackPlaced.ToString();
                case InfeedInputRejectReason.TimeoutWaitingForPack: return Reasons.TimeoutWaitingForPack.ToString();
                case InfeedInputRejectReason.UnknownTenant: return Reasons.UnknownTenant.ToString();
                case InfeedInputRejectReason.UnknownStockLocation: return Reasons.UnknownStockLocation.ToString();
                case InfeedInputRejectReason.InternalError: return Reasons.InternalError.ToString();
                case InfeedInputRejectReason.EmergencyStop: return Reasons.EmergencyStop.ToString();

                case InfeedInputRejectReason.UnknownPackDimensions:
                case InfeedInputRejectReason.MeasurementError: return Reasons.MeasurementError.ToString();

                case InfeedInputRejectReason.RejectedNoPickingIndicator:
                case InfeedInputRejectReason.RejectedNoSerialNumber:
                    return Reasons.NotAllowed.ToString();
            }

            return Reasons.NotAllowed.ToString();
        }


        /// <summary>
        /// Translates the current TIOSysMan compatible reject reason into a Mosaic internal one.
        /// </summary>
        /// <returns>
        /// Mosaic internal reject reason.
        /// </returns>
        private InfeedInputRejectReason GetRejectReason()
        {
            if (string.IsNullOrEmpty(this.Details?.Reason))
                return InfeedInputRejectReason.None;

            var rejectReason = TypeConverter.ConvertEnum<Reasons>(this.Details.Reason, Reasons.InputBroken);

            switch (rejectReason)
            {
                case Reasons.NotAllowed: return InfeedInputRejectReason.Rejected;
                case Reasons.ExpiryDateRequired: return InfeedInputRejectReason.RejectedNoExpiryDate;
                case Reasons.InvalidExpiryDate: return InfeedInputRejectReason.RejectedInvalidExpiryDate;
                case Reasons.BatchNumberRequired: return InfeedInputRejectReason.RejectedNoBatchNumber;
                case Reasons.StockLocationRequired: return InfeedInputRejectReason.RejectedNoStockLocation;
                case Reasons.FridgeRequired: return InfeedInputRejectReason.FridgeMissing;
                case Reasons.MeasurementError: return InfeedInputRejectReason.MeasurementError;
                case Reasons.PackAcknowledged: return InfeedInputRejectReason.PackAcknowledged;
                case Reasons.InputBroken: return InfeedInputRejectReason.InputBroken;
                case Reasons.NoSpaceInMachine: return InfeedInputRejectReason.NoSpaceInMachine;
                case Reasons.NoSpaceOnBelt: return InfeedInputRejectReason.NoSpaceOnBelt;
                case Reasons.NoPackPlaced: return InfeedInputRejectReason.NoPackPlaced;
                case Reasons.TimeoutWaitingForPack: return InfeedInputRejectReason.TimeoutWaitingForPack;
                case Reasons.UnknownTenant: return InfeedInputRejectReason.UnknownTenant;
                case Reasons.UnknownStockLocation: return InfeedInputRejectReason.UnknownStockLocation;
                case Reasons.EmergencyStop: return InfeedInputRejectReason.EmergencyStop;
                case Reasons.InternalError: return InfeedInputRejectReason.InternalError;
            }

            return InfeedInputRejectReason.InputBroken;
        }
    }
}
