using System; using System.Text.RegularExpressions; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Text; using System.Threading; using System.Threading.Tasks; using Discord; using Discord.WebSocket; using Newtonsoft.Json; using QRCoder; namespace vassago.Conversion { public static class Converter { public static string DebugInfo(){ var convertibles = knownConversions.Select(kc => kc.Item1).Union(knownConversions.Select(kc => kc.Item2)).Union( knownAliases.Keys.SelectMany(k => k)).Distinct(); return $"{convertibles.Count()} convertibles; {string.Join(", ", convertibles)}"; } private delegate decimal Convert1Way(decimal input); private static string currencyPath; private static ExchangePairs currencyConf = null; private static DateTime lastUpdatedCurrency = DateTime.UnixEpoch; private static List> knownConversions = new List>() { new Tuple("℉", "°C", (f => {return(f- 32.0m) / 1.8m;}), (c => {return 1.8m*c + 32.0m;})), }; private static Dictionary, string> knownAliases = new Dictionary, string>(new List, string>>()); public static void Load(string currencyPath) { Converter.currencyPath = currencyPath; var convConf = JsonConvert.DeserializeObject(File.ReadAllText("assets/conversion.json")); foreach (var unit in convConf.Units) { knownAliases.Add(unit.Aliases.ToList(), unit.Canonical); } foreach (var lp in convConf.LinearPairs) { AddLinearPair(lp.item1, lp.item2, lp.factor); } Task.Run(async () => { while(true) { loadCurrency(); await Task.Delay(TimeSpan.FromHours(8)); } }); } private static void loadCurrency() { Console.WriteLine("loading currency exchange data."); if(currencyConf != null) { knownConversions.RemoveAll(kc => kc.Item1 == currencyConf.Base); } if (File.Exists(currencyPath)) { currencyConf = JsonConvert.DeserializeObject(File.ReadAllText(currencyPath)); if(!knownAliases.ContainsValue(currencyConf.Base)) { knownAliases.Add(new List() { currencyConf.Base.ToLower() }, currencyConf.Base); } foreach (var rate in currencyConf.rates) { if(!knownAliases.ContainsValue(rate.Key)) { knownAliases.Add(new List() { rate.Key.ToLower() }, rate.Key); } AddLinearPair(currencyConf.Base, rate.Key, rate.Value); Console.WriteLine($"{rate.Key.ToLower()} alias of {rate.Key}"); } } } public static string Convert(decimal numericTerm, string sourceunit, string destinationUnit) { var normalizedSourceUnit = normalizeUnit(sourceunit); if (string.IsNullOrWhiteSpace(normalizedSourceUnit)) { return $"parse failure: what's {sourceunit}?"; } var normalizedDestUnit = normalizeUnit(destinationUnit); if (string.IsNullOrWhiteSpace(normalizedDestUnit)) { return $"parse failure: what's {destinationUnit}?"; } if (normalizedSourceUnit == normalizedDestUnit) { return $"source and dest are the same, so... {numericTerm} {normalizedDestUnit}?"; } var foundPath = exhaustiveBreadthFirst(normalizedDestUnit, new List() { normalizedSourceUnit })?.ToList(); if (foundPath != null) { var accumulator = numericTerm; for (int j = 0; j < foundPath.Count - 1; j++) { var forwardConversion = knownConversions.FirstOrDefault(kc => kc.Item1 == foundPath[j] && kc.Item2 == foundPath[j + 1]); if (forwardConversion != null) { accumulator = forwardConversion.Item3(accumulator); } else { var reverseConversion = knownConversions.First(kc => kc.Item2 == foundPath[j] && kc.Item1 == foundPath[j + 1]); accumulator = reverseConversion.Item4(accumulator); } } if (currencyConf != null && (normalizedDestUnit == currencyConf.Base || currencyConf.rates.Select(r => r.Key).Contains(normalizedDestUnit))) { return $"{String.Format("approximately {0:0.00}", accumulator)} {normalizedDestUnit} as of {currencyConf.DateUpdated.ToLongDateString()}"; } else { if(String.Format("{0:G3}", accumulator).Contains("E-")) { return $"{accumulator} {normalizedDestUnit}"; } else { return $"{String.Format("{0:N}", accumulator)} {normalizedDestUnit}"; } } } return "dimensional analysis failure - I know those units but can't find a path between them."; } private static string normalizeUnit(string unit) { if(string.IsNullOrWhiteSpace(unit)) return null; var normalizedUnit = unit.ToLower(); if (knownConversions.FirstOrDefault(c => c.Item1 == normalizedUnit || c.Item2 == normalizedUnit) != null) { return normalizedUnit; } if (!knownAliases.ContainsValue(normalizedUnit)) { var key = knownAliases.Keys.FirstOrDefault(listkey => listkey.Contains(normalizedUnit)); if (key != null) { return knownAliases[key]; } } if (normalizedUnit.EndsWith("es")) { return normalizeUnit(normalizedUnit.Substring(0, normalizedUnit.Length - 2)); } else if (normalizedUnit.EndsWith('s')) { return normalizeUnit(normalizedUnit.Substring(0, normalizedUnit.Length - 1)); } return null; } private static IEnumerable exhaustiveBreadthFirst(string dest, IEnumerable currentPath) { var last = currentPath.Last(); if (last == dest) { return currentPath; } var toTest = new List>(); foreach (var conv in knownConversions) { if (conv.Item1 == last && currentPath.Contains(conv.Item2) == false && conv.Item3 != null) { var test = exhaustiveBreadthFirst(dest, currentPath.Append(conv.Item2)); if (test != null) return test; } if (conv.Item2 == last && currentPath.Contains(conv.Item1) == false && conv.Item4 != null) { var test = exhaustiveBreadthFirst(dest, currentPath.Append(conv.Item1)); if (test != null) return test; } } return null; } private static void AddLinearPair(string key1, string key2, decimal factor) { var reverseFactor = 1.0m / factor; knownConversions.Add(new Tuple( key1, key2, x => x * factor, y => y * reverseFactor )); } } }