//TODO: razmotriti stanje outage na ako se algoritam enduje sa nekim fejlom.... da li brisati sam outage npr...
        private async Task OnEndAlgorithmCleanUp(long headBreakerGid)
        {
            var algorithmBaseLogString = $"{baseLogString} [HeadBreakerGid: 0x{headBreakerGid:X16}]";

            await StartedIsolationAlgorithms.TryRemoveAsync(headBreakerGid);

            await MonitoredHeadBreakerMeasurements.TryRemoveAsync(headBreakerGid);

            var enumerableCommandedElements = await CommandedElements.GetEnumerableDictionaryAsync();

            var commandedElementsToBeRemoved = enumerableCommandedElements.Values.Where(element => element.CorrespondingHeadElementGid == headBreakerGid);

            foreach (var element in commandedElementsToBeRemoved)
            {
                await CommandedElements.TryRemoveAsync(element.ElementGid);
            }

            var enumerableOptimumIsolationPoints = await OptimumIsolationPoints.GetEnumerableDictionaryAsync();

            var optimumIsolationPointsToBeRemovedGids = enumerableOptimumIsolationPoints.Where(kvp => kvp.Value == headBreakerGid).Select(kvp => kvp.Key);

            foreach (var gid in optimumIsolationPointsToBeRemovedGids)
            {
                await OptimumIsolationPoints.TryRemoveAsync(gid);
            }
        }
        private async Task <bool> SendCommands(IsolationAlgorithm algorithm, Dictionary <long, DiscreteCommandingType> commands, Dictionary <long, CommandedElement> commandedElements)
        {
            var algorithmBaseLogString = $"{baseLogString} [HeadBreakerGid: 0x{algorithm.HeadBreakerGid:X16}]";

            if (!await lifecycleHelper.SendMultipleScadaCommandAsync(commands, commandedElements, CommandOriginType.ISOLATING_ALGORITHM_COMMAND))
            {
                string message = $"{algorithmBaseLogString} StartIndividualAlgorithmCycle => Sending multiple command failed.";
                Logger.LogError(message);
                return(false);
            }

            commands.Keys.ToList().ForEach(async commandedElementGid =>
            {
                var commandedElement = new CommandedElement()
                {
                    ElementGid = commandedElementGid,
                    CorrespondingHeadElementGid = algorithm.HeadBreakerGid,
                    CommandingType = commands[commandedElementGid],
                };

                await CommandedElements.SetAsync(commandedElementGid, commandedElement);
                await ElementsToBeIgnoredInReportPotentialOutage.SetAsync(commandedElementGid, DateTime.UtcNow);
                Logger.LogDebug($"{algorithmBaseLogString} SendCommands => Element 0x{commandedElementGid:X16} set to collection '{ReliableDictionaryNames.ElementsToBeIgnoredInReportPotentialOutage}' at {DateTime.UtcNow}.");
            });

            await StartedIsolationAlgorithms.SetAsync(algorithm.HeadBreakerGid, algorithm);

            return(true);
        }
        private async Task <bool> SendCommands(IsolationAlgorithm algorithm, Dictionary <long, DiscreteCommandingType> commands, Dictionary <long, CommandedElement> commandedElements)
        {
            var algorithmBaseLogString = $"{baseLogString} [HeadBreakerGid: 0x{algorithm.HeadBreakerGid:X16}]";

            if (!await lifecycleHelper.SendMultipleScadaCommandAsync(commands, commandedElements, CommandOriginType.ISOLATING_ALGORITHM_COMMAND))
            {
                string message = $"{algorithmBaseLogString} StartIndividualAlgorithmCycle => Sending multiple command failed.";
                Logger.LogError(message);
                return(false);
            }

            commands.Keys.ToList().ForEach(async commandedElementGid =>
            {
                var commandedElement = new CommandedElement()
                {
                    ElementGid = commandedElementGid,
                    CorrespondingHeadElementGid = algorithm.HeadBreakerGid,
                    CommandingType = commands[commandedElementGid],
                };
                await CommandedElements.SetAsync(commandedElementGid, commandedElement);

                algorithm.ElementsCommandedInCurrentCycle.Add(commandedElementGid);
            });

            await StartedIsolationAlgorithms.SetAsync(algorithm.HeadBreakerGid, algorithm);

            return(true);
        }
        public async Task Start()
        {
            while (!ReliableDictionariesInitialized)
            {
                await Task.Delay(1000);
            }

            var enumerableStartedAlgorithms = await StartedIsolationAlgorithms.GetEnumerableDictionaryAsync();

            if (enumerableStartedAlgorithms.Count == 0)
            {
                Logger.LogVerbose($"{baseLogString} Start => No started algorithms.");
                return;
            }

            var enumerableTopology = await OutageTopologyModel.GetEnumerableDictionaryAsync();

            if (!enumerableTopology.ContainsKey(ReliableDictionaryNames.OutageTopologyModel))
            {
                Logger.LogError($"{baseLogString} Start => Topology not found in Rel Dictionary: {ReliableDictionaryNames.OutageTopologyModel}.");
                return;
            }

            var topology = enumerableTopology[ReliableDictionaryNames.OutageTopologyModel];
            var tasks    = new List <Task <ConditionalValue <long> > >();

            foreach (var algorithm in enumerableStartedAlgorithms.Values)
            {
                tasks.Add(StartIndividualAlgorithmCycle(algorithm, topology));
            }

            var tasksArray = tasks.ToArray();

            Task.WaitAll(tasksArray);

            foreach (var task in tasksArray)
            {
                //SVESNO SE POGRESNO KORISTI HasValue
                if (!task.Result.HasValue)
                {
                    var headBreakerGid = task.Result.Value;
                    await OnEndAlgorithmCleanUp(headBreakerGid);
                }
            }
        }
        private async Task <bool> CheckPreconditions(long elementGid, CommandOriginType commandOriginType, List <long> affectedConsumersGids, IHistoryDBManagerContract historyDBManagerClient)
        {
            if (this.ignorableCommandOriginTypes.Contains(commandOriginType))
            {
                Logger.LogDebug($"{baseLogString} CheckPreconditions => ignorable command origin type: {commandOriginType}");
                return(false);
            }

            var enumerableStartedAlgorithms = await StartedIsolationAlgorithms.GetEnumerableDictionaryAsync();

            var enumerableOptimumIsolationPoints = await OptimumIsolationPoints.GetEnumerableDictionaryAsync();

            if (enumerableStartedAlgorithms.Values.Any(algorithm => algorithm.ElementsCommandedInCurrentCycle.Contains(elementGid)) || enumerableOptimumIsolationPoints.ContainsKey(elementGid))
            {
                Logger.LogWarning($"{baseLogString} CheckPreconditions => ElementGid 0x{elementGid:X16} found in elements commanded in current isolating algorithm cycle or in optimumIsolationPoints.");
                return(false);
            }

            if (affectedConsumersGids.Count == 0)
            {
                await OnZeroAffectedConsumersCase(elementGid, historyDBManagerClient);

                Logger.LogWarning($"{baseLogString} ReportPotentialOutage => There is no affected consumers => outage report is not valid. ElementGid: 0x{elementGid:X16}, CommandOriginType: {commandOriginType}");
                return(false);
            }

            var outageModelAccessClient = OutageModelAccessClient.CreateClient();
            var activeOutages           = await outageModelAccessClient.GetAllActiveOutages();

            if (activeOutages.Any(active => (active.OutageState == OutageState.CREATED && active.OutageElementGid == elementGid) ||
                                  (active.OutageState != OutageState.CREATED && active.DefaultIsolationPoints.Any(point => point.EquipmentId == elementGid))))
            {
                Logger.LogWarning($"{baseLogString} ReportPotentialOutage => duplicate... ElementGID: 0x{elementGid:X16}");
                return(false);
            }

            return(true);
        }
        /// <summary>
        ///
        /// </summary>
        /// <param name="algorithm"></param>
        /// <param name="topology"></param>
        /// <returns>ConditionalValue with HeadElementGid as value - HasValue: false indicates that task ended unsuccessfully, value will never be null and will represent the id of the task -> HeadElementGid</returns>
        private async Task <ConditionalValue <long> > StartIndividualAlgorithmCycle(IsolationAlgorithm algorithm, OutageTopologyModel topology)
        {
            var algorithmBaseLogString = $"{baseLogString} [HeadBreakerGid: 0x{algorithm.HeadBreakerGid:X16}]";

            //END CONDITION - poslednji otvoren brejker se nije otvorio vise od 'cycleUpperLimit' milisekundi => on predstavlja prvu optimalnu izolacionu tacku
            if (algorithm.CycleCounter * CycleInterval >= CycleUpperLimit)
            {
                Logger.LogInformation($"{algorithmBaseLogString} StartIndividualAlgorithmCycle => END CONDITION: {algorithm.CycleCounter} ms * {CycleInterval} ms > {CycleUpperLimit} ms.");
                var success = await FinishIndividualAlgorithmCycle(algorithm, topology);

                return(new ConditionalValue <long>(success, algorithm.HeadBreakerGid));
            }
            else
            {
                Logger.LogInformation($"{algorithmBaseLogString} StartIndividualAlgorithmCycle => END CONDITION NOT MET: {algorithm.CycleCounter} ms * {CycleInterval} ms <= {CycleUpperLimit} ms.");
            }

            #region Check if HeadBreaker has OPENED after the last cycle
            var result = await MonitoredHeadBreakerMeasurements.TryGetValueAsync(algorithm.HeadBreakerMeasurementGid);

            if (!result.HasValue)
            {
                Logger.LogError($"{algorithmBaseLogString} StartIndividualAlgorithmCycle => HeadBreakerMeasurement with gid: 0x{algorithm.HeadBreakerMeasurementGid:X16} not found in {ReliableDictionaryNames.MonitoredHeadBreakerMeasurements}.");
                return(new ConditionalValue <long>(false, algorithm.HeadBreakerGid));
            }

            var headMeasurementData = result.Value;

            if (headMeasurementData.Value == (ushort)DiscreteCommandingType.CLOSE)
            {
                Logger.LogInformation($"{algorithmBaseLogString} StartIndividualAlgorithmCycle => HeadBreaker is CLOSED.");

                //by exiting now we apply the logical WAITING (cycle mechanism in RunAsync):
                //1) For HeadBreaker to open => moving to next breaker
                //2) For "time" to run up => FinishIndividualAlgorithmCycle

                //counting cycles from after the command was successfully executed
                var commandsCount = await CommandedElements.GetCountAsync();

                if (commandsCount == 0)
                {
                    algorithm.CycleCounter++;
                    await StartedIsolationAlgorithms.SetAsync(algorithm.HeadBreakerGid, algorithm);

                    Logger.LogInformation($"{algorithmBaseLogString} StartIndividualAlgorithmCycle => AlgorithCycleCounter set to {algorithm.CycleCounter}.");
                }
                else
                {
                    Logger.LogInformation($"{algorithmBaseLogString} StartIndividualAlgorithmCycle => Skipping the cycle and waiting for {commandsCount} commands to be executed. HEAD is CLOSED. AlgorithCycleCounter remains set on {algorithm.CycleCounter}.");
                }

                return(new ConditionalValue <long>(true, algorithm.HeadBreakerGid));
            }
            else if (headMeasurementData.Value == (ushort)DiscreteCommandingType.OPEN)
            {
                //skipping untill all commands were successfully executed
                var commandsCount = await CommandedElements.GetCountAsync();

                if (commandsCount > 0)
                {
                    Logger.LogInformation($"{algorithmBaseLogString} StartIndividualAlgorithmCycle => Skipping the cycle and waiting for {commandsCount} commands to be executed. HEAD is OPENED. AlgorithCycleCounter remains set on {algorithm.CycleCounter}.");
                    return(new ConditionalValue <long>(true, algorithm.HeadBreakerGid));
                }
            }
            else
            {
                Logger.LogError($"{algorithmBaseLogString} StartIndividualAlgorithmCycle => headMeasurementData.Value is {headMeasurementData.Value} and cannot be casted to {typeof(DiscreteCommandingType)}.");
                return(new ConditionalValue <long>(false, algorithm.HeadBreakerGid));
            }
            #endregion

            Logger.LogInformation($"{algorithmBaseLogString} StartIndividualAlgorithmCycle => HeadBreaker is OPENED.");

            //Closing current breaker, before moving to the next breaker
            var commands = new Dictionary <long, DiscreteCommandingType>();
            if (algorithm.CurrentBreakerGid != algorithm.HeadBreakerGid)
            {
                Logger.LogInformation($"{algorithmBaseLogString} StartIndividualAlgorithmCycle => Preaparing command to CLOSE the current breaker 0x{algorithm.CurrentBreakerGid:X16}");
                commands.Add(algorithm.CurrentBreakerGid, DiscreteCommandingType.CLOSE);
            }

            algorithm.CycleCounter = 0;

            //moving to the next breaker
            algorithm.CurrentBreakerGid = lifecycleHelper.GetNextBreaker(algorithm.CurrentBreakerGid, topology);
            Logger.LogDebug($"{algorithmBaseLogString} StartIndividualAlgorithmCycle => Next breaker gid is 0x{algorithm.CurrentBreakerGid:X16}.");

            if (algorithm.CurrentBreakerGid <= 0 || !topology.GetElementByGid(algorithm.CurrentBreakerGid, out _))
            {
                Logger.LogError($"{algorithmBaseLogString} StartIndividualAlgorithmCycle => HeadBreakerMeasurement with gid: 0x{algorithm.HeadBreakerMeasurementGid:X16} not found in {ReliableDictionaryNames.MonitoredHeadBreakerMeasurements}.");
                return(new ConditionalValue <long>(false, algorithm.HeadBreakerGid));
            }

            var enumerableCommandedElements = await CommandedElements.GetEnumerableDictionaryAsync();

            //reaching the END of the FEEDER - ending the algorithm
            if (algorithm.CurrentBreakerGid == algorithm.RecloserGid)
            {
                string message = $"{algorithmBaseLogString} StartIndividualAlgorithmCycle => End of the feeder, no outage detected.";
                Logger.LogWarning(message);

                //TODO: HOW TO HANDEL - archived, deleted....
                await SendCommands(algorithm, commands, enumerableCommandedElements);

                return(new ConditionalValue <long>(false, algorithm.HeadBreakerGid));
            }

            if (!commands.ContainsKey(algorithm.CurrentBreakerGid) && !commands.ContainsKey(algorithm.HeadBreakerGid))
            {
                commands.Add(algorithm.CurrentBreakerGid, DiscreteCommandingType.OPEN);
                Logger.LogInformation($"{algorithmBaseLogString} StartIndividualAlgorithmCycle => Preaparing command to OPEN the current breaker 0x{algorithm.CurrentBreakerGid:X16}");

                commands.Add(algorithm.HeadBreakerGid, DiscreteCommandingType.CLOSE);
                Logger.LogInformation($"{algorithmBaseLogString} StartIndividualAlgorithmCycle => Preaparing command to CLOSE the head breaker 0x{algorithm.HeadBreakerGid:X16}");
            }

            if (await SendCommands(algorithm, commands, enumerableCommandedElements))
            {
                Logger.LogInformation($"{algorithmBaseLogString} StartIndividualAlgorithmCycle => Commands sent with success.");
                return(new ConditionalValue <long>(true, algorithm.HeadBreakerGid));
            }
            else
            {
                Logger.LogInformation($"{algorithmBaseLogString} StartIndividualAlgorithmCycle => Send commands failed.");
                return(new ConditionalValue <long>(false, algorithm.HeadBreakerGid));
            }
        }