// Copyright © Selmo Technology GmbH. All Rights Reserved

using Novus.Connectivity.Common;
using SelmoStudio.Hmi.Common.Authentication;
using SelmoStudio.Hmi.Common.SelmoProject;
using SelmoStudio.Hmi.Common.SelmoProject.ProcessValueDefinition;
using SelmoStudio.Hmi.Server.ProcessValue.Model;
using SelmoStudio.Hmi.Server.ProjectData;
using SelmoStudio.Hmi.Server.SelmoHmi.Cmz.Model.Dtos;
using SelmoStudio.Hmi.Server.SelmoHmi.Group.Model.Ids;
using SelmoStudio.Hmi.Server.SelmoHmi.Model.Dtos;
using SelmoStudio.Hmi.Server.SelmoHmi.Model.Ids;
using SelmoStudio.Hmi.Server.SelmoHmi.Model.ProjectItems;
using SelmoStudio.Hmi.Server.SelmoHmi.Parameter.Model.Dto;
using SelmoStudio.Hmi.Server.SelmoHmi.Plant.Model;
using SelmoStudio.Hmi.Server.SelmoHmi.Plant.Model.Ids;
using SelmoStudio.Hmi.Server.SelmoHmi.Sequence.Model;
using SelmoStudio.Hmi.Server.SelmoHmi.Sequence.Model.Ids;
using SelmoStudio.Hmi.Server.SelmoHmi.Shared.Model;
using static SelmoStudio.Hmi.Server.SelmoHmi.Shared.TestImpl.TestValuesForDemo;

namespace SelmoStudio.Hmi.Server.SelmoHmi.Shared.Service
{
    public class ProjectService : IProjectService
    {
        private SelmoHmiProject _project;
        private const string GROUPNONE = "None";

        public SelmoHmiProject Project => _project;

        public SelmoHmiProject GetProject()
        {
            if (_project == null)
                _project = LoadProject();

            return Project;
        }

        public ProcessValuesPlantDefinition GetProcessValueDefinitions()
        {
            return ProjectDataHelper.GetProcessValueDefinitions();
        }

        public void Reload()
        {
            _project = LoadProject();
        }

        private SelmoHmiProject LoadProject()
        {
            var project = ProjectDataHelper.GetProject();
            // TODO Temporary solution for a test projects without the Logic diagram assigned. Same test diagram is assigned to
            // every sequence
            EnsureLogicDiagram(project);

            return project;
        }

        public HmiSettings GetHmiSettings()
        {
            EnsureProjectIsLoaded();

            return Project.Settings;
        }

        public List<HmiUser> GetUserList()
        {
            EnsureProjectIsLoaded();

            return Project.Users;
        }

        public string GetDefaultLanguageLcidCode()
        {
            EnsureProjectIsLoaded();

            return Project.DefaultLanguageLcidCode;
        }

        public IList<string> GetLanguageLcidCodes()
        {
            EnsureProjectIsLoaded();

            return Project.LanguageLcidCodes;
        }

        private void EnsureProjectIsLoaded()
        {
            if (_project == null)
                _project = LoadProject();
        }

        private void EnsureLogicDiagram(SelmoHmiProject project)
        {
            // TODO find a way to remove this sh.. !!!!!

            //project?.Plants?.SelectMany(p => p.HardwareZones ?? Enumerable.Empty<ProjectItemCollectionHwInfo>())
            //    .SelectMany(h => h.Sequences ?? Enumerable.Empty<ProjectItemCollectionSequence>())
            //        .Where(s => s.LogicDiagram == null).ToList()
            //            .ForEach(s => s.LogicDiagram = TestDiagramUtil.TestDiagram);
        }

        private ProjectItemCollectionSequence GetSequence(SequenceId id)
        {
            var seq = new ProjectItemCollectionSequence();
            Project.Plants.ForEach(p =>
            {
                p.HardwareZones.ForEach(hw =>
                {
                    hw.Sequences.ForEach(s =>
                    {
                        if (s.SequenceInfo.Id.Equals(id))
                            seq = s;
                    });
                });
            });
            return seq;
        }

        private ProjectItemCollectionHwInfo GetHardwareZone(HardwareZoneId id)
        {
            var hwInfo = new ProjectItemCollectionHwInfo();
            Project.Plants.ForEach(p =>
            {
                p.HardwareZones.ForEach(hw =>
                {
                    if (hw.HardwareZoneInfo.Id.Equals(id))
                        hwInfo = hw;
                });
            });
            return hwInfo;
        }

        private ProjectItemCollectionPlant GetPlant(PlantId id)
        {
            if (Project.Plants != null && Project.Plants.Count > 0)
                return Project.Plants[0];
            return null;
        }

        #region Process Values

        public List<ProcessInfoDataCollection> GetTestProcessInfoCollections()
        {
            List<IProcessValue> infos = CreateTestValues(5).ToList();
            return new List<ProcessInfoDataCollection> {
                new ProcessInfoDataCollection()
                {
                    Id = new(infos[0].ValueInfo.Id.Guid),
                    processValue = ((TestProcessValue)infos[0]).ValueInfo,
                    processInfoValues = new()
                },
                new ProcessInfoDataCollection(){
                    Id = new(infos[1].ValueInfo.Id.Guid),
                    processValue = ((TestProcessValue)infos[1]).ValueInfo,
                    processInfoValues = new()
                },
                new ProcessInfoDataCollection(){
                    Id = new(infos[2].ValueInfo.Id.Guid),
                    processValue = ((TestProcessValue)infos[2]).ValueInfo,
                    processInfoValues = new()
                },
                new ProcessInfoDataCollection(){
                    Id = new(infos[3].ValueInfo.Id.Guid),
                    processValue = ((TestProcessValue)infos[3]).ValueInfo,
                    processInfoValues = new()
                },
                new ProcessInfoDataCollection(){
                    Id = new(infos[4].ValueInfo.Id.Guid),
                    processValue = ((TestProcessValue)infos[4]).ValueInfo,
                    processInfoValues = new()
                }
            };
        }

        public Task<List<TestProcessValueInfo>> GetProcessValues(SequenceId id)
        {
            List<TestProcessValueInfo> pvs = new();

            // TODO implement

            return Task.FromResult(pvs);
        }

        public Task<List<TestProcessValueInfo>> GetProcessValues(HardwareZoneId id)
        {
            List<TestProcessValueInfo> pvs = new();

            // TODO implement

            return Task.FromResult(pvs);
        }

        public Task<List<TestProcessValueInfo>> GetProcessValues(PlantId id)
        {
            List<TestProcessValueInfo> pvs = new();

            // TODO implement

            return Task.FromResult(pvs);
        }

        #endregion Process Values

        public List<CmzDto> GetAllCmzs()
        {
            List<CmzDto> cmzs = new List<CmzDto>();
            _project.Plants.ForEach(plant =>
            {
                plant.CmzCollection.ForEach(cmzColl =>
                {
                    cmzs.AddRange(cmzColl.Cmzs);
                });
                plant.HardwareZones.ForEach(hardwareZone =>
                {
                    hardwareZone.CmzCollection.ForEach(cmzColl =>
                    {
                        cmzs.AddRange(cmzColl.Cmzs);
                    });
                    hardwareZone.Sequences.ForEach(sequence =>
                    {
                        cmzs.AddRange(sequence.CmzCollection.Cmzs);
                    });
                });
            });
            return cmzs;
        }

        private static Dictionary<GroupId, string> GetUsedParameterGroups(Dictionary<GroupId, string> allGroups, List<ParameterDto> parameters)
        {
            var emptyGroupId = new GroupId(Guid.Empty);
            var usedGroupIds = parameters.Select(p => p.Group).Distinct();

            var usedGroups = usedGroupIds
                .Where(groupId => allGroups.ContainsKey(groupId))
                .ToDictionary(groupId => groupId, groupId => allGroups[groupId]);

            if (parameters.Any(p => !allGroups.ContainsKey(p.Group)) && !usedGroups.ContainsKey(emptyGroupId))
            {
                usedGroups.Add(emptyGroupId, GROUPNONE);
            }

            return usedGroups
                .OrderBy(kvp => kvp.Key.Guid == Guid.Empty ? 0 : 1)
                .ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
        }

        private static Dictionary<GroupId, string> GetUsedZoneGroups(Dictionary<GroupId, string> allGroups, List<ZoneDto> zones)
        {
            var emptyGroupId = new GroupId(Guid.Empty);
            var hmiButtonZones = zones.Where(z => z.HmiButton);
            var usedGroupIds = hmiButtonZones.Select(z => z.Group).Distinct();

            var usedGroups = usedGroupIds
                .Where(groupId => allGroups.ContainsKey(groupId))
                .ToDictionary(groupId => groupId, groupId => allGroups[groupId]);

            if (hmiButtonZones.Any(z => !allGroups.ContainsKey(z.Group)) && !usedGroups.ContainsKey(emptyGroupId))
            {
                usedGroups.Add(emptyGroupId, GROUPNONE);
            }

            return usedGroups
                .OrderBy(kvp => kvp.Key.Guid == Guid.Empty ? 0 : 1)
                .ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
        }

        public Dictionary<GroupId, string> GetUsedParameterGroups(PlantId plantId, SequenceId sequenceId)
        {
            Dictionary<GroupId, string> allGroups = GetAllGroups(plantId);

            var sequence = GetSequence(sequenceId);

            Dictionary<GroupId, string> usedGroups = GetUsedParameterGroups(allGroups, sequence.ParameterCollection.Parameters);

            return usedGroups;
        }

        public Dictionary<GroupId, string> GetUsedParameterGroups(PlantId plantId, HardwareZoneId hardwareZoneId)
        {
            Dictionary<GroupId, string> allGroups = GetAllGroups(plantId);

            var hwZone = GetHardwareZone(hardwareZoneId);

            Dictionary<GroupId, string> usedGroups = GetUsedParameterGroups(allGroups, hwZone.ParameterCollection[0].Parameters);

            return usedGroups;
        }

        public Dictionary<GroupId, string> GetUsedParameterGroups(PlantId plantId)
        {
            var plant = GetPlant(plantId);

            Dictionary<GroupId, string> allGroups = GetAllGroups(plantId);

            Dictionary<GroupId, string> usedGroups = GetUsedParameterGroups(allGroups, plant.ParameterCollection[0].Parameters);

            return usedGroups;
        }

        public Dictionary<GroupId, string> GetUsedZonesGroups(PlantId plantId, SequenceId sequenceId)
        {
            var plant = GetPlant(plantId);

            Dictionary<GroupId, string> allGroups = GetAllGroups(plantId);

            var sequence = GetSequence(sequenceId);

            Dictionary<GroupId, string> usedGroups = GetUsedZoneGroups(allGroups, sequence.ZoneCollection.Zones);

            return usedGroups;
        }

        private Dictionary<GroupId, string> GetAllGroups(PlantId plantId)
        {
            var plant = GetPlant(plantId);

            return plant.GroupCollection
                .SelectMany(groupColl => groupColl.Groups)
                .Where(group => group.Id.Guid != Guid.Empty)
                .ToDictionary(group => group.Id, group => group.Name);
        }

        private static List<ParameterDto> GetParameters(GroupId currentGroupId, Dictionary<GroupId, string> usedGroups, List<ParameterDto> parameters)
        {
            if (currentGroupId.Guid != Guid.Empty && usedGroups.ContainsKey(currentGroupId))
            {
                return parameters.Where(param => param.Group == currentGroupId).ToList();
            }
            else
            {
                return parameters.Where(param => !usedGroups.ContainsKey(param.Group) || param.Group.Guid == Guid.Empty).ToList();
            }
        }

        public List<ParameterDto> GetParameters(PlantId plantId, SequenceId sequenceId, GroupId currentGroupId)
        {
            var plant = GetPlant(plantId);

            var usedGroups = GetUsedParameterGroups(plantId, sequenceId);

            var seq = GetSequence(sequenceId);

            return GetParameters(currentGroupId, usedGroups, seq.ParameterCollection.Parameters);
        }

        public List<ParameterDto> GetParameters(PlantId plantId, HardwareZoneId hardwareZoneId, GroupId currentGroupId)
        {
            List<ParameterDto> parameterList = new();

            var plant = GetPlant(plantId);

            var usedGroups = GetUsedParameterGroups(plantId, hardwareZoneId);

            var hwZone = GetHardwareZone(hardwareZoneId);

            return GetParameters(currentGroupId, usedGroups, hwZone.ParameterCollection[0].Parameters);
        }

        public List<ParameterDto> GetParameters(PlantId plantId, GroupId currentGroupId)
        {
            List<ParameterDto> parameterList = new();

            var plant = GetPlant(plantId);

            var usedGroups = GetUsedParameterGroups(plantId);

            return GetParameters(currentGroupId, usedGroups, plant.ParameterCollection[0].Parameters);
        }

        private static List<ZoneDto> GetZones(GroupId currentGroupId, Dictionary<GroupId, string> usedGroups, List<ZoneDto> zones)
        {
            var hmiButtonZones = zones.Where(z => z.HmiButton);

            if (currentGroupId.Guid != Guid.Empty && usedGroups.ContainsKey(currentGroupId))
            {
                return hmiButtonZones.Where(zone => zone.Group == currentGroupId).ToList();
            }
            else
            {
                return hmiButtonZones.Where(zone => !usedGroups.ContainsKey(zone.Group) || zone.Group.Guid == Guid.Empty).ToList();
            }
        }

        public List<ZoneDto> GetZones(PlantId plantId, SequenceId sequenceId, GroupId currentGroupId)
        {
            var plant = GetPlant(plantId);

            var usedGroups = GetUsedZonesGroups(plantId, sequenceId);

            var seq = GetSequence(sequenceId);

            return GetZones(currentGroupId, usedGroups, seq.ZoneCollection.Zones);
        }
    }
}