using ShellProgressBar; using System.Collections.ObjectModel; using View_by_Distance.Shared.Models; using View_by_Distance.Shared.Models.Stateless.Methods; namespace View_by_Distance.Map.Models.Stateless; internal abstract class RelationLogic { internal record Group(string Key, long PersonKey, ReadOnlyCollection RelationContainersCollection); private static Dictionary>> GetPersonKeyTo(Configuration configuration, List locationContainers) { List? collection; Dictionary>? yearTo; Dictionary>> personKeyTo = []; foreach (LocationContainer locationContainer in locationContainers) { if (locationContainer.PersonKey is null) continue; if (!locationContainer.FromDistanceContent) continue; if (!locationContainer.FilePath.FullName.Contains(configuration.LocationContainerDirectoryPattern)) continue; if (!personKeyTo.TryGetValue(locationContainer.PersonKey.Value, out yearTo)) { personKeyTo.Add(locationContainer.PersonKey.Value, []); if (!personKeyTo.TryGetValue(locationContainer.PersonKey.Value, out yearTo)) throw new Exception(); } if (!yearTo.TryGetValue(locationContainer.CreationDateOnly.Year, out collection)) { yearTo.Add(locationContainer.CreationDateOnly.Year, []); if (!yearTo.TryGetValue(locationContainer.CreationDateOnly.Year, out collection)) throw new Exception(); } collection.Add(locationContainer); } return personKeyTo; } private static ReadOnlyCollection GetGroups(Configuration configuration, List locationContainers) { List results = []; string key; int lastIndex; List years = []; List indices = []; LocationContainer locationContainer; List<(int Index, int Year)> sort = []; List collection = []; KeyValuePair> keyValue; Dictionary>> personKeyTo = GetPersonKeyTo(configuration, locationContainers); foreach (KeyValuePair>> keyValuePair in personKeyTo) { sort.Clear(); years.Clear(); indices.Clear(); for (int i = 0; i < keyValuePair.Value.Count; i++) sort.Add(new(i, keyValuePair.Value.ElementAt(i).Key)); if (sort.Count == 0) continue; foreach ((int index, int _) in sort.OrderBy(l => l.Year)) indices.Add(index); lastIndex = indices[^1]; foreach (int index in indices) { keyValue = keyValuePair.Value.ElementAt(index); if (keyValue.Value.Count == 0) continue; years.Add(keyValue.Key); collection.AddRange(keyValue.Value); if (index != lastIndex && years.Count < 6 && years.Sum() > configuration.LocationContainerDistanceGroupMinimum) continue; if (years.Count == 1) key = keyValue.Key.ToString(); else key = $"{years.Min()}-{keyValue.Key}"; if (collection.Count == 0) continue; locationContainer = collection[0]; if (locationContainer.PersonKey is null) continue; results.Add(new(key, locationContainer.PersonKey.Value, new(collection))); collection = []; years.Clear(); } } return new(results); } private static ReadOnlyDictionary MoveFiles(Configuration configuration, string key, bool isCounterPersonYear, string? displayDirectoryName, ReadOnlyCollection relationContainers, List> linked) { Dictionary results = []; string value; string checkFile; string debugFile; string checkDirectory; string? yearDirectory; string? personNameDirectory; string? maybeTicksDirectoryName; string? personNameDirectoryName; string? personKeyFormattedDirectory; foreach ((FileHolder fileHolder, _) in relationContainers) { personNameDirectory = fileHolder.DirectoryName; yearDirectory = Path.GetDirectoryName(personNameDirectory); personNameDirectoryName = Path.GetFileName(personNameDirectory); personKeyFormattedDirectory = Path.GetDirectoryName(yearDirectory); maybeTicksDirectoryName = Path.GetFileName(personKeyFormattedDirectory); if (string.IsNullOrEmpty(personNameDirectory) || string.IsNullOrEmpty(yearDirectory) || string.IsNullOrEmpty(personKeyFormattedDirectory) || string.IsNullOrEmpty(personNameDirectoryName)) continue; if (linked[24].Contains(fileHolder.FullName)) value = "ZZZ"; else if (linked[23].Contains(fileHolder.FullName)) value = "YYY"; else if (linked[22].Contains(fileHolder.FullName)) value = "XXX"; else if (linked[21].Contains(fileHolder.FullName)) value = "WWW"; else if (linked[20].Contains(fileHolder.FullName)) value = "VVV"; else if (linked[19].Contains(fileHolder.FullName)) value = "UUU"; else if (linked[18].Contains(fileHolder.FullName)) value = "TTT"; else if (linked[17].Contains(fileHolder.FullName)) value = "SSS"; else if (linked[16].Contains(fileHolder.FullName)) value = "RRR"; else if (linked[15].Contains(fileHolder.FullName)) value = "QQQ"; else if (linked[14].Contains(fileHolder.FullName)) value = "PPP"; else if (linked[13].Contains(fileHolder.FullName)) value = "OOO"; else if (linked[12].Contains(fileHolder.FullName)) value = "NNN"; else if (linked[11].Contains(fileHolder.FullName)) value = "MMM"; else if (linked[10].Contains(fileHolder.FullName)) value = "LLL"; else if (linked[9].Contains(fileHolder.FullName)) value = "KKK"; else if (linked[8].Contains(fileHolder.FullName)) value = "JJJ"; else if (linked[7].Contains(fileHolder.FullName)) value = "III"; else if (linked[6].Contains(fileHolder.FullName)) value = "HHH"; else if (linked[5].Contains(fileHolder.FullName)) value = "GGG"; else if (linked[4].Contains(fileHolder.FullName)) value = "FFF"; else if (linked[3].Contains(fileHolder.FullName)) value = "EEE"; else if (linked[2].Contains(fileHolder.FullName)) value = "DDD"; else if (linked[1].Contains(fileHolder.FullName)) value = "CCC"; else if (linked[0].Contains(fileHolder.FullName)) value = "BBB"; else value = "AAA"; if (maybeTicksDirectoryName == configuration.LocationContainerDebugDirectory) checkDirectory = Path.Combine(yearDirectory, $"{key}-{value}"); else { if (!string.IsNullOrEmpty(configuration.LocationContainerDebugDirectory)) continue; checkDirectory = Path.Combine(personKeyFormattedDirectory, $"{key}-{value}"); } if (maybeTicksDirectoryName != configuration.LocationContainerDebugDirectory) { if (isCounterPersonYear || string.IsNullOrEmpty(displayDirectoryName)) checkDirectory = Path.Combine(checkDirectory, personNameDirectoryName); else checkDirectory = Path.Combine(checkDirectory, displayDirectoryName[0].ToString()); } if (checkDirectory == personNameDirectory) continue; if (!Directory.Exists(checkDirectory)) _ = Directory.CreateDirectory(checkDirectory); checkFile = Path.Combine(checkDirectory, fileHolder.Name); if (File.Exists(checkFile)) continue; results.Add(fileHolder.FullName, checkFile); File.Move(fileHolder.FullName, checkFile); debugFile = $"{fileHolder.FullName[..^4]}.gif"; if (File.Exists(debugFile)) { checkFile = Path.Combine(checkDirectory, $"{Path.GetFileName(fileHolder.FullName)[..^4]}.gif"); if (File.Exists(checkFile)) continue; File.Move(debugFile, checkFile); } if (maybeTicksDirectoryName == configuration.LocationContainerDebugDirectory && !string.IsNullOrEmpty(displayDirectoryName)) { checkDirectory = Path.Combine(checkDirectory, displayDirectoryName); if (!Directory.Exists(checkDirectory)) _ = Directory.CreateDirectory(checkDirectory); } } return new(results); } private static string? GetDisplayDirectoryName(ReadOnlyDictionary> readOnlyPersonKeyToPersonContainerCollection, ReadOnlyDictionary readOnlyPersonKeyFormattedToPersonContainer, long personKey, string personKeyFormatted) { string? result; PersonContainer? personContainer; List? collection; _ = readOnlyPersonKeyToPersonContainerCollection.TryGetValue(personKey, out collection); if (collection is not null) result = collection[0].DisplayDirectoryName; else { if (!readOnlyPersonKeyFormattedToPersonContainer.TryGetValue(personKeyFormatted, out personContainer)) result = null; else result = personContainer.DisplayDirectoryName; } return result; } private static int GetTake(int locationContainerDistanceTake, int count) { int result = locationContainerDistanceTake; int subtract = (int)(locationContainerDistanceTake * .05); if (subtract < 1) subtract = 1; if (count > 9000) result -= subtract; if (count > 8000) result -= subtract; if (count > 7000) result -= subtract; if (count > 6000) result -= subtract; if (count > 5000) result -= subtract; if (count > 4000) result -= subtract; if (count > 3000) result -= subtract; if (count > 2000) result -= subtract; if (count > 1000) result -= subtract; if (result < 3) result = 3; return result; } private static void WriteVsCodeFiles(string eDistanceContentDirectory, string? displayDirectoryName, string directory) { string json; string vsCodeDirectory = Path.Combine(directory, ".vscode"); if (!Directory.Exists(vsCodeDirectory)) _ = Directory.CreateDirectory(vsCodeDirectory); if (displayDirectoryName is not null) File.WriteAllText(Path.Combine(directory, $"_ {displayDirectoryName}.txt"), string.Empty); json = /*lang=json*/ """{ "[markdown]": { "editor.wordWrap": "off" }, "foam.links.hover.enable": false, "foam.graph.style": { "background": "#202020", "node": { "note": "#f2cb1d", "distance": "green", "image": "orange", "placeholder": "white", } } }"""; _ = IPath.WriteAllText(Path.Combine(vsCodeDirectory, "settings.json"), json, updateDateWhenMatches: false, compareBeforeWrite: true, updateToWhenMatches: null); json = string.Concat("{ \"version\": \"2.0.0\", \"tasks\": [ { \"label\": \"MKLink\", \"type\": \"shell\", \"command\": \"New-Item\", \"args\": [ \"-ItemType\", \"Junction\", \"-Path\", \"'", directory.Replace('\\', '/'), "/()'\", \"-Target\", \"'", eDistanceContentDirectory.Replace('\\', '/'), "'\" ], \"problemMatcher\": [] } ] }"); _ = IPath.WriteAllText(Path.Combine(vsCodeDirectory, "tasks.json"), json, updateDateWhenMatches: false, compareBeforeWrite: true, updateToWhenMatches: null); } private static ReadOnlyDictionary GetMoveFiles(Configuration configuration, string key, int take, bool isCounterPersonYear, string? displayDirectoryName, ReadOnlyCollection relationContainers) { ReadOnlyDictionary results; List> linked = []; for (int i = 0; i < 25; i++) linked.Add([]); foreach ((FileHolder fileHolder, ReadOnlyCollection relations) in relationContainers) { foreach (Relation relation in relations.Take(take)) { for (int i = 0; i < 25; i++) { if (!linked[i].Contains(relation.File)) { linked[i].Add(relation.File); break; } } } } results = MoveFiles(configuration, key, isCounterPersonYear, displayDirectoryName, relationContainers, linked); return results; } private static void WriteFile(int take, long personKey, bool isCounterPersonYear, string personKeyFormatted, string? displayDirectoryName, string directory, long ticks, Uri uri, ReadOnlyCollection relationContainers, ReadOnlyDictionary movedFiles) { string a; string b; int years; string text; string? file; Relation relation; string markDownFile; FileHolder fileHolder; string originalString; List lines = []; string fileNameWithoutExtension; foreach ((FileHolder relationFileHolder, ReadOnlyCollection relations) in relationContainers) { lines.Clear(); if (movedFiles.TryGetValue(relationFileHolder.FullName, out file)) fileHolder = IFileHolder.Get(file); else fileHolder = IFileHolder.Get(relationFileHolder.FullName); if (!relationFileHolder.Exists || relationFileHolder.CreationTime is null) continue; if (isCounterPersonYear) (years, _) = IAge.GetAge(ticks, relationFileHolder.CreationTime.Value.Ticks); else (years, _) = IAge.GetAge(relationFileHolder.CreationTime.Value.Ticks, personKey); fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fileHolder.NameWithoutExtension); markDownFile = $"{fileNameWithoutExtension}.md"; lines.Add("---"); lines.Add("type: \"distance\""); lines.Add("---"); lines.Add(string.Empty); if (displayDirectoryName is null) lines.Add($"## {personKeyFormatted}"); else lines.Add($"## {displayDirectoryName}"); lines.Add(string.Empty); lines.Add($"![0]({uri.MakeRelativeUri(new(fileHolder.FullName)).OriginalString})"); lines.Add($"- __{fileNameWithoutExtension}__"); if (isCounterPersonYear) lines.Add($"- #{years}yrs-ago"); else lines.Add($"- #{years}yrs-old"); lines.Add($"- ~~{relations.Count}~~"); lines.Add(string.Empty); for (int i = 0; i < relations.Count; i++) { relation = relations[i]; if (movedFiles.TryGetValue(relation.File, out file)) fileHolder = IFileHolder.Get(file); else fileHolder = IFileHolder.Get(relation.File); if (!fileHolder.Exists || fileHolder.CreationTime is null) continue; fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fileHolder.NameWithoutExtension); originalString = uri.MakeRelativeUri(new(fileHolder.FullName)).OriginalString; if (i < take) (a, b) = ("[[", "]]"); else (a, b) = ("~~", "~~"); lines.Add($"![{relation.DistancePermyriad}]({originalString}){Environment.NewLine}{a}{fileNameWithoutExtension}{b}"); lines.Add(string.Empty); } lines.Add(""); text = string.Join(Environment.NewLine, lines); File.WriteAllText(Path.Combine(directory, markDownFile), text); } } private static void AddDisplayDirectoryNames(Configuration configuration, string eDistanceContentDirectory, ReadOnlyDictionary readOnlyPersonKeyFormattedToPersonContainer, ReadOnlyDictionary> readOnlyPersonKeyToPersonContainerCollection, ReadOnlyCollection groups) { bool isCounterPersonYear; string personKeyFormatted; string? displayDirectoryName; string? checkDirectory = Path.Combine(eDistanceContentDirectory, configuration.LocationContainerDebugDirectory); _ = IPath.DeleteEmptyDirectories(checkDirectory); foreach (Group group in groups) { if (configuration.LocationContainerDistanceTolerance is null) break; isCounterPersonYear = IPersonBirthday.IsCounterPersonYear(new DateTime(group.PersonKey).Year); if (isCounterPersonYear) continue; personKeyFormatted = IPersonBirthday.GetFormatted(configuration.PersonBirthdayFormat, group.PersonKey); checkDirectory = Path.Combine(eDistanceContentDirectory, configuration.LocationContainerDebugDirectory, personKeyFormatted); if (!Directory.Exists(checkDirectory)) continue; displayDirectoryName = GetDisplayDirectoryName(readOnlyPersonKeyToPersonContainerCollection, readOnlyPersonKeyFormattedToPersonContainer, group.PersonKey, personKeyFormatted); if (string.IsNullOrEmpty(displayDirectoryName)) continue; foreach (string yearDirectory in Directory.GetDirectories(checkDirectory, "*", SearchOption.TopDirectoryOnly)) { checkDirectory = Path.Combine(yearDirectory, displayDirectoryName); if (!Directory.Exists(checkDirectory)) _ = Directory.CreateDirectory(checkDirectory); } checkDirectory = Path.Combine(eDistanceContentDirectory, configuration.LocationContainerDebugDirectory, personKeyFormatted, displayDirectoryName); if (!Directory.Exists(checkDirectory)) _ = Directory.CreateDirectory(checkDirectory); } } internal static void SaveMappedRelations(Configuration configuration, Shared.Models.Methods.IDistance distance, string a2PeopleContentDirectory, string eDistanceContentDirectory, long ticks, List locationContainers, ReadOnlyDictionary readOnlyPersonKeyFormattedToPersonContainer, ReadOnlyDictionary> readOnlyPersonKeyToPersonContainerCollection) { int take; string directory; bool isCounterPersonYear; string personKeyFormatted; string? displayDirectoryName; Uri uri = new(eDistanceContentDirectory); ReadOnlyDictionary movedFiles; ReadOnlyCollection relationContainers; ReadOnlyCollection groups = GetGroups(configuration, locationContainers); int totalSeconds = (int)Math.Floor(new TimeSpan(DateTime.Now.Ticks - ticks).TotalSeconds); string message = $") Save Mapped Relations - {totalSeconds} total second(s)"; ProgressBarOptions options = new() { ProgressCharacter = '─', ProgressBarOnBottom = true, DisableBottomPercentage = true }; using ProgressBar progressBar = new(groups.Count, message, options); foreach (Group group in groups) { if (configuration.LocationContainerDistanceTolerance is null) break; progressBar.Tick(); if (group.RelationContainersCollection.Count == 0) continue; take = GetTake(configuration.LocationContainerDistanceTake, group.RelationContainersCollection.Count); isCounterPersonYear = IPersonBirthday.IsCounterPersonYear(new DateTime(group.PersonKey).Year); personKeyFormatted = IPersonBirthday.GetFormatted(configuration.PersonBirthdayFormat, group.PersonKey); displayDirectoryName = GetDisplayDirectoryName(readOnlyPersonKeyToPersonContainerCollection, readOnlyPersonKeyFormattedToPersonContainer, group.PersonKey, personKeyFormatted); directory = Path.Combine(a2PeopleContentDirectory, $"{ticks}-{configuration.LocationContainerDistanceTolerance.Value}", personKeyFormatted, group.Key); if (!Directory.Exists(directory)) _ = Directory.CreateDirectory(directory); WriteVsCodeFiles(eDistanceContentDirectory, displayDirectoryName, directory); relationContainers = distance.GetRelationContainers(configuration.DistanceLimits, configuration.FaceDistancePermyriad, configuration.LocationContainerDistanceTake, configuration.LocationContainerDistanceTolerance.Value, group.RelationContainersCollection); movedFiles = GetMoveFiles(configuration, group.Key, take, isCounterPersonYear, displayDirectoryName, relationContainers); WriteFile(take, group.PersonKey, isCounterPersonYear, personKeyFormatted, displayDirectoryName, directory, ticks, uri, relationContainers, movedFiles); } if (string.IsNullOrEmpty(configuration.LocationContainerDebugDirectory)) _ = IPath.DeleteEmptyDirectories(eDistanceContentDirectory); else AddDisplayDirectoryNames(configuration, eDistanceContentDirectory, readOnlyPersonKeyFormattedToPersonContainer, readOnlyPersonKeyToPersonContainerCollection, groups); } }