using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Input;

namespace XLAB
{
    internal sealed class TipsEditWindowViewModel : ObservableObject
    {
        private readonly IReadOnlyList<TipsDirectoryItem> _existingItems;
        private int _categoryId;
        private int _designId;
        private int _instrumentNameId;
        private bool? _isMkPrimaryOnly;
        private bool? _isSpecialPurpose;
        private int _measurementAreaId;
        private string _metrControlCode;
        private string _notes;
        private string _registryPeriodMonthsText;
        private string _registryTypeNumber;
        private string _serviceLifeYearsText;
        private string _typeName;
        private string _validationMessage;
        private string _vniimsTypeCodeText;

        public TipsEditWindowViewModel(TipsDirectoryItem seed, bool isNew, IReadOnlyList<TipsDirectoryItem> existingItems, TypeSizeDirectoryService service)
        {
            var source = seed ?? new TipsDirectoryItem();
            _existingItems = existingItems ?? Array.Empty<TipsDirectoryItem>();
            Id = source.Id;
            IsNew = isNew;

            MeasurementAreas = service.LoadSpoiReferences();
            InstrumentNames = service.LoadSpnmtpReferences();
            Categories = WithEmpty(service.LoadSpktReferences());
            Designs = WithEmpty(service.LoadSpkiReferences());

            MeasurementAreaId = source.MeasurementAreaId;
            InstrumentNameId = source.InstrumentNameId;
            CategoryId = source.CategoryId ?? 0;
            DesignId = source.DesignId ?? 0;
            TypeName = source.TypeName ?? string.Empty;
            ServiceLifeYearsText = source.ServiceLifeYears.HasValue ? source.ServiceLifeYears.Value.ToString() : string.Empty;
            RegistryPeriodMonthsText = source.RegistryPeriodMonths.HasValue ? source.RegistryPeriodMonths.Value.ToString() : string.Empty;
            RegistryTypeNumber = source.RegistryTypeNumber ?? string.Empty;
            VniimsTypeCodeText = source.VniimsTypeCode.HasValue ? source.VniimsTypeCode.Value.ToString() : string.Empty;
            MetrControlCode = source.MetrControlCode ?? string.Empty;
            Notes = source.Notes ?? string.Empty;
            IsSpecialPurpose = source.IsSpecialPurpose;
            IsMkPrimaryOnly = source.IsMkPrimaryOnly;

            ConfirmCommand = new RelayCommand(Confirm);
            CancelCommand = new RelayCommand(Cancel);
        }

        public event EventHandler<bool?> CloseRequested;

        public ICommand CancelCommand { get; private set; }
        public IReadOnlyList<DirectoryLookupItem> Categories { get; private set; }
        public ICommand ConfirmCommand { get; private set; }
        public IReadOnlyList<DirectoryLookupItem> Designs { get; private set; }
        public int Id { get; private set; }
        public IReadOnlyList<DirectoryLookupItem> InstrumentNames { get; private set; }
        public bool IsNew { get; private set; }
        public IReadOnlyList<DirectoryLookupItem> MeasurementAreas { get; private set; }

        public int MeasurementAreaId { get { return _measurementAreaId; } set { SetProperty(ref _measurementAreaId, value); } }
        public int InstrumentNameId { get { return _instrumentNameId; } set { SetProperty(ref _instrumentNameId, value); } }
        public int CategoryId { get { return _categoryId; } set { SetProperty(ref _categoryId, value); } }
        public int DesignId { get { return _designId; } set { SetProperty(ref _designId, value); } }
        public string TypeName { get { return _typeName; } set { SetProperty(ref _typeName, value); } }
        public string ServiceLifeYearsText { get { return _serviceLifeYearsText; } set { SetProperty(ref _serviceLifeYearsText, value); } }
        public string RegistryPeriodMonthsText { get { return _registryPeriodMonthsText; } set { SetProperty(ref _registryPeriodMonthsText, value); } }
        public string RegistryTypeNumber { get { return _registryTypeNumber; } set { SetProperty(ref _registryTypeNumber, value); } }
        public string VniimsTypeCodeText { get { return _vniimsTypeCodeText; } set { SetProperty(ref _vniimsTypeCodeText, value); } }
        public string MetrControlCode { get { return _metrControlCode; } set { SetProperty(ref _metrControlCode, value); } }
        public string Notes { get { return _notes; } set { SetProperty(ref _notes, value); } }
        public bool? IsSpecialPurpose { get { return _isSpecialPurpose; } set { SetProperty(ref _isSpecialPurpose, value); } }
        public bool? IsMkPrimaryOnly { get { return _isMkPrimaryOnly; } set { SetProperty(ref _isMkPrimaryOnly, value); } }

        public string Title
        {
            get { return IsNew ? "Новый тип СИ" : "Редактирование типа СИ"; }
        }

        public string ValidationMessage
        {
            get { return _validationMessage; }
            private set { SetProperty(ref _validationMessage, value); }
        }

        public TipsDirectoryItem ToResult()
        {
            return new TipsDirectoryItem
            {
                Id = Id,
                MeasurementAreaId = MeasurementAreaId,
                InstrumentNameId = InstrumentNameId,
                CategoryId = CategoryId > 0 ? (int?)CategoryId : null,
                DesignId = DesignId > 0 ? (int?)DesignId : null,
                TypeName = Normalize(TypeName),
                ServiceLifeYears = ParseNullableInt(ServiceLifeYearsText),
                RegistryPeriodMonths = ParseNullableInt(RegistryPeriodMonthsText),
                RegistryTypeNumber = Normalize(RegistryTypeNumber),
                VniimsTypeCode = ParseNullableInt(VniimsTypeCodeText),
                MetrControlCode = Normalize(MetrControlCode),
                Notes = Normalize(Notes),
                IsSpecialPurpose = IsSpecialPurpose,
                IsMkPrimaryOnly = IsMkPrimaryOnly
            };
        }

        private void Cancel(object parameter)
        {
            RaiseCloseRequested(false);
        }

        private void Confirm(object parameter)
        {
            if (MeasurementAreaId <= 0)
            {
                ValidationMessage = "Укажите область измерений.";
                return;
            }

            if (InstrumentNameId <= 0)
            {
                ValidationMessage = "Укажите наименование типа СИ.";
                return;
            }

            var normalizedTypeName = Normalize(TypeName);
            if (string.IsNullOrWhiteSpace(normalizedTypeName))
            {
                ValidationMessage = "Укажите тип СИ.";
                return;
            }

            if (normalizedTypeName.Length > TipsDirectoryRules.TypeNameMaxLength)
            {
                ValidationMessage = string.Format("Тип СИ не должен превышать {0} символов.", TipsDirectoryRules.TypeNameMaxLength);
                return;
            }

            int? parsedValue;
            if (!TryParseNullableInt(ServiceLifeYearsText, out parsedValue))
            {
                ValidationMessage = "Срок службы должен быть целым числом.";
                return;
            }

            if (!TryParseNullableInt(RegistryPeriodMonthsText, out parsedValue))
            {
                ValidationMessage = "МПИ по Госреестру должен быть целым числом.";
                return;
            }

            if (!TryParseNullableInt(VniimsTypeCodeText, out parsedValue))
            {
                ValidationMessage = "Код ВНИИМС должен быть целым числом.";
                return;
            }

            var normalizedRegistryNumber = Normalize(RegistryTypeNumber);
            if (!string.IsNullOrWhiteSpace(normalizedRegistryNumber) && normalizedRegistryNumber.Length > TipsDirectoryRules.RegistryTypeNumberMaxLength)
            {
                ValidationMessage = string.Format("Номер по Госреестру не должен превышать {0} символов.", TipsDirectoryRules.RegistryTypeNumberMaxLength);
                return;
            }

            var normalizedMetrControlCode = Normalize(MetrControlCode);
            if (!string.IsNullOrWhiteSpace(normalizedMetrControlCode) && normalizedMetrControlCode.Length > TipsDirectoryRules.MetrControlCodeMaxLength)
            {
                ValidationMessage = string.Format("Код АИС \"Метрконтроль\" не должен превышать {0} символов.", TipsDirectoryRules.MetrControlCodeMaxLength);
                return;
            }

            var duplicate = _existingItems.FirstOrDefault(delegate(TipsDirectoryItem item)
            {
                return item != null
                       && item.Id != Id
                       && item.InstrumentNameId == InstrumentNameId
                       && string.Equals(Normalize(item.TypeName), normalizedTypeName, StringComparison.OrdinalIgnoreCase);
            });
            if (duplicate != null)
            {
                ValidationMessage = string.Format("Тип СИ \"{0}\" уже существует для выбранного наименования типа СИ.", normalizedTypeName);
                return;
            }

            ValidationMessage = string.Empty;
            RaiseCloseRequested(true);
        }

        private static IReadOnlyList<DirectoryLookupItem> WithEmpty(IReadOnlyList<DirectoryLookupItem> source)
        {
            return new[] { new DirectoryLookupItem { Id = 0, Name = string.Empty } }.Concat(source ?? Array.Empty<DirectoryLookupItem>()).ToList();
        }

        private static string Normalize(string value)
        {
            return string.IsNullOrWhiteSpace(value) ? string.Empty : value.Trim();
        }

        private static int? ParseNullableInt(string value)
        {
            int? parsedValue;
            return TryParseNullableInt(value, out parsedValue) ? parsedValue : (int?)null;
        }

        private static bool TryParseNullableInt(string value, out int? result)
        {
            if (string.IsNullOrWhiteSpace(value))
            {
                result = null;
                return true;
            }

            int parsed;
            if (int.TryParse(value.Trim(), out parsed))
            {
                result = parsed;
                return true;
            }

            result = null;
            return false;
        }

        private void RaiseCloseRequested(bool? dialogResult)
        {
            var handler = CloseRequested;
            if (handler != null)
            {
                handler(this, dialogResult);
            }
        }
    }
}
