public CalculatedDivePlan SaveDive(CalculatedDivePlan plan)
        {
            var session = _contextAccessor.HttpContext.Session;

            if (session == null)
            {
                return(null);
            }

            var dives = session.GetString(DivesSessionKey);

            if (plan == null)
            {
                return(null);
            }

            if (string.IsNullOrEmpty(dives))
            {
                plan.DiveIndex = 1;
                var list = new List <CalculatedDivePlan> {
                    plan
                };
                session.SetString(DivesSessionKey, JsonConvert.SerializeObject(list));
            }
            else
            {
                var list = JsonConvert.DeserializeObject <List <CalculatedDivePlan> >(dives);

                var existingDive = list.FirstOrDefault(d => d.DiveIndex == plan.DiveIndex);
                if (existingDive != null)
                {
                    list[list.IndexOf(existingDive)] = plan;
                }
                else
                {
                    plan.DiveIndex = list.Max(d => d.DiveIndex) + 1;
                    list.Add(plan);
                }

                session.SetString(DivesSessionKey, JsonConvert.SerializeObject(list));
            }

            return(plan);
        }
        public CalculatedDivePlan Calculate(CalculatedDivePlan prevDive, DiveParameters diveParameters)
        {
            var    points       = new List <DivePlanPoint>(sizeof(double));
            var    bottomLevels = new List <LevelInfo>(diveParameters.Levels.Count());
            double totalTime    = 0;

            _diveParameters = diveParameters;

            var plan = new CalculatedDivePlan
            {
                MaxDepth       = _diveParameters.Levels.Max(l => l.DepthFactor.Depth),
                BottomTime     = _diveParameters.Levels.Sum(l => l.Time),
                PlanPoints     = points,
                MaxPpO         = _diveParameters.Levels.Max(l => BreathGas.GetGasPartialPreasureForDepth(l.DepthFactor, l.Gas.PpO)),
                MaxPpN         = _diveParameters.Levels.Max(l => BreathGas.GetGasPartialPreasureForDepth(l.DepthFactor, l.Gas.PpN)),
                DiveParameters = _diveParameters
            };

            var           diveResult = _diveAlgorythm.ProcessDive(_diveParameters, prevDive?.TissuesSaturationData);
            DivePlanPoint lastPoint  = null;

            if (diveResult.Errors?.Any() == true)
            {
                return new CalculatedDivePlan {
                           Errors = diveResult.Errors
                }
            }
            ;

            // dive start
            points.Add(new DivePlanPoint
            {
                Depth        = 0,
                AbsoluteTime = 0,
                Duration     = 0,
                Type         = DivePlanPointType.StartDive,
                Gas          = _diveParameters.Levels.First().Gas
            });

            lastPoint = points[0];

            foreach (var level in _diveParameters.Levels)
            {
                var depthDistance = Math.Abs(lastPoint.Depth - level.Depth);

                var descend = level.Depth > lastPoint.Depth;

                if (lastPoint.Type != DivePlanPointType.StartDive)
                {
                    lastPoint.Type = DivePlanPointType.Bottom | (descend ? DivePlanPointType.Descent : DivePlanPointType.Ascent);
                }

                var goToLevelTime = descend ?
                                    depthDistance / _diveParameters.DiveConfig.MaxDescentSpeed :
                                    depthDistance / _diveParameters.DiveConfig.MaxAscentSpeed;

                totalTime += goToLevelTime;
                var reachedTime = totalTime;

                points.Add(new DivePlanPoint
                {
                    Depth        = level.Depth,
                    AbsoluteTime = totalTime,
                    Duration     = goToLevelTime,
                    Gas          = level.Gas,
                    Type         = (descend ? DivePlanPointType.Descent : DivePlanPointType.Ascent) | DivePlanPointType.Bottom
                });

                totalTime += level.Time;

                points.Add(new DivePlanPoint
                {
                    Depth        = level.Depth,
                    AbsoluteTime = totalTime,
                    Duration     = level.Time,
                    Gas          = level.Gas,
                    Type         = DivePlanPointType.Bottom
                });

                bottomLevels.Add(new LevelInfo
                {
                    Depth       = level.Depth,
                    PpO         = BreathGas.GetGasPartialPreasureForDepth(level.DepthFactor, level.Gas.PpO),
                    END         = DivingMath.CalculateEND(level.DepthFactor, level.Gas),
                    Gas         = level.Gas,
                    TimeReached = reachedTime
                });

                lastPoint = points.Last();
            }

            lastPoint.Type = DivePlanPointType.Ascent | DivePlanPointType.Bottom | DivePlanPointType.FinalAscent;

            // ascend & deco
            if (diveResult.DecoStops?.Any() == true)
            {
                double lastDepth = lastPoint.Depth;

                foreach (var deco in diveResult.DecoStops)
                {
                    var decoAscendTime = (lastDepth - deco.Depth) / _diveParameters.DiveConfig.MaxAscentSpeed;
                    totalTime += decoAscendTime;

                    points.Add(new DivePlanPoint
                    {
                        Depth        = deco.Depth,
                        AbsoluteTime = totalTime,
                        Duration     = deco.Time,
                        Gas          = deco.Gas,
                        Type         = DivePlanPointType.Deco
                    });

                    totalTime += deco.Time;

                    points.Add(new DivePlanPoint
                    {
                        Depth        = deco.Depth,
                        AbsoluteTime = totalTime,
                        Duration     = deco.Time,
                        Gas          = deco.Gas,
                        Type         = DivePlanPointType.Deco | DivePlanPointType.FinalAscent
                    });

                    lastDepth = deco.Depth;
                }

                var ascendTime = lastDepth / _diveParameters.DiveConfig.MaxAscentSpeed;
                totalTime += ascendTime;
            }
            else
            {
                // ascend
                var ascendTime = (lastPoint.Depth - _diveParameters.DiveConfig.SafeStopDepth) / _diveParameters.DiveConfig.MaxAscentSpeed;
                totalTime += ascendTime;

                var avrAscDepth = (lastPoint.Depth + _diveParameters.DiveConfig.SafeStopDepth) / 2;

                if (lastPoint.Depth <= _diveParameters.DiveConfig.SafeStopDepth)
                {
                    // no safety stop
                    totalTime += lastPoint.Depth / _diveParameters.DiveConfig.MaxAscentSpeed;
                }
                else
                {
                    // safety stop
                    points.Add(new DivePlanPoint
                    {
                        Depth        = _diveParameters.DiveConfig.SafeStopDepth,
                        AbsoluteTime = totalTime,
                        Duration     = _diveParameters.DiveConfig.SafeStopTime,
                        Gas          = _diveParameters.Levels.First().Gas,
                        Type         = DivePlanPointType.SafeStop
                    });

                    totalTime += _diveParameters.DiveConfig.SafeStopTime;

                    points.Add(new DivePlanPoint
                    {
                        Depth        = _diveParameters.DiveConfig.SafeStopDepth,
                        AbsoluteTime = totalTime,
                        Duration     = _diveParameters.DiveConfig.SafeStopTime,
                        Gas          = _diveParameters.Levels.First().Gas,
                        Type         = DivePlanPointType.SafeStop | DivePlanPointType.FinalAscent
                    });

                    ascendTime = _diveParameters.DiveConfig.SafeStopDepth / _diveParameters.DiveConfig.MaxAscentSpeed;
                    totalTime += ascendTime;
                }
            }

            // end dive
            points.Add(new DivePlanPoint {
                Depth = 0, AbsoluteTime = totalTime, Type = DivePlanPointType.EndDive, Gas = new BreathGas()
            });

            var gasSwitches = new List <GasSwitch>();

            plan.FullDesaturationTime   = diveResult.FullDesaturationTime;
            plan.TissuesSaturationData  = diveResult.TissuesSaturationData;
            plan.TotalTime              = diveResult.DiveTotalTime;
            plan.MaxNoDecoDepthTime     = diveResult.MaxNoDecoDepthTime;
            plan.DynamicNoDecoDepthTime = diveResult.DynamicNoDecoDepthTime;
            plan.IntervalTime           = _diveParameters.IntervalTime;
            plan.OxygenCns              = OxygenToxicityCalculator.CalulateOxygenCns(diveResult.DivePoints);
            plan.MaxEND      = _diveParameters.Levels.Max(l => DivingMath.CalculateEND(l.DepthFactor, l.Gas));
            plan.GasSwitches = gasSwitches;
            plan.LevelsInfo  = bottomLevels;

            foreach (var level in _diveParameters.Levels.Skip(1))
            {
                // use strict reference comparsion for presise gas check
                var gasSwitchPoint = diveResult.DivePoints.FirstOrDefault(d => d.CurrentGas == level.Gas);
                if (!gasSwitchPoint.IsEmpty())
                {
                    gasSwitches.Add(new GasSwitch
                    {
                        Depth        = gasSwitchPoint.DepthFactor.Depth,
                        AbsoluteTime = gasSwitchPoint.CurrentDiveTime,
                        Gas          = gasSwitchPoint.CurrentGas,
                        IsDeco       = false,
                        PpO          = BreathGas.GetGasPartialPreasureForDepth(gasSwitchPoint.DepthFactor, gasSwitchPoint.CurrentGas.PpO)
                    });
                }
            }

            foreach (var decoLevel in _diveParameters.DecoLevels ?? new List <DiveLevel>())
            {
                // use strict reference comparsion for presise gas check
                var decoGasSwitchPoint = diveResult.DivePoints.FirstOrDefault(d => d.CurrentGas == decoLevel.Gas);
                if (!decoGasSwitchPoint.IsEmpty())
                {
                    gasSwitches.Add(new GasSwitch
                    {
                        Depth        = decoGasSwitchPoint.DepthFactor.Depth,
                        AbsoluteTime = decoGasSwitchPoint.CurrentDiveTime,
                        Gas          = decoGasSwitchPoint.CurrentGas,
                        IsDeco       = true,
                        PpO          = BreathGas.GetGasPartialPreasureForDepth(decoGasSwitchPoint.DepthFactor, decoGasSwitchPoint.CurrentGas.PpO)
                    });
                }
            }

            // celing points reduce logic
            if (diveResult.CeilingDepthPoints?.Any() == true)
            {
                const int maxPoints = 100;
                var       skip      = 2 * diveResult.CeilingDepthPoints.Count() / 2 / maxPoints;
                if (skip == 0)
                {
                    skip = 1;
                }

                var ceilingDepthPoints = new List <DepthTime>(diveResult.CeilingDepthPoints.Count() / skip);

                var count = diveResult.CeilingDepthPoints.Count();
                for (int i = 0; i < count; i++)
                {
                    if (i % skip == 0)
                    {
                        var elem = diveResult.CeilingDepthPoints.ElementAt(i);
                        ceilingDepthPoints.Add(new DepthTime(elem.Depth, elem.Time));
                    }
                }

                ceilingDepthPoints.Add(new DepthTime(0, diveResult.CeilingDepthPoints.Last().Time));
                plan.CeilingDepthPoints = ceilingDepthPoints;
            }

            var consumedGas = CalculateConsumedGas(diveResult, gasSwitches);

            plan.ConsumedBottomGases = consumedGas.Key;
            plan.ConsumedDecoGases   = consumedGas.Value;
            plan.DiveResultBlocks    = plan.GetDiveInfo();

            return(plan);
        }