// Copyright © Selmo Technology GmbH. All Rights Reserved

using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using Novus.Connectivity.Common;
using Novus.Connectivity.Service;
using SelmoStudio.Hmi.Server.ProcessValue;
using SelmoStudio.Hmi.Server.ProcessValue.Services;
using SelmoStudio.Hmi.Server.ProcessValue.Validator;
using SelmoStudio.Hmi.Server.SelmoHmi.Helpers;
using SelmoStudio.Hmi.Server.SelmoHmi.Model.Ids;
using SelmoStudio.Hmi.Server.SelmoHmi.Parameter.Model.Dto;
using SelmoStudio.Hmi.Server.SelmoHmi.Parameter.Model.Ids;
using SelmoStudio.Hmi.Server.SelmoHmi.Plant.Model.Ids;
using SelmoStudio.Hmi.Server.SelmoHmi.Sequence.Model.Ids;
using SelmoStudio.Hmi.Server.SelmoHmi.Shared;
using SelmoStudio.Hmi.Server.SelmoHmi.Shared.Component;
using SelmoStudio.Hmi.Server.SelmoHmi.Shared.Component.CustomArgs;
using SelmoStudio.Hmi.Server.SelmoHmi.Shared.WellKnownTypes;
using System.Text;

namespace SelmoStudio.Hmi.Server.SelmoHmi.Sequence.Components
{
    public partial class SequenceParameterComponent : IBaseProcessValueComponent, IAsyncDisposable
    {
        private List<ParameterDto> _parameters { get; set; }
        private HashSet<ParameterId> _processingIds = new HashSet<ParameterId>();
        private Dictionary<ParameterId, ProcessValueId> _parameterLookupTable;

        [Inject]
        private IProcessValueInfoService _processValueInfoService { get; set; }

        [Parameter]
        public string Id { get; set; }

        [Parameter]
        public List<ParameterDto> Parameters { get; set; }

        [Parameter]
        public SequenceId SequenceId { get; set; }

        [Parameter]
        public PlantId PlantId { get; set; }

        [Parameter]
        public HardwareZoneId HardwareZoneId { get; set; }

        [Parameter]
        public EventCallback<bool> BackClicked { get; set; }

        [Parameter]
        public WellKnownParameterSource ParameterSource { get; set; }

        [Parameter]
        public bool ShowBack { get; set; } = true;

        #region Init/Lifecycle

        private bool _isRendered;

        public bool IsRendered
        {
            get
            {
                return _isRendered;
            }
        }

        protected override async Task OnAfterRenderAsync(bool firstRender)
        {
            await base.OnAfterRenderAsync(firstRender);

            _isRendered = true;
        }

        protected override async Task OnParametersSetAsync()
        {
            if (_parameters != Parameters)
            {
                _parameters = Parameters;
            }

            var paramIds = _parameters?.Select(p => p.Id).ToList() ?? new List<ParameterId>();

            if (ParameterSource == WellKnownParameterSource.Plant)
            {
                Registration = _processValueInfoService.CreateParameterRegistration(paramIds);
                _parameterLookupTable = _processValueInfoService.GetParameterLookupTable(paramIds);
            }
            else if (ParameterSource == WellKnownParameterSource.HardwareZone)
            {
                Registration = _processValueInfoService.CreateParameterRegistration(paramIds, HardwareZoneId);
                _parameterLookupTable = _processValueInfoService.GetParameterLookupTable(paramIds, HardwareZoneId);
            }
            else if (ParameterSource == WellKnownParameterSource.Sequence)
            {
                Registration = _processValueInfoService.CreateParameterRegistration(paramIds, SequenceId);
                _parameterLookupTable = _processValueInfoService.GetParameterLookupTable(paramIds, SequenceId);
            }
            _processValueInfos = Registration.ValueInfos.ToList();
            await base.OnParametersSetAsync();
        }

        #endregion Init/Lifecycle

        private string GetParameterText(ParameterDto parameterDto)
        {
            return !string.IsNullOrEmpty(parameterDto.HmiText) ? parameterDto.HmiText : !string.IsNullOrEmpty(parameterDto.Name)
                ? parameterDto.Name : parameterDto.DefaultDisplayText;
        }

        private bool IsDisabled(ParameterDto param)
        {
            if (param.Type == ParameterType.Output)
                return true;
            else
                return param.Disable;
        }

        #region Handle ProcessValue Get/Set

        private void SetParameterValue(IProcessValue value)
        {
            if (value == null || _parameterLookupTable == null)
                return;

            var parameterId = _parameterLookupTable.FirstOrDefault(x => x.Value == value.ValueInfo.Id).Key;

            if (!_processingIds.Contains(parameterId))
            {
                var parameter = _parameters?.FirstOrDefault(p => p.Id == parameterId);

                if (value.Value == null)
                    return;

                var valueToSet = value.Value.ToString();

                if (parameter != null && !string.IsNullOrEmpty(valueToSet))
                {
                    parameter.LoadedValue = valueToSet;
                }
            }
        }

        #endregion Handle ProcessValue Get/Set

        #region Style

        private string GetParameterClassString(ParameterDto parameter) =>
            parameter.Type == ParameterType.Output ? "output-parameter-theme" : string.Empty;

        #endregion Style

        public async ValueTask DisposeAsync()
        {
        }

        #region ComponentHandling

        public event EventHandler<ComponentEventArgs> ComponentChangeEvent;

        protected ValuesConnectionRegistration Registration;

        private List<IProcessValueInfo> _processValueInfos = new();

        public List<IProcessValueInfo> GetValueInfos()
        {
            return _processValueInfos;
        }

        public void Update(IDictionary<ProcessValueId, IProcessValue> updatedValues)
        {
            if (!IsRendered)
                return;

            foreach (var value in _parameterLookupTable.Values)
            {
                if (updatedValues.ContainsKey(value))
                {
                    SetParameterValue(updatedValues[value]);
                }
            }
        }

        public void TriggerComponentChangeEvent(TextBoxEventArgs e, ParameterDto parameter)
        {
            if (e == null)
                return;
            try
            {
                _processingIds.Add(parameter.Id);
                var value = e.Value;
                var oldValue = parameter.LoadedValue;
                parameter.LoadedValue = value;

                if (string.IsNullOrEmpty(value) || !_parameterLookupTable.TryGetValue(parameter.Id, out var valueInfoId))
                {
                    parameter.LoadedValue = oldValue;
                    return;
                }

                var valueInfo = Registration.ValueInfos.FirstOrDefault(vI => vI.Id == valueInfoId);

                if (valueInfo != null && !string.IsNullOrEmpty(value))
                {
                    if (!ProcessValueValidator.Validate(parameter, valueInfo, value))
                    {
                        parameter.LoadedValue = oldValue;
                        return;
                    }
                    bool isTimer = false;
                    if (parameter.PlcType == Common.SelmoProject.Types.HmiPlcType.TIME)
                    {
                        value = ConvertToTimerValue(value, parameter.Unit);
                        isTimer = true;
                    }

                    SetParameterValue(e, valueInfo, value, isTimer);
                }
            }
            finally
            {
                _processingIds.Remove(parameter.Id);
            }
        }

        private void SetParameterValue(TextBoxEventArgs e, IProcessValueInfo valueInfo, string value, bool isTimer)
        {
            object valueToSet = ProcessValueConverter.Convert(valueInfo, value);
            if(isTimer)
            {
                valueToSet = ProcessValueConverter.Convert(typeof(uint), value);
            }

            var processValueHandleInfo = new ProcessValueHandleInfo()
            {
                ProcessValueHandle = ProcessValueHandle.TextInput,
                ProcessValueInfo = valueInfo,
                Value = valueToSet
            };

            ComponentChangeEvent?.Invoke(this, ComponentEventArgs.Create(e, processValueHandleInfo));
        }

        public void TriggerMouseOut(MouseEventArgs e, ParameterDto parameter)
        {
            if (e == null)
                return;

            try
            {
                _processingIds.Add(parameter.Id);

                if (!_parameterLookupTable.TryGetValue(parameter.Id, out var valueInfoId))
                {
                    return;
                }
                var valueInfo = Registration.ValueInfos.FirstOrDefault(vI => vI.Id == valueInfoId);

                if (valueInfo == null) return;

                if (parameter.ButtonMode == Common.SelmoProject.Parameter.HmiButtonMode.PushButton)
                {
                    var processValueHandleInfoPush = new ProcessValueHandleInfo()
                    {
                        ProcessValueHandle = ProcessValueHandle.Switch,
                        ProcessValueInfo = valueInfo,
                        Value = false
                    };

                    ComponentChangeEvent?.Invoke(this, ComponentEventArgs.Create(new ComponentEventInfo() { }, processValueHandleInfoPush));

                    return;
                }
            }
            finally
            {
                _processingIds.Remove(parameter.Id);
            }
        }

        public void TriggerComponentChangeEvent(ButtonEventArgs e, ParameterDto parameter)
        {
            if (e == null)
                return;

            try
            {
                _processingIds.Add(parameter.Id);

                if (!_parameterLookupTable.TryGetValue(parameter.Id, out var valueInfoId))
                {
                    return;
                }
                var valueInfo = Registration.ValueInfos.FirstOrDefault(vI => vI.Id == valueInfoId);

                if (valueInfo == null) return;

                if (parameter.ButtonMode == Common.SelmoProject.Parameter.HmiButtonMode.PushButton)
                {
                    bool pushValue = e.EventType == Shared.Component.Types.EventType.MouseDown;

                    var processValueHandleInfoPush = new ProcessValueHandleInfo()
                    {
                        ProcessValueHandle = ProcessValueHandle.Switch,
                        ProcessValueInfo = valueInfo,
                        Value = pushValue
                    };

                    ComponentChangeEvent?.Invoke(this, ComponentEventArgs.Create(e, processValueHandleInfoPush));

                    return;
                }

                if (e.EventType == Shared.Component.Types.EventType.MouseDown)
                {
                    bool currentValue = false;
                    bool.TryParse(parameter.LoadedValue, out currentValue);

                    bool newValue = !currentValue;

                    var processValueHandleInfo = new ProcessValueHandleInfo()
                    {
                        ProcessValueHandle = ProcessValueHandle.Switch,
                        ProcessValueInfo = valueInfo,
                        Value = newValue
                    };

                    ComponentChangeEvent?.Invoke(this, ComponentEventArgs.Create(e, processValueHandleInfo));
                }
            }
            finally
            {
                _processingIds.Remove(parameter.Id);
            }
        }

        private bool GetCheckBoxValue(ParameterDto parameter)
        {
            if (bool.TryParse(parameter.LoadedValue, out var boolValue) && boolValue)
            {
                return boolValue;
            }

            return false;
        }

        private void CheckedChanged((ComponentEventInfo EventArgs, bool Value) args, ParameterDto parameter)
        {
            try
            {
                _processingIds.Add(parameter.Id);

                if (!_parameterLookupTable.TryGetValue(parameter.Id, out var valueInfoId))
                {
                    return;
                }

                var valueInfo = Registration.ValueInfos.FirstOrDefault(vI => vI.Id == valueInfoId);

                bool valueToSet = args.Value;

                if (valueInfo != null)
                {
                    var processValueHandleInfo = new ProcessValueHandleInfo()
                    {
                        ProcessValueHandle = ProcessValueHandle.Switch,
                        ProcessValueInfo = valueInfo,
                        Value = valueToSet
                    };
                    ComponentChangeEvent?.Invoke(this, ComponentEventArgs.Create(args.EventArgs, processValueHandleInfo));
                }
            }
            finally
            {
                _processingIds.Remove(parameter.Id);
            }
        }

        private string GetManualControlButtonStyle(ParameterId parameterId)
        {
            var styleString = new StringBuilder("background-color: ");

            var parameter = _parameters?.FirstOrDefault(p => p.Id.Equals(parameterId));

            var background = HtmlStyleHelper.GetToggleButtonBackgroundColor(false);

            if (bool.TryParse(parameter.LoadedValue, out var boolValue) && boolValue)
            {
                background = HtmlStyleHelper.GetActiveInOutZoneButtonBackgroundColor();
            }

            styleString.Append(background);
            styleString.Append(';');

            return styleString.ToString();
        }

        private string GetText(ParameterId parameterId)
        {
            var parameter = _parameters?.FirstOrDefault(p => p.Id.Equals(parameterId));

            if (bool.TryParse(parameter.LoadedValue, out var boolValue) && boolValue)
            {
                return "TRUE";
            }

            return "FALSE";
        }

        private string ConvertToTimerValue(string value, string unit)
        {
            if (!int.TryParse(value, out int parseValue))
                return value;

            int factor = CalculateFactor(unit);

            return (parseValue * factor).ToString();
        }

        private int CalculateFactor(string unit)
        {
            if(unit == null)
                return 1;

            var unitUC = unit.Trim().ToUpperInvariant();
            if (unitUC.Equals("MS"))
                return 1;
            if (unitUC.Equals("S"))
                return 1_000;
            if (unitUC.Equals("M"))
                return 60_000;
            if (unitUC.Equals("H"))
                return 3_600_000;
            if (unitUC.Equals("D"))
                return 86_400_000;

            return 1;
        }

        #endregion ComponentHandling
    }
}