private TimeSpan GotoDecoDepth(ZH_L16 algorithm, double currentDepth, double finalDepth, int runTime,
            ref Collection<DiveSegment> diveTable, out double stopDepth)
        {
            double minDepthAscent = algorithm.MinDepthAscent(CalcGradient(firstDecoDepth, currentDepth, pref));
            double decoDepth = GetDecoDepth(minDepthAscent, pref);

            if (decoDepth >= currentDepth)
            {
                stopDepth = decoDepth;
                return new TimeSpan();
            }

            Logger.Trace("GotoDecoDepth => start deco depth:" + decoDepth);
            algorithm.AscendDecsend(currentDepth, decoDepth, pref.AscRate);
            stopDepth = ContinueAscend(algorithm, finalDepth, decoDepth);

            TimeSpan timeSpentInGoingUp = CalcAscDescTime(currentDepth - stopDepth, pref.AscRate);
            int timespent = TimeHelper.MinutesRoundedUp(timeSpentInGoingUp);

            // Round to complete minute
            double diffTime = Math.Abs(timeSpentInGoingUp.TotalMinutes - timespent);
            double diffDepth = CalcHalfWayDepth(currentDepth, stopDepth);
            if (Math.Abs(diffTime - 0) > 0.001)
            {
                var algorithm2 = new ZH_L16(algorithm);
                algorithm2.AddRunTimeInMinutes(diffTime, diffDepth);
                    // Try adding additional runtime to verify if we should go up even more.
                double newStopDepth = ContinueAscend(algorithm2, finalDepth, stopDepth);
                if (newStopDepth < stopDepth)
                {
                    ContinueAscend(algorithm, finalDepth, stopDepth);

                    timeSpentInGoingUp = CalcAscDescTime(currentDepth - newStopDepth, pref.AscRate);
                    timespent = TimeHelper.MinutesRoundedUp(timeSpentInGoingUp);

                    // Round to complete minute
                    diffTime = Math.Abs(timeSpentInGoingUp.TotalMinutes - timespent);
                    diffDepth = CalcHalfWayDepth(currentDepth, stopDepth);
                    if (Math.Abs(diffTime - 0) > 0.001)
                    {
                        algorithm.AddRunTimeInMinutes(diffTime, diffDepth);
                    }

                    stopDepth = newStopDepth;
                }
            }

            double cns = CentralNervousSystem.AscendDescend(algorithm.ActiveGas, currentDepth, stopDepth, Math.Abs(pref.AscRate));
            double otu = OxygenToxicityUnit.AscendDescend(algorithm.ActiveGas, currentDepth, stopDepth, Math.Abs(pref.AscRate));
            timeSpentInGoingUp = TimeSpan.FromMinutes(timespent);
            var seg = new DiveSegment(stopDepth, timeSpentInGoingUp, DiveState.Ascend, runTime, runTime + timespent,
                                      algorithm.ActiveGas, cns, otu);
            Logger.Trace("GotoDecoDepth => Segment:" + seg);
            diveTable.Add(seg);

            return timeSpentInGoingUp;
        }
        private TimeSpan GoingUp(ZH_L16 algorithm, ref Collection<DiveSegment> divetable, int runTime, double depth, WayPoint waypoint)
        {
            var ascdescTime = new TimeSpan();

            double minDepthAscent = algorithm.MinDepthAscent(CalcGradient(firstDecoDepth, depth, pref));

            //// No deco needed
            if (minDepthAscent <= waypoint.Depth)
            {
                algorithm.AscendDecsend(depth, waypoint.Depth, pref.AscRate);
                ascdescTime = CalcAscDescTime(depth - waypoint.Depth, pref.AscRate);
                int timespent = TimeHelper.MinutesRoundedUp(ascdescTime);
                double cns = CentralNervousSystem.AscendDescend(algorithm.ActiveGas, depth, waypoint.Depth, Math.Abs(pref.AscRate));
                double otu = OxygenToxicityUnit.AscendDescend(algorithm.ActiveGas, depth, waypoint.Depth, Math.Abs(pref.AscRate));

                // Round to complete minute
                double diffTime = Math.Abs(ascdescTime.TotalMinutes - timespent);
                double diffDepth = CalcHalfWayDepth(depth, waypoint.Depth);
                algorithm.AddRunTimeInMinutes(diffTime, diffDepth);
                ascdescTime = TimeSpan.FromMinutes(timespent);
                cns += CentralNervousSystem.ConstantDepth(algorithm.ActiveGas, diffDepth, diffTime);
                otu += OxygenToxicityUnit.ConstantDepth(algorithm.ActiveGas, diffDepth, diffTime);

                var seg = new DiveSegment(waypoint.Depth, ascdescTime, DiveState.Ascend, runTime, runTime + timespent,
                                          algorithm.ActiveGas, cns, otu);
                Logger.Trace("Calc => Segment:" + seg);
                divetable.Add(seg);
            }
            else
            {
                ascdescTime = GotoDecoDepth(algorithm, depth, waypoint.Depth, runTime, ref divetable, out depth);
                runTime += TimeHelper.MinutesRoundedUp(ascdescTime);
                if (Math.Abs(depth - waypoint.Depth) > 0.001)
                {
                    TimeSpan decoTime = DoDeco(algorithm, depth, waypoint.Depth, runTime, ref divetable);
                    ascdescTime += decoTime;
                }
            }

            return ascdescTime;
        }
        private TimeSpan GoingDown(ZH_L16 algorithm, ref Collection<DiveSegment> divetable, int runTime,
            double currentDepth, WayPoint waypoint)
        {
            algorithm.AscendDecsend(currentDepth, waypoint.Depth, pref.DescRate);
            TimeSpan ascdescTime = CalcAscDescTime(waypoint.Depth - currentDepth, pref.DescRate);
            int timespent = TimeHelper.MinutesRoundedUp(ascdescTime);
            double cns = CentralNervousSystem.AscendDescend(algorithm.ActiveGas, currentDepth, waypoint.Depth, Math.Abs(pref.DescRate));
            double otu = OxygenToxicityUnit.AscendDescend(algorithm.ActiveGas, currentDepth, waypoint.Depth, Math.Abs(pref.DescRate));

            // Round to complete minute
            double diffTime = Math.Abs(ascdescTime.TotalMinutes - timespent);
            double diffDepth = CalcHalfWayDepth(currentDepth, waypoint.Depth);
            if (Math.Abs(diffTime - 0) > 0.001)
            {
                algorithm.AddRunTimeInMinutes(diffTime, diffDepth);
                ascdescTime = TimeSpan.FromMinutes(timespent);
                cns += CentralNervousSystem.ConstantDepth(algorithm.ActiveGas, diffDepth, diffTime);
                otu += OxygenToxicityUnit.ConstantDepth(algorithm.ActiveGas, diffDepth, diffTime);
            }

            var seg = new DiveSegment(waypoint.Depth, ascdescTime, DiveState.Desend, runTime, runTime + timespent,
                                      algorithm.ActiveGas, cns, otu);
            Logger.Trace("Calc => Segment:" + seg);
            divetable.Add(seg);

            return ascdescTime;
        }
        private TimeSpan DoDeco(ZH_L16 algorithm, double currentDepth, double finalDepth, int runTime,
            ref Collection<DiveSegment> diveTable)
        {
            if (currentDepth < pref.MiniDepthForDeco)
            {
                Logger.Error("DoDeco => Can't do deco because of current depth is lower than min deco depth");
                throw new ArgumentException("currentDepth can't be smaller then miniDepthForDeco");
            }

            Logger.Trace("DoDeco => currentDepth:{0}, FinalDepth:{1}", currentDepth, finalDepth);

            double minDepthAscent = algorithm.MinDepthAscent(CalcGradient(firstDecoDepth, currentDepth, pref));
            double decoDepth = GetDecoDepth(minDepthAscent, pref);

            if (Math.Abs(currentDepth - decoDepth) > 0.001)
            {
                Logger.Error("DoDeco => Can't do deco because current depth is not deco depth");
                throw new ArgumentException("Can't do deco because current depth is not deco depth");
            }

            firstDecoDepth = decoDepth;

            Logger.Trace("DoDeco => start deco depth:" + decoDepth);

            var timeSpentDeco = new TimeSpan();

            double decoStartDepth = decoDepth;
            while (minDepthAscent > finalDepth)
            {
                decoDepth = GetDecoDepth(minDepthAscent, pref);

                int timespent = GetDecoStopTime(algorithm, decoDepth, pref, CalcGradient(firstDecoDepth, decoDepth, pref));
                TimeSpan decoTime = TimeSpan.FromMinutes(timespent);
                timeSpentDeco += TimeSpan.FromMinutes(timespent);
                double cns = CentralNervousSystem.ConstantDepth(algorithm.ActiveGas, decoDepth, timespent);
                double otu = OxygenToxicityUnit.ConstantDepth(algorithm.ActiveGas, decoDepth, timespent);
                var seg = new DiveSegment(decoDepth, decoTime, DiveState.Deco, runTime, runTime + timespent,
                                          algorithm.ActiveGas, cns, otu);
                Logger.Trace("DoDeco => Segment:" + seg);
                diveTable.Add(seg);
                runTime += timespent;

                if (Math.Abs(decoDepth - pref.MiniDepthForDeco) < 0.001)
                {
                    minDepthAscent = 0;
                }
                else
                {
                    minDepthAscent = decoDepth - pref.DepthBetweenDecoStops;
                }
            }

            return timeSpentDeco;
        }
 private static void StayAtDepth(ZH_L16 algorithm, ref Collection<DiveSegment> divetable, int runTime,
     double depth, double time)
 {
     algorithm.AddRunTimeInMinutes(time, depth);
     double cns = CentralNervousSystem.ConstantDepth(algorithm.ActiveGas, depth, time);
     double otu = OxygenToxicityUnit.ConstantDepth(algorithm.ActiveGas, depth, time);
     var seg = new DiveSegment(depth, TimeSpan.FromMinutes(time), DiveState.Diving, runTime,
                               runTime + TimeHelper.MinutesRoundedUp(TimeSpan.FromMinutes(time)), algorithm.ActiveGas, cns,
                               otu);
     Logger.Trace("Calc => Segment:" + seg);
     divetable.Add(seg);
 }
        public Collection<DiveSegment> Calc(Collection<WayPoint> waypoints, ZH_L16 algorithm, Preference preference)
        {
            pref = preference;

            var divetable = new Collection<DiveSegment>();
            int runTime = 0;
            double depth = 0;
            if (algorithm.ActiveGas == null)
            {
                algorithm.ActiveGas = waypoints[0].Gas;
            }

            foreach (WayPoint waypoint in waypoints)
            {
                var ascdescTime = new TimeSpan();

                //// Going down
                if (waypoint.Depth > depth)
                {
                    ascdescTime = GoingDown(algorithm, ref divetable, runTime, depth, waypoint);
                }

                //// Going upp
                if (waypoint.Depth < depth)
                {
                    ascdescTime = GoingUp(algorithm, ref divetable, runTime, depth, waypoint);
                }

                int timeSpentAscDesc = TimeHelper.MinutesRoundedUp(ascdescTime);
                runTime += timeSpentAscDesc;

                // Gas swith
                if (!Equals(algorithm.ActiveGas, waypoint.Gas))
                {
                    if (this.pref.TimeToSwitchGas > 0)
                    {
                        algorithm.AddRunTimeInMinutes(pref.TimeToSwitchGas, waypoint.Depth);
                    }

                    double cns = CentralNervousSystem.ConstantDepth(algorithm.ActiveGas, waypoint.Depth, pref.TimeToSwitchGas);
                    double otu = OxygenToxicityUnit.ConstantDepth(algorithm.ActiveGas, waypoint.Depth, pref.TimeToSwitchGas);
                    var seg = new DiveSegment(waypoint.Depth, new TimeSpan(0, 0, (int) pref.TimeToSwitchGas, 0, 0),
                                              DiveState.GasSwitch, runTime, runTime + (int) pref.TimeToSwitchGas,
                                              algorithm.ActiveGas, cns, otu);
                    Logger.Trace("Calc => Segment:" + seg);
                    divetable.Add(seg);
                    runTime += (int) pref.TimeToSwitchGas;
                    algorithm.ActiveGas = waypoint.Gas;
                }

                //// still time left to stay at depth
                if (timeSpentAscDesc < waypoint.Time)
                {
                    double timeLeft = waypoint.Time - timeSpentAscDesc;
                    StayAtDepth(algorithm, ref divetable, runTime, waypoint.Depth, timeLeft);
                    runTime += (int) waypoint.Time - timeSpentAscDesc;
                }

                depth = waypoint.Depth;
            }

            return divetable;
        }