using CareFusion.Lib.StorageSystem.Attributes;
using CareFusion.Lib.StorageSystem.Helpers.Enums;
using CareFusion.Lib.StorageSystem.Interfaces.Messages.Input;
using Rowa.Mosaic.Shared.Converters;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Xml.Serialization;

namespace CareFusion.Lib.StorageSystem.Wwks2.Messages.Input
{
    public class InputResponsePack : IInputResponsePack
    {
        #region Private members

        private List<string> _allowedSerializationProperties { get; set; }

        private string _index { get; set; }

        private string _scanCode { get; set; }

        private string _deliveryNumber { get; set; }

        private string _batchNumber { get; set; }

        private string _externalId { get; set; }

        private string _serialNumber { get; set; }

        private string _expiryDate { get; set; }

        private string _expiryDateSource { get; set; }

        private string _subItemQuantity { get; set; }

        private string _stockLocationId { get; set; }

        private string _machineLocation { get; set; }

        #endregion

        #region Constructors

        public InputResponsePack()
        {
        }

        public InputResponsePack(List<string> allowedSerializationProperties)
        {
            if (allowedSerializationProperties != null)
            {
                this._allowedSerializationProperties = allowedSerializationProperties;
            }
        }

        #endregion

        #region IInputResponsePack specific properties

        /// <inheritdoc/>
        [XmlAttribute]
        [UserSelectableProperty]
        public int Index
        {
            get => TypeConverter.ConvertInt(this._index);
            set => this._index = value >= 0 
                ? value.ToString()
                : default;
        }

        /// <inheritdoc/>
        [XmlAttribute]
        [UserSelectableProperty]
        public string ScanCode
        {
            get => this._scanCode;
            set => this._scanCode = TextConverter.EscapeInvalidXmlChars(value);
        }

        /// <inheritdoc/>
        [XmlAttribute]
        [UserSelectableProperty]
        public string DeliveryNumber
        {
            get => this._deliveryNumber;
            set => this._deliveryNumber = TextConverter.EscapeInvalidXmlChars(value);
        }

        /// <inheritdoc/>
        [XmlAttribute]
        [UserSelectableProperty]
        public string BatchNumber
        {
            get => this._batchNumber;
            set => this._batchNumber = TextConverter.EscapeInvalidXmlChars(value);
        }

        /// <inheritdoc/>
        [XmlAttribute]
        [UserSelectableProperty]
        public string ExternalId
        {
            get => this._externalId;
            set => this._externalId = TextConverter.EscapeInvalidXmlChars(value);
        }

        /// <inheritdoc/>
        [XmlAttribute]
        [UserSelectableProperty]
        public string SerialNumber
        {
            get => this._serialNumber;
            set => this._serialNumber = TextConverter.EscapeInvalidXmlChars(value);
        }

        /// <inheritdoc/>
        [XmlAttribute]
        [UserSelectableProperty]
        public DateTime ExpiryDate
        {
            get => TypeConverter.ConvertDate(this._expiryDate);
            set => this._expiryDate = value != DateTime.MinValue
                ? TypeConverter.ConvertDateNull(value)
                : default;
        }

        /// <inheritdoc/>
        [XmlAttribute]
        public ExpiryDateSource ExpiryDateSource
        {
            get => TypeConverter.ConvertEnum(this._expiryDateSource, ExpiryDateSource.Unknown);
            set
            {
                try
                {
                    this._expiryDateSource = TextConverter.EscapeInvalidXmlChars(value.ToString());
                }
                catch
                {
                    this._expiryDateSource = TextConverter.EscapeInvalidXmlChars(ExpiryDateSource.Unknown.ToString());
                }
            }
        }

        /// <inheritdoc/>
        [XmlAttribute]
        [UserSelectableProperty]
        public int SubItemQuantity
        {
            get => TypeConverter.ConvertInt(this._subItemQuantity);
            set => this._subItemQuantity = value >= 0
                ? value.ToString()
                : default;
        }

        /// <inheritdoc/>
        [XmlAttribute]
        [UserSelectableProperty]
        public string StockLocationId
        {
            get => this._stockLocationId;
            set => this._stockLocationId = TextConverter.EscapeInvalidXmlChars(value);
        }

        /// <inheritdoc/>
        [XmlAttribute]
        [UserSelectableProperty]
        public string MachineLocation
        {
            get => this._machineLocation;
            set => this._machineLocation = TextConverter.EscapeInvalidXmlChars(value);
        }

        /// <inheritdoc/>
        [XmlElement]
        public InputResponsePackHandling Handling { get; set; }

        #endregion

        #region Serialization properties

        [XmlIgnore]
        public bool IndexSpecified => this.IsPropertySpecified(nameof(this.Index), this._index != null);

        [XmlIgnore]
        public bool ScanCodeSpecified => this.IsPropertySpecified(nameof(this.ScanCode), this._scanCode != null);

        [XmlIgnore]
        public bool DeliveryNumberSpecified => this.IsPropertySpecified(nameof(this.DeliveryNumber), this._deliveryNumber != null);

        [XmlIgnore]
        public bool BatchNumberSpecified => this.IsPropertySpecified(nameof(this.BatchNumber), this._batchNumber != null);

        [XmlIgnore]
        public bool ExternalIdSpecified => this.IsPropertySpecified(nameof(this.ExternalId), this._externalId != null);

        [XmlIgnore]
        public bool SerialNumberSpecified => this.IsPropertySpecified(nameof(this.SerialNumber), this._serialNumber != null);

        [XmlIgnore]
        public bool ExpiryDateSpecified => this.IsPropertySpecified(nameof(this.ExpiryDate), this._expiryDate != null);

        [XmlIgnore]
        public bool ExpiryDateSourceSpecified => this.IsPropertySpecified(nameof(this.ExpiryDateSource), this._expiryDateSource != null);

        [XmlIgnore]
        public bool SubItemQuantitySpecified => this.IsPropertySpecified(nameof(this.SubItemQuantity), this._subItemQuantity != null);

        [XmlIgnore]
        public bool StockLocationIdSpecified => this.IsPropertySpecified(nameof(this.StockLocationId), this._stockLocationId != null);

        [XmlIgnore]
        public bool MachineLocationSpecified => this.IsPropertySpecified(nameof(this.MachineLocation), this._machineLocation != null);

        #endregion

        #region Private methods

        /// <summary>
        /// Determines whether a property is specified and thus could be included in <see cref="InputResponse"/> message.
        /// </summary>
        /// <param name="propertyName">Name of the property.</param>
        /// <param name="hasValue">if set to <c>true</c> [has value].</param>
        /// <returns>
        ///   <c>true</c> if [is property specified] [the specified property name]; otherwise, <c>false</c>.
        /// </returns>
        /// <remarks>The property's value is checked by its private string property which must not be nullable</remarks>
        private bool IsPropertySpecified(string propertyName, bool hasValue)
        {
            Debug.Assert(!string.IsNullOrEmpty(propertyName));

            var propertyIsUserSelectable = this.GetType()
                .GetProperty(propertyName)
                .GetCustomAttributes(false)
                .Any(i => i.GetType().Name.Equals($"{nameof(UserSelectablePropertyAttribute)}"));

            if (!propertyIsUserSelectable)
            {
                return hasValue;
            }

            return hasValue
                && (this._allowedSerializationProperties == null
                || this._allowedSerializationProperties.Any(p => p.Equals(propertyName, StringComparison.InvariantCultureIgnoreCase)));
        }

        #endregion
    }
}