using System;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;
using System.Windows.Threading;
using MPF.Core.Data;
#pragma warning disable IDE1006 // Naming Styles
namespace MPF.UI.Core.UserControls
{
public partial class LogOutput : UserControl
{
///
/// Document representing the text
///
internal FlowDocument Document { get; private set; }
///
/// Queue of items that need to be logged
///
internal ProcessingQueue LogQueue { get; private set; }
///
/// Paragraph backing the log
///
private readonly Paragraph _paragraph;
///
/// Cached value of the last line written
///
private Run? lastLine = null;
#if NET35
private Button? _ClearButton => ItemHelper.FindChild(this, "ClearButton");
private RichTextBox? _Output => ItemHelper.FindChild(this, "Output");
private ScrollViewer? _OutputViewer => ItemHelper.FindChild(this, "OutputViewer");
private Button? _SaveButton => ItemHelper.FindChild(this, "SaveButton");
#endif
public LogOutput()
{
#if NET40_OR_GREATER || NETCOREAPP
InitializeComponent();
#endif
// Update the internal state
Document = new FlowDocument()
{
Background = new SolidColorBrush(Color.FromArgb(0xFF, 0x20, 0x20, 0x20))
};
_paragraph = new Paragraph();
Document.Blocks.Add(_paragraph);
// Setup the processing queue
LogQueue = new ProcessingQueue(ProcessLogLine);
// Add handlers
#if NET35
_OutputViewer!.SizeChanged += OutputViewerSizeChanged;
_Output!.TextChanged += OnTextChanged;
_ClearButton!.Click += OnClearButton;
_SaveButton!.Click += OnSaveButton;
#else
OutputViewer.SizeChanged += OutputViewerSizeChanged;
Output.TextChanged += OnTextChanged;
ClearButton.Click += OnClearButton;
SaveButton.Click += OnSaveButton;
#endif
// Update the internal state
#if NET35
_Output.Document = Document;
#else
Output.Document = Document;
#endif
}
#region Logging
///
/// Enqueue text to the log with formatting
///
/// LogLevel for the log
/// Text to write to the log
public void EnqueueLog(LogLevel logLevel, string text)
{
// Null text gets ignored
if (text == null)
return;
// Enqueue the text
LogQueue.Enqueue(new LogLine(text, logLevel));
}
///
/// Log line wrapper
///
internal readonly struct LogLine
{
public readonly string Text;
public readonly LogLevel LogLevel;
public LogLine(string text, LogLevel logLevel)
{
this.Text = text;
this.LogLevel = logLevel;
}
///
/// Get the foreground Brush for the current LogLevel
///
/// Brush representing the color
public Brush GetForegroundColor()
{
return this.LogLevel switch
{
LogLevel.SECRET => Brushes.Blue,
LogLevel.ERROR => Brushes.Red,
LogLevel.VERBOSE => Brushes.Yellow,
_ => Brushes.White,
};
}
///
/// Generate a Run object from the current LogLine
///
/// Run object based on internal values
public Run GenerateRun()
{
return new Run { Text = this.Text, Foreground = GetForegroundColor() };
}
}
///
/// Process the log lines in the queue
///
/// LogLine item to process
internal void ProcessLogLine(LogLine nextLogLine)
{
// Null text gets ignored
string nextText = nextLogLine.Text;
if (nextText == null)
return;
try
{
if (nextText.StartsWith("\r"))
ReplaceLastLine(nextLogLine);
else
AppendToTextBox(nextLogLine);
}
catch (Exception ex)
{
// In the event that something fails horribly, we want to log
AppendToTextBox(new LogLine(ex.ToString(), LogLevel.ERROR));
}
}
///
/// Append log line to the log text box
///
/// LogLine value to append
private void AppendToTextBox(LogLine logLine)
{
Dispatcher.Invoke(() =>
{
var run = logLine.GenerateRun();
_paragraph.Inlines.Add(run);
lastLine = run;
});
}
///
/// Replace the last line written to the log text box
///
/// LogLine value to append
private void ReplaceLastLine(LogLine logLine)
{
Dispatcher.Invoke(() =>
{
lastLine ??= new Run();
lastLine.Text = logLine.Text;
lastLine.Foreground = logLine.GetForegroundColor();
});
}
#endregion
#region Helpers
///
/// Clear all inlines of the paragraph
///
private void ClearInlines() => _paragraph.Inlines.Clear();
///
/// Save all inlines to console.log
///
private void SaveInlines()
{
using var sw = new StreamWriter(File.OpenWrite("console.log"));
foreach (var inline in _paragraph.Inlines)
{
if (inline is Run run)
sw.Write(run.Text);
}
}
///
/// Scroll the current view to the bottom
///
#if NET35
public void ScrollToBottom() => _OutputViewer!.ScrollToBottom();
#else
public void ScrollToBottom() => OutputViewer.ScrollToBottom();
#endif
#endregion
#region EventHandlers
private void OnClearButton(object sender, EventArgs e)
=> ClearInlines();
private void OnSaveButton(object sender, EventArgs e)
=> SaveInlines();
private void OnTextChanged(object sender, TextChangedEventArgs e)
=> ScrollToBottom();
private void OutputViewerSizeChanged(object sender, SizeChangedEventArgs e)
=> ScrollToBottom();
#endregion
}
}