static void Main(string[] args)
        {
            // Check if we need to print help by checking for -h or --help at the first or second index
            // Also, when fewer arguments are available, print the help
            int shortIndex = Array.IndexOf(args, "-h");
            int longIndex  = Array.IndexOf(args, "--help");

            if (args.Length < 2 || shortIndex == 0 || shortIndex == 1 || longIndex == 0 || longIndex == 1)
            {
                PrintHelp();
                return;
            }

            int  commandIndex = 1;
            byte runOnDays    = 0;

            if (args[1] == "--days")
            {
                commandIndex += 2;
                string[] splitDays = args[2].Split(",");

                for (int i = 0; i < splitDays.Length; i++)
                {
                    if (splitDays[i].Length == 1)
                    {
                        // Attempt to parse it as an integer
                        int dayIndex;
                        if (int.TryParse(splitDays[i], out dayIndex))
                        {
                            Day?foundIntDay = DayUtils.FindDay(dayIndex);
                            if (foundIntDay.HasValue)
                            {
                                runOnDays |= (byte)foundIntDay.Value;
                                continue;
                            }
                        }
                    }
                    // Attempt to find the day as valid string
                    Day?foundStrDay = DayUtils.FindDay(splitDays[i]);
                    if (!foundStrDay.HasValue)
                    {
                        Console.Error.WriteLine($"Invalid day: {splitDays[i]}");
                        Environment.Exit(1);
                    }
                    runOnDays |= (byte)foundStrDay.Value;
                }
            }

            string commandFileName  = args[commandIndex++];
            string commandArguments = CommandAction.BuildArguments(args, commandIndex);

            IntervalRange[] intervals = IntervalRange.GetIntervals(args[0], Console.Error);
            if (intervals == null)
            {
                Environment.Exit(1);
            }

            IntervalScheduler intervalScheduler = new IntervalScheduler(
                new CommandAction(commandFileName, commandArguments), intervals);

            if (runOnDays != 0)
            {
                intervalScheduler.SetDayFlags(runOnDays);
            }

            intervalScheduler.RunScheduler(CancellationToken.None);
        }
        /// <summary>
        /// Entrypoint of this program which parses the following arguments from the CLI:
        /// <list type="bullet">
        /// <item><code>--raw-dir</code>: The path to output directory to put the raw downloaded files in</item>
        /// <item><code>--processed-dir</code>: The path to the directory to put the processed files in</item>
        /// <item><code>--fetch-url</code>: The url to download to use as raw data</item>
        /// </list>
        /// </summary>
        /// <param name="args">The arguments passed from the command line</param>
        public static void Main(string[] args)
        {
            List <string> arguments = new List <string>();

            arguments.AddRange(args);

            // Print the help section when requested
            string[] helpLong  = FindOption(arguments, "--help", 0);
            string[] helpShort = FindOption(arguments, "-h", 0);
            if (helpLong != null || helpShort != null)
            {
                PrintHelp();
                return;
            }

            // First parse the interval, if available
            string[]        intervalOption = FindOption(arguments, "--interval");
            IntervalRange[] intervalRanges = null;
            if (intervalOption != null)
            {
                intervalRanges = IntervalRange.GetIntervals(intervalOption[0], Console.Error);
                if (intervalRanges == null)
                {
                    Environment.Exit(1);
                }
            }

            DataWriterOption selectedOption = null;

            DataWriterOption[] availableOptions =
            {
                new DbDataWriterOption(), new FileDataWriterOption()
            };

            foreach (DataWriterOption option in availableOptions)
            {
                if (option.isAvailable(arguments))
                {
                    if (!option.validArguments())
                    {
                        Environment.Exit(1);
                    }
                    selectedOption = option;
                    break;
                }
            }

            // Parse the arguments
            Options options = new Options
            {
                RawDataOutput       = GetDirectory(arguments, "--raw-dir"),
                ProcessedDataOutput = GetDirectory(arguments, "--processed-dir"),
                ReaderOption        = GetEnumOption <ReaderOption>(arguments, "--reader"),
                DataWriterOption    = selectedOption,
                RemoveRawFiles      = FindOption(arguments, "--remove-raw", 0) != null
            };

            // Parse the Fetch url
            string[] urlOption = FindOption(arguments, "--fetch-url");
            if (urlOption != null)
            {
                Uri fetchUrl;
                if (!Uri.TryCreate(urlOption[0], UriKind.Absolute, out fetchUrl) ||
                    (fetchUrl.Scheme != Uri.UriSchemeHttp && fetchUrl.Scheme != Uri.UriSchemeHttps))
                {
                    Console.Error.WriteLine($"Invalid fetch url {urlOption[0]}");
                    Environment.Exit(1);
                }

                options.FetchUri = fetchUrl;
            }

            BaseAction action = new DownloadAndProcessAction(options);

            if (intervalRanges == null)
            {
                action.Execute();
            }
            else
            {
                IntervalScheduler intervalScheduler = new IntervalScheduler(action, intervalRanges);
                intervalScheduler.RunScheduler(CancellationToken.None);
            }
        }
        private void InsertInternalInternal(IntLineSegment2 s, int sRangeId, double insertionThetaLower, double insertionThetaUpper, bool supportOverlappingLines, bool furthestSegmentWins)
        {
            // ReSharper disable once CompareOfFloatsByEqualityOperator
            if (insertionThetaLower == insertionThetaUpper)
            {
                return;
            }

            // See distrsxy for why this makes sense.
//         var sDist = _origin.To(sMidpoint).SquaredNorm2D();
            var srange = new IntervalRange {
                Id         = sRangeId,
                ThetaStart = insertionThetaLower,
                ThetaEnd   = insertionThetaUpper,
                Segment    = s
            };

            var splittableBeginIndexInclusive = FindOverlappingRangeIndex(insertionThetaLower, 0, true);
            var splittableEndIndexInclusive   = FindOverlappingRangeIndex(insertionThetaUpper, splittableBeginIndexInclusive, false);

            // a given segment can be split into 3 at max - technically this overallocates because it's impossible
            // for two 3-splits to happen in a row. Actually, assuming no overlaps one can only really produce
            // # splittables + 2 total new segments (new segments on left/right side).
            var n = new IntervalRange[(splittableEndIndexInclusive - splittableBeginIndexInclusive + 1) * 3];
            //new IntervalRange[(splittableEndIndexInclusive - splittableBeginIndexInclusive + 1) + 2];
            var nSize = 0;

            void EmitRange(int rangeId, ref IntLineSegment2 segment, double thetaStart, double thetaEnd)
            {
                if (thetaStart == thetaEnd)
                {
                    return;
                }
                if (nSize > 0 && n[nSize - 1].Id == rangeId)
                {
                    n[nSize - 1].ThetaEnd = thetaEnd;
                }
                else
                {
                    n[nSize] = new IntervalRange {
                        Id = rangeId, Segment = segment, ThetaStart = thetaStart, ThetaEnd = thetaEnd
                    };
                    nSize++;
                }
            }

            // near and far unioned must cover thetaUpper
            void HandleNearFarSplit(IntervalRange nearRange, IntervalRange farRange, double thetaLower, double thetaUpper)
            {
                // case: near covers range
                if (nearRange.ThetaStart <= thetaLower && thetaUpper <= nearRange.ThetaEnd)
                {
                    EmitRange(nearRange.Id, ref nearRange.Segment, thetaLower, thetaUpper);
                    return;
                    //               return new[] { new IntervalRange { Id = nearRange.Id, ThetaStart = thetaLower, ThetaEnd = thetaUpper, Segment = nearRange.Segment } };
                }

                // case: near exclusively within range
                if (thetaLower < nearRange.ThetaStart && nearRange.ThetaEnd < thetaUpper)
                {
                    EmitRange(farRange.Id, ref farRange.Segment, thetaLower, nearRange.ThetaStart);
                    EmitRange(nearRange.Id, ref nearRange.Segment, nearRange.ThetaStart, nearRange.ThetaEnd);
                    EmitRange(farRange.Id, ref farRange.Segment, nearRange.ThetaEnd, thetaUpper);
                    return;
                    //               return new[] {
                    //                  new IntervalRange { Id = farRange.Id, ThetaStart = thetaLower, ThetaEnd = nearRange.ThetaStart, Segment = farRange.Segment},
                    //                  new IntervalRange { Id = nearRange.Id, ThetaStart = nearRange.ThetaStart, ThetaEnd = nearRange.ThetaEnd, Segment = nearRange.Segment },
                    //                  new IntervalRange { Id = farRange.Id, ThetaStart = nearRange.ThetaEnd, ThetaEnd = thetaUpper, Segment = farRange.Segment}
                    //               };
                }

                // case: near covers left of range (as in, covers the lower thetas of range)
                if (nearRange.ThetaStart <= thetaLower && nearRange.ThetaEnd < thetaUpper)
                {
                    EmitRange(nearRange.Id, ref nearRange.Segment, thetaLower, nearRange.ThetaEnd);
                    EmitRange(farRange.Id, ref farRange.Segment, nearRange.ThetaEnd, thetaUpper);
                    return;
                    //               return new[] {
                    //                  new IntervalRange { Id = nearRange.Id, ThetaStart = thetaLower, ThetaEnd = nearRange.ThetaEnd, Segment = nearRange.Segment },
                    //                  new IntervalRange { Id = farRange.Id, ThetaStart = nearRange.ThetaEnd, ThetaEnd = thetaUpper, Segment = farRange.Segment }
                    //               };
                }

                // case: near covers right of range
                if (nearRange.ThetaStart > thetaLower && thetaUpper <= nearRange.ThetaEnd)
                {
                    EmitRange(farRange.Id, ref farRange.Segment, thetaLower, nearRange.ThetaStart);
                    EmitRange(nearRange.Id, ref nearRange.Segment, nearRange.ThetaStart, thetaUpper);
                    return;
                    //               return new[] {
                    //                  new IntervalRange { Id = farRange.Id, ThetaStart = thetaLower, ThetaEnd = nearRange.ThetaStart, Segment = farRange.Segment },
                    //                  new IntervalRange { Id = nearRange.Id, ThetaStart = nearRange.ThetaStart, ThetaEnd = thetaUpper, Segment = nearRange.Segment }
                    //               };
                }

                // impossible to reach here
                throw new Exception($"Impossible state at null split of {nameof(HandleNearFarSplit)}.");
            }

            void HandleSplit(IntervalRange range)
            {
                Debug.Assert(IsRangeOverlap(insertionThetaLower, insertionThetaUpper, range.ThetaStart, range.ThetaEnd));

                if (range.Id == RANGE_ID_INFINITELY_FAR)
                {
                    HandleNearFarSplit(srange, range, range.ThetaStart, range.ThetaEnd);
                    return;
                }

                var rsxy = range.Segment;

//            // is this code necessary? Seems like not... though not sure why. We do have intersecting segments
//            // but the intersect is quite minor (just at corners)...
                DoubleVector2 intersection;

                // HACK: No segment-segment intersect point implemented
                // sxy.Intersects(rsxy) && GeometryOperations.TryFindLineLineIntersection(sxy, rsxy, out intersection)
                if (GeometryOperations.TryFindSegmentSegmentIntersection(ref s, ref rsxy, out intersection))
                {
                    // conceptually a ray from _origin to intersection hits s and rs at the same time.
                    // If shifted perpendicular to angle of intersection, then the near segment emerges.
                    var thetaIntersect = FindXYRadiansRelativeToOrigin(intersection.X, intersection.Y);
                    if (range.ThetaStart <= thetaIntersect && thetaIntersect <= range.ThetaEnd)
                    {
                        var directionToLower        = DoubleVector2.FromRadiusAngle(1.0, thetaIntersect - PiDiv2);
                        var vsxy                    = s.First.To(s.Second).ToDoubleVector2().ToUnit();
                        var vrsxy                   = rsxy.First.To(rsxy.Second).ToDoubleVector2().ToUnit();
                        var lvsxy                   = vsxy.ProjectOntoComponentD(directionToLower) > 0 ? vsxy : -1.0 * vsxy;
                        var lvrsxy                  = vrsxy.ProjectOntoComponentD(directionToLower) > 0 ? vrsxy : -1.0 * vrsxy;
                        var originToIntersect       = _origin.To(intersection);
                        var clvsxy                  = lvsxy.ProjectOntoComponentD(originToIntersect);
                        var clvrsxy                 = lvrsxy.ProjectOntoComponentD(originToIntersect);
                        var isInserteeNearerAtLower = clvsxy < clvrsxy;
//                  Console.WriteLine("IINAL: " + isInserteeNearerAtLower);
                        if (isInserteeNearerAtLower)
                        {
                            HandleNearFarSplit(range, srange, range.ThetaStart, thetaIntersect);
                            HandleNearFarSplit(srange, range, thetaIntersect, range.ThetaEnd);
                        }
                        else
                        {
                            HandleNearFarSplit(srange, range, range.ThetaStart, thetaIntersect);
                            HandleNearFarSplit(range, srange, thetaIntersect, range.ThetaEnd);
                        }
                        return;
                    }
                }

                // At here, one segment completely overlaps the other for the theta range
                // Either that, or inserted segment in front of (but not totally covering) range
                // Either way, it will always be the case that any point on the "near" segment is closer
                // to _origin than any point on the "far" segment assuming within correct theta.
                // I take center of segments as their endpoints are ambiguous between neighboring segments
                // of a polygon.

//            var distrsxy = range.MidpointDistanceToOriginSquared;
                bool ComputeIsInserteeNearer()
                {
                    //range.Id != RANGE_ID_INFINITELY_FAR && (sRangeId == RANGE_ID_INFINITELY_FAR || _segmentComparer.Compare(s, rsxy) < 0);
                    if (range.Id == RANGE_ID_INFINITELY_FAR)
                    {
                        return(true);
                    }
                    if (range.Id == RANGE_ID_INFINITESIMALLY_NEAR)
                    {
                        return(false);
                    }
                    if (srange.Id == RANGE_ID_INFINITELY_FAR)
                    {
                        return(false);
                    }
                    if (srange.Id == RANGE_ID_INFINITESIMALLY_NEAR)
                    {
                        return(true);
                    }
                    return(_segmentComparer.Compare(s, rsxy) < 0);
                }

                bool inserteeNearer = ComputeIsInserteeNearer();
                var  nearRange      = inserteeNearer ? srange : range;
                var  farRange       = inserteeNearer ? range : srange;

                if (furthestSegmentWins)
                {
                    (nearRange, farRange) = (farRange, nearRange);
                }
                HandleNearFarSplit(nearRange, farRange, range.ThetaStart, range.ThetaEnd);
            }

//         n.AddRange(_intervalRanges.Take(ibegin));
            for (int it = splittableBeginIndexInclusive; it <= splittableEndIndexInclusive; it++)
            {
                HandleSplit(_intervalRanges[it]);
            }
            //         n.AddRange(_intervalRanges.Skip(iend + 1));

            bool segmentInserted = false;

            for (int i = 0; i < nSize && !segmentInserted; i++)
            {
                if (n[i].Id == srange.Id)
                {
                    segmentInserted = true;
                }
            }
            if (!segmentInserted)
            {
                return;
            }

            var nhead  = splittableBeginIndexInclusive;
            var ntail  = _intervalRanges.Length - splittableEndIndexInclusive - 1;
            var result = new IntervalRange[nhead + nSize + ntail];

            Array.Copy(_intervalRanges, 0, result, 0, nhead);
            Array.Copy(n, 0, result, nhead, nSize);
            Array.Copy(_intervalRanges, _intervalRanges.Length - ntail, result, nhead + nSize, ntail);
            _intervalRanges = result;
        }