using InABox.Core;
using NPOI.DDF;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace InABox.DynamicGrid
{
///
/// Interaction logic for ExpressionEditorWindow.xaml
///
public partial class ExpressionEditorWindow : Window
{
public static List>> FunctionTemplates = new();
public List Variables { get; private set; }
public string Expression
{
get => Editor.Text;
set => Editor.Text = value;
}
private static void RegisterFunctionTemplate(string group, string name, string? description, List parameters)
{
var groupList = FunctionTemplates.FirstOrDefault(x => x.Item1 == group)?.Item2;
if(groupList is null)
{
groupList = new List();
FunctionTemplates.Add(new(group, groupList));
}
groupList.Add(new FunctionTemplate
{
Name = name,
Parameters = parameters,
Description = description ?? ""
});
}
private static void RegisterFunctionTemplate(string group, string name, params string[] parameters)
=> RegisterFunctionTemplate(group, name, null, parameters.Select(x =>
{
var parts = x.Split(' ');
if (parts.Length == 1)
{
return new FunctionParameter(parts[0], "");
}
return new FunctionParameter(parts[1], parts[0]);
}).ToList());
private static void RegisterFunctionTemplateDesc(string group, string name, string description, params string[] parameters)
=> RegisterFunctionTemplate(group, name, description, parameters.Select(x =>
{
var parts = x.Split(' ');
if (parts.Length == 1)
{
return new FunctionParameter(parts[0], "");
}
return new FunctionParameter(parts[1], parts[0]);
}).ToList());
static ExpressionEditorWindow()
{
RegisterFunctionTemplate("Math", "Abs", "double x");
RegisterFunctionTemplate("Math", "Acos", "double x");
RegisterFunctionTemplate("Math", "Asin", "double x");
RegisterFunctionTemplate("Math", "Atan", "double x");
RegisterFunctionTemplate("Math", "Ceiling", "double x");
RegisterFunctionTemplate("Math", "Cos", "double x");
RegisterFunctionTemplate("Math", "Count", "...");
RegisterFunctionTemplate("Math", "Exp", "double exp");
RegisterFunctionTemplate("Math", "Floor", "double x");
RegisterFunctionTemplate("Math", "IEEERemainder", "double x", "double y");
RegisterFunctionTemplate("Math", "Log10", "double x");
RegisterFunctionTemplate("Math", "Log", "double x", "double base");
RegisterFunctionTemplate("Math", "Max", "double x", "double y");
RegisterFunctionTemplate("Math", "Min", "double x", "double y");
RegisterFunctionTemplateDesc("Math", "Pow", "Returns {base} ^ {exp}.", "double base", "double exp");
RegisterFunctionTemplate("Math", "Random");
RegisterFunctionTemplateDesc("Math", "Round", "Rounds a number to a particular number of decimal places, given by {precision}.", "double x", "double precision");
RegisterFunctionTemplate("Math", "Sign", "double x");
RegisterFunctionTemplate("Math", "Sin", "double x");
RegisterFunctionTemplate("Math", "Sqrt", "double x");
RegisterFunctionTemplate("Math", "Tan", "double x");
RegisterFunctionTemplate("Math", "Truncate", "double x");
RegisterFunctionTemplateDesc("Logical", "If", "If {condition} is true or non-zero, returns {exp1}; otherwise, returns {exp2}.", "bool condition", "exp1", "exp2");
RegisterFunctionTemplateDesc("Logical", "In", "Returns true if {value} is in the list of values.", "value", "...");
RegisterFunctionTemplateDesc("Statistical", "Average", "Takes the average of a list of numbers.", "double ...");
RegisterFunctionTemplateDesc("Statistical", "Mean", "Calculates the mean for a list of numbers.", "double ...");
RegisterFunctionTemplateDesc("Statistical", "Median", "Calculates the median for a list of numbers.", "double ...");
RegisterFunctionTemplateDesc("Statistical", "Mode", "Calculates the mode for a list of numbers.", "double ...");
RegisterFunctionTemplateDesc("Statistical", "Sum", "Calculates the sum of a list of numbers.", "double ...");
RegisterFunctionTemplate("String", "Contains", "string text", "str value");
RegisterFunctionTemplate("String", "EndsWith", "string text", "str value");
RegisterFunctionTemplate("String", "Length", "string value");
RegisterFunctionTemplate("String", "PadLeft", "string value", "int length", "char character");
RegisterFunctionTemplate("String", "PadRight", "string value", "int length", "char character");
RegisterFunctionTemplate("String", "StartsWith", "string text", "string value");
RegisterFunctionTemplate("String", "Substring", "string text", "int startIndex", "int length");
RegisterFunctionTemplate("Date", "AddYears", "date date", "int years");
RegisterFunctionTemplate("Date", "AddMonths", "date date", "int months");
RegisterFunctionTemplate("Date", "AddDays", "date date", "int days");
RegisterFunctionTemplate("Date", "AddHours", "date date", "int hrs");
RegisterFunctionTemplate("Date", "AddMinutes", "date date", "int mins");
RegisterFunctionTemplate("Date", "AddSeconds", "date date", "int secs");
RegisterFunctionTemplate("Date", "AddMilliseconds", "date date", "int ms");
RegisterFunctionTemplate("Date", "YearOf", "date date");
RegisterFunctionTemplate("Date", "MonthOf", "date date");
RegisterFunctionTemplate("Date", "DayOf", "date date");
RegisterFunctionTemplate("Date", "HourOf", "date date");
RegisterFunctionTemplate("Date", "MinuteOf", "date date");
RegisterFunctionTemplate("Date", "SecondOf", "date date");
RegisterFunctionTemplate("Date", "MillisecondOf", "date date");
RegisterFunctionTemplate("Date", "DaysBetween", "date date", "date date");
RegisterFunctionTemplate("Date", "HoursBetween", "date date", "date date");
RegisterFunctionTemplate("Date", "MinutesBetween", "date date", "date date");
RegisterFunctionTemplate("Date", "SecondsBetween", "date date", "date date");
RegisterFunctionTemplate("Date", "MillisecondsBetween", "date date", "date date");
RegisterFunctionTemplate("Conversion", "Date", "value");
RegisterFunctionTemplate("Conversion", "Date", "value", "string format");
RegisterFunctionTemplate("Conversion", "Decimal", "value");
RegisterFunctionTemplate("Conversion", "Double", "value");
RegisterFunctionTemplate("Conversion", "Integer", "value");
RegisterFunctionTemplate("Conversion", "Long", "value");
RegisterFunctionTemplate("Conversion", "String", "value");
foreach(var function in CoreExpression.Functions)
{
RegisterFunctionTemplate(
function.Group,
function.Name,
function.Description,
function.Parameters.Select(x => new FunctionParameter(x.Name, x.Type)).ToList());
}
}
public ExpressionEditorWindow(List variables)
{
Variables = variables;
InitializeComponent();
}
private void ExpressionWindow_Loaded(object sender, RoutedEventArgs e)
{
Editor.Focus();
}
private void InsertText(string text)
{
var newCaret = Editor.CaretIndex + text.Length;
Editor.Text = Editor.Text.Insert(Editor.CaretIndex, text);
Editor.CaretIndex = newCaret;
}
private void AppendText(string text)
{
var oldCaret = Editor.CaretIndex;
Editor.Text = Editor.Text.Insert(Editor.CaretIndex, text);
Editor.CaretIndex = oldCaret;
}
private void DeleteText(int start, int length)
{
var oldCaret = Editor.CaretIndex;
Editor.Text = Editor.Text.Remove(start, length);
if(oldCaret >= start && oldCaret < start + length)
{
Editor.CaretIndex = start;
}
else
{
Editor.CaretIndex = oldCaret - length;
}
}
private static Regex tagRegex = new("\\{\\S+\\}");
private static Regex backTagRegex = new("\\{\\S+\\}", RegexOptions.RightToLeft);
private bool DeletePlaceholder()
{
if (Editor.CaretIndex >= Editor.Text.Length) return false;
var tagStart = Editor.Text.LastIndexOf('{', Editor.CaretIndex);
if (tagStart == -1) return false;
var tagMatch = tagRegex.Match(Editor.Text, tagStart);
if (!tagMatch.Success) return false;
var startIndex = tagMatch.Index;
if (startIndex != tagStart) return false;
var length = tagMatch.Length;
if (Editor.CaretIndex >= startIndex + length) return false;
DeleteText(startIndex, length);
return true;
}
private void JumpPlaceholderBackwards()
{
if (Editor.CaretIndex == 0) return;
var tagMatch = backTagRegex.Match(Editor.Text, Editor.CaretIndex - 1);
if (!tagMatch.Success) return;
Editor.CaretIndex = tagMatch.Index;
}
private void JumpPlaceholder(bool allowStart = false)
{
if (Editor.CaretIndex >= Editor.Text.Length) return;
var start = allowStart ? Editor.CaretIndex : Editor.CaretIndex + 1;
var tagMatch = tagRegex.Match(Editor.Text, start);
if (!tagMatch.Success) return;
Editor.CaretIndex = tagMatch.Index;
}
private void FunctionTemplate_Click(object sender, MouseButtonEventArgs e)
{
if ((sender as FrameworkElement)!.Tag is not FunctionTemplate template) return;
var placeholder = DeletePlaceholder();
InsertText($"{template.Name}(");
AppendText(string.Join(", ", template.Parameters.Select(x => $"{{{x.Name}}}")) + ")");
if (placeholder)
JumpPlaceholder(true);
}
private void Variable_Click(object sender, MouseButtonEventArgs e)
{
if ((sender as FrameworkElement)!.Tag is not string str) return;
DeletePlaceholder();
InsertText($"[{str}]");
}
private void Editor_PreviewLostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
e.Handled = true;
}
private void Editor_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
DeletePlaceholder();
}
private void Editor_KeyDown(object sender, KeyEventArgs e)
{
if(e.Key == Key.Tab)
{
if((Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift)
{
JumpPlaceholderBackwards();
}
else
{
JumpPlaceholder();
}
e.Handled = true;
}
}
private void OK_Click(object sender, RoutedEventArgs e)
{
DialogResult = true;
}
}
}