// nohousenumber=yes
        // consider just leaving out all addr:unit=*
        // option to moveNode or not. (Do you trust E911 locations more than OSM?)
        // MatchWidth list to handle multi-match

        // Maybe fix addr:street punctuation on elements that I didn't add or update
        // Maybe fix addr:street should be addr:place on elements that I didn't add or update
        // Maybe fix nodes merging into buildings on elements that I didn't add or update
        public void Main()
        {
            Static.Municipalities = FileSerializer.ReadJsonCacheOrSource("MaineMunicipalities.json",
                                                                         GetMunicipalities).Result;
            ShowProgress();
            Municipality = Static.Municipalities.Values.First(m => !m.ChangeSetIds.Any()).Name;
            Console.WriteLine("Starting in " + Municipality);

            while (true)
            {
                Console.Write("> ");
                var userInput = Console.ReadLine();

                if (Is(userInput, "reference"))
                {
                    //Func<Feature, bool> filter = f => (f.Geometry as Point).Coordinates.Longitude >= -70.505;
                    var reference = References.Fetch(Municipality).Result;
                    FileSerializer.WriteXml(Municipality + "/Reference.osm", reference);
                    File.Delete(Municipality + "/Conflated.osc");
                }
                else if (Is(userInput, "review ref"))
                {
                    var reference = GetReference();
                    References.Report(reference.GetElements().ToArray());
                }
                else if (Is(userInput, "subject"))
                {
                    var reference = GetReference();
                    var subject   = Subjects.GetElementsInBoundingBox(reference.Bounds.ExpandBy(15));
                    FileSerializer.WriteXml(Municipality + "/Subject.osm", subject);
                    File.Delete(Municipality + "/Conflated.osc");
                    Console.WriteLine("ChangeId high watermark: " + subject.GetHighestChangeSetId());
                }
                else if (Is(userInput, "conflate"))
                {
                    DoConflate();
                }
                else if (Is(userInput, "review con"))
                {
                    Conflate.Review(Municipality);
                }
                else if (Is(userInput, "review"))
                {
                    OpenJosm();
                }
                else if (Is(userInput, "list"))
                {
                    var key       = userInput.Split(" ")[1];
                    var reference = GetReference();
                    var values    = reference.GetElements().Where(e => e.Tags.ContainsKey(key)).Select(e => "\n\t" + e.Tags[key]).GroupBy(n => n).ToArray();

                    Console.WriteLine(string.Concat(values.Select(v => v.Key + "\tx" + v.Count())));
                }
                else if (Is(userInput, "filter"))
                {
                    var key       = userInput.Split(" ")[1];
                    var reference = GetReference();
                    DoFilter(key, reference.GetElements());
                    File.Delete(Municipality + "/Reference.osm");
                    File.Delete(Municipality + "/Conflated.osc");
                }
                else if (Is(userInput, "note"))
                {
                    Static.Municipalities[Municipality].Notes += "/n" + userInput.Split(' ', 2)[1];
                    FileSerializer.WriteJson("MaineMunicipalities.json", Static.Municipalities);
                }
                else if (Is(userInput, "WhiteAll"))
                {
                    var review    = FileSerializer.ReadXml <Osm>(Municipality + "/Conflated.Review.osm");
                    var selection = review.GetElements()
                                    .Where(e => e.Tags != null && e.Tags.ContainsKey(Static.maineE911id))
                                    .Select(e => e.Tags[Static.maineE911id])
                                    .SelectMany(id => id.Split(new char[] { ' ', ',', ';', '-' }, StringSplitOptions.RemoveEmptyEntries))
                                    .Select(long.Parse)
                                    .Except(Static.Municipalities[Municipality].WhiteList)
                                    .ToArray();
                    Static.Municipalities[Municipality].WhiteList.AddRange(selection);

                    FileSerializer.WriteJson("MaineMunicipalities.json", Static.Municipalities);
                    File.Delete(Municipality + "/Conflated.osc");
                }
                else if (Is(userInput, "white"))
                {
                    var selection = userInput.Split(' ', 2)[1]
                                    .Split(new char[] { ' ', ',', ';', '-', '=' }, StringSplitOptions.RemoveEmptyEntries)
                                    .Where(c => long.TryParse(c, out _))
                                    .Select(long.Parse)
                                    .Except(Static.Municipalities[Municipality].WhiteList)
                                    .ToArray();
                    Static.Municipalities[Municipality].WhiteList.AddRange(selection);

                    FileSerializer.WriteJson("MaineMunicipalities.json", Static.Municipalities);
                    File.Delete(Municipality + "/Conflated.osc");
                }
                else if (Is(userInput, "blacktag"))
                {
                    var tag = userInput.Split(' ', 2)[1].Replace("maineE911id=", "");
                    Static.Municipalities[Municipality].BlackTags.Add(tag);
                    Static.Municipalities[Municipality].BlackTags = Static.Municipalities[Municipality].BlackTags.Distinct().ToList();
                    FileSerializer.WriteJson("MaineMunicipalities.json", Static.Municipalities);
                    File.Delete(Municipality + "/Reference.osm");
                    File.Delete(Municipality + "/Conflated.osc");
                }
                else if (Is(userInput, "black"))
                {
                    AddToList(userInput.Split(' ', 2)[1], Static.Municipalities[Municipality].BlackList);
                    FileSerializer.WriteJson("MaineMunicipalities.json", Static.Municipalities);
                    File.Delete(Municipality + "/Reference.osm");
                    File.Delete(Municipality + "/Conflated.osc");
                }
                else if (Is(userInput, "ignore"))
                {
                    AddToList(userInput.Split(' ', 2)[1], Static.Municipalities[Municipality].IgnoreList);
                    FileSerializer.WriteJson("MaineMunicipalities.json", Static.Municipalities);
                }
                else if (Is(userInput, "commit"))
                {
                    var change  = FileSerializer.ReadXml <OsmChange>(Municipality + "/Conflated.osc");
                    var results = Subjects.UploadChange(change, Municipality).Result;
                    Static.Municipalities[Municipality].ChangeSetIds.AddRange(results);
                    Static.Municipalities[Municipality].ImportDate = DateTime.UtcNow;
                    FileSerializer.WriteJson("MaineMunicipalities.json", Static.Municipalities);
                    Console.WriteLine("Finished!");

                    Next();
                }
                else if (Is(userInput, "skip"))
                {
                    Static.Municipalities[Municipality].Notes += " SKIPPED";
                    Static.Municipalities[Municipality].ChangeSetIds.Add(-1);
                    FileSerializer.WriteJson("MaineMunicipalities.json", Static.Municipalities);
                }
                else if (Is(userInput, "next"))
                {
                    Next();
                }
                else if (Is(userInput, "switch"))
                {
                    Municipality = ChooseMunicipality();
                    ShowProgress();
                }
                else if (Is(userInput, "folder"))
                {
                    OpenExplorer();
                }
                else if (Is(userInput, "remind"))
                {
                    var parts     = userInput.Split(' ', 3);
                    var id        = long.Parse(parts[1].Replace("maineE911id=", ""));
                    var reference = GetReference();
                    var element   = reference.Nodes.First(e => Math.Abs(e.Id.Value) == Math.Abs(id));
                    var message   = parts.Length > 2 ? parts[2] : "The addresses imported on this neighborhood need to be aligned with the correct buildings";
                    Subjects.CreateNote(element.Latitude.Value, element.Longitude.Value, message).Wait();
                }
                else if (Is(userInput, "help"))
                {
                    Console.WriteLine("Options:");
                    Console.WriteLine("\tSwitch");
                    Console.WriteLine("\tNext");
                    Console.WriteLine("\tReference");
                    Console.WriteLine("\tReview Reference");
                    Console.WriteLine("\tSubject");
                    Console.WriteLine("\tConflate");
                    Console.WriteLine("\tReview Conflate");
                    Console.WriteLine("\tReview");
                    Console.WriteLine("\tList [key]");
                    Console.WriteLine("\tFilter [key]");
                    Console.WriteLine("\t\t[Y/N/AUnit/Descr/Build/Move]");
                    Console.WriteLine("\tNote [message]");
                    Console.WriteLine("\tWhitelist [###]<,[###]...>");
                    Console.WriteLine("\tWhiteAll");
                    Console.WriteLine("\tBlacklist [###]<,[###]...>");
                    Console.WriteLine("\tBlackTag [###].[key] || *.[key]=[value]");
                    Console.WriteLine("\tIgnore [###]<,[###]...>");
                    Console.WriteLine("\tRemind [ref###] <message>");
                    Console.WriteLine("\tCommit");
                }
                else
                {
                    Console.WriteLine("What?");
                }

                Console.WriteLine("Done");
            }
        }
 private Osm GetReference()
 {
     return(FileSerializer.ReadXmlCacheOrSource(Municipality + "/Reference.osm",
                                                () => References.Fetch(Municipality)).Result);
 }