using CareFusion.Lib.StorageSystem.Attributes;
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 InputResponseArticle : IInputResponseArticle
    {
        #region Private members

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

        private string _id { get; set; }

        private string _name { get; set; }

        private string _requiresFridge { get; set; }

        private string _virtualId { get; set; }

        private string _virtualName { get; set; }

        private string _dosageForm { get; set; }

        private string _packagingUnit { get; set; }

        private string _maxSubItemQuantity { get; set; }

        #endregion

        #region Constructors

        public InputResponseArticle()
        {
        }

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

        #endregion

        #region IInputResponseArticle specific properties

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

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

        /// <inheritdoc/>
        [XmlAttribute]
        [UserSelectableProperty]
        public bool RequiresFridge
        {
            get => TypeConverter.ConvertBool(this._requiresFridge);
            set => this._requiresFridge = TextConverter.EscapeInvalidXmlChars(value.ToString());
        }

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

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

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

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

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

        /// <inheritdoc/>
        [XmlElement(ElementName = "Pack")]
        public List<InputResponsePack> Packs { get; set; } = new List<InputResponsePack>();

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

        #endregion

        #region Serialization properties

        [XmlIgnore]
        public bool IdSpecified => this.IsPropertySpecified(nameof(this.Id), this._id != null);

        [XmlIgnore]
        public bool NameSpecified => this.IsPropertySpecified(nameof(this.Name), this._name != null);

        [XmlIgnore]
        public bool RequiresFridgeSpecified => this.IsPropertySpecified(nameof(this.RequiresFridge), this._requiresFridge != null);

        [XmlIgnore]
        public bool VirtualIdSpecified => this.IsPropertySpecified(nameof(this.VirtualId), this._virtualId != null);

        [XmlIgnore]
        public bool VirtualNameSpecified => this.IsPropertySpecified(nameof(this.VirtualName), this._virtualName != null);

        [XmlIgnore]
        public bool DosageFormSpecified => this.IsPropertySpecified(nameof(this.DosageFormSpecified), this._dosageForm != null);

        [XmlIgnore]
        public bool PackagingUnitSpecified => this.IsPropertySpecified(nameof(this.PackagingUnit), this._packagingUnit != null);

        [XmlIgnore]
        public bool MaxSubItemQuantitySpecified => this.IsPropertySpecified(nameof(this.MaxSubItemQuantity), this._maxSubItemQuantity != 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
    }
}