using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Windows.Forms;
using PolyTraderSharp.Services;
namespace PolyTraderSharp.Ui.Views
{
///
/// Terminal-/Log-Ansicht. Designbar (siehe TerminalView.Designer.cs). Die Laufzeit-
/// Abhängigkeit (TerminalLogger) wird per injiziert, damit der
/// parameterlose Konstruktor für den VS-Designer nutzbar bleibt.
///
public partial class TerminalView : Form
{
private TerminalLogger? _logger;
private readonly ConcurrentQueue _logQueue = new();
private readonly System.Windows.Forms.Timer _uiLogTimer = new() { Interval = 250 };
private bool _autoScroll = true;
private EventHandler? _logHandler;
public TerminalView()
{
InitializeComponent();
// Steuerelemente + statische Eigenschaften stammen aus dem Designer.
// Hier nur noch Verhalten verdrahten und einen sinnvollen Default wählen.
if (cbLogLevel.Items.Count > 0) cbLogLevel.SelectedIndex = 0;
btnAutoscroll.Click += (_, _) => ToggleAutoscroll();
_uiLogTimer.Tick += ProcessLogQueue;
// Kontextmenü: Text aus dem Terminal kopieren (Ctrl+C funktioniert zusätzlich nativ).
miCopy.Click += (_, _) => { if (rtbTerminal.SelectionLength > 0) rtbTerminal.Copy(); };
miSelectAll.Click += (_, _) => rtbTerminal.SelectAll();
miCopyAll.Click += (_, _) => CopyAllToClipboard();
miClear.Click += (_, _) => rtbTerminal.Clear();
// Log Viewer (S-0): JSONL-Tagesdateien menschenlesbar, filterbar nach Level/CID/Text.
if (cbViewerLevel.Items.Count > 0) cbViewerLevel.SelectedIndex = 0;
btnViewerLoad.Click += (_, _) => LoadViewer();
tbViewerCid.KeyDown += (_, e) => { if (e.KeyCode == Keys.Enter) { LoadViewer(); e.SuppressKeyPress = true; } };
tbViewerText.KeyDown += (_, e) => { if (e.KeyCode == Keys.Enter) { LoadViewer(); e.SuppressKeyPress = true; } };
// Doppelklick auf eine CID: komplette Signal-Kette anzeigen.
dgvLogs.CellDoubleClick += (_, e) =>
{
if (e.RowIndex < 0) return;
if (dgvLogs.Rows[e.RowIndex].DataBoundItem is LogViewerRow row && !string.IsNullOrEmpty(row.Cid))
{
tbViewerCid.Text = row.Cid;
tbViewerText.Text = "";
LoadViewer();
}
};
}
// ===== Log Viewer =====
/// Anzeige-Zeile des Log Viewers (Bindung über DataPropertyName).
private sealed class LogViewerRow
{
public string Time { get; init; } = "";
public string Level { get; init; } = "";
public string Cid { get; init; } = "";
public string Message { get; init; } = "";
}
private void LoadViewer()
{
try
{
string path = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs",
$"{dtViewerDate.Value:yyyy-MM-dd}.jsonl");
if (!System.IO.File.Exists(path))
{
dgvLogs.DataSource = new System.ComponentModel.BindingList();
lblViewerStatus.Text = "Keine JSONL-Datei für dieses Datum.";
return;
}
string levelFilter = cbViewerLevel.SelectedItem?.ToString() ?? "Alle";
string cidFilter = tbViewerCid.Text.Trim();
string textFilter = tbViewerText.Text.Trim();
var rows = new List();
foreach (string line in System.IO.File.ReadLines(path))
{
var p = LogJson.ParseLine(line);
if (p == null) continue;
if (levelFilter != "Alle" && p.Level != levelFilter) continue;
if (cidFilter.Length > 0 && !p.Cid.Contains(cidFilter, StringComparison.OrdinalIgnoreCase)) continue;
if (textFilter.Length > 0 && !p.Message.Contains(textFilter, StringComparison.OrdinalIgnoreCase)) continue;
rows.Add(new LogViewerRow { Time = p.Time, Level = p.Level, Cid = p.Cid, Message = p.Message });
if (rows.Count >= 20000) break; // UI-Schutz bei sehr großen Tagen
}
dgvLogs.DataSource = new System.ComponentModel.BindingList(rows);
lblViewerStatus.Text = $"{rows.Count} Einträge.";
}
catch (Exception ex)
{
lblViewerStatus.Text = $"Fehler: {ex.Message}";
}
}
private void CopyAllToClipboard()
{
try
{
if (!string.IsNullOrEmpty(rtbTerminal.Text))
Clipboard.SetText(rtbTerminal.Text);
}
catch { /* Zwischenablage kann kurzzeitig belegt sein – bewusst ignorieren */ }
}
/// Verbindet die View mit dem Logger (Laufzeit-DI).
public void Initialize(TerminalLogger logger)
{
_logger = logger;
// Jüngste Historie vorladen, damit das Fenster beim Öffnen nicht leer ist.
foreach (var e in _logger.GetHistory(TimeSpan.FromMinutes(10)))
_logQueue.Enqueue(e);
_logHandler = (_, e) => _logQueue.Enqueue(e);
_logger.OnLogMessage += _logHandler;
_uiLogTimer.Start();
Disposed += OnDisposed;
}
private void OnDisposed(object? sender, EventArgs e)
{
_uiLogTimer.Stop();
if (_logger != null && _logHandler != null)
_logger.OnLogMessage -= _logHandler;
}
private void ToggleAutoscroll()
{
_autoScroll = !_autoScroll;
btnAutoscroll.BackColor = _autoScroll ? System.Drawing.Color.LightGreen : System.Drawing.Color.IndianRed;
btnAutoscroll.Text = _autoScroll ? "Stop Autoscroll" : "Start Autoscroll";
}
private void ProcessLogQueue(object? sender, EventArgs e)
{
if (_logQueue.IsEmpty || !IsHandleCreated) return;
string filter = cbLogLevel.SelectedItem?.ToString() ?? "Alle";
TimeZoneInfo berlinTz = TimeZoneInfo.FindSystemTimeZoneById("W. Europe Standard Time");
bool appended = false;
int count = 0;
const int maxProcess = 500;
rtbTerminal.SuspendLayout();
while (count < maxProcess && _logQueue.TryDequeue(out var logEvent))
{
count++;
if (filter != "Alle" && logEvent.Level.ToString() != filter) continue;
DateTime logTime = logEvent.Timestamp.Kind == DateTimeKind.Utc
? TimeZoneInfo.ConvertTimeFromUtc(logEvent.Timestamp, berlinTz)
: TimeZoneInfo.ConvertTime(logEvent.Timestamp, berlinTz);
string timeStr = $"[{logTime:HH:mm:ss}]";
System.Drawing.Color c = System.Drawing.Color.White;
if (logEvent.Level == LogLevel.Error) c = System.Drawing.Color.Red;
else if (logEvent.Level == LogLevel.Warning) c = System.Drawing.Color.Yellow;
else if (logEvent.Level == LogLevel.Trade) c = System.Drawing.Color.LightGreen;
else if (logEvent.Level == LogLevel.TradeReasoning) c = System.Drawing.Color.Orange;
rtbTerminal.SelectionStart = rtbTerminal.TextLength;
rtbTerminal.SelectionLength = 0;
rtbTerminal.SelectionColor = c;
rtbTerminal.AppendText($"{timeStr} [{logEvent.Level}] {logEvent.Message}\n");
appended = true;
}
if (appended)
{
if (rtbTerminal.TextLength > 80000)
{
rtbTerminal.Clear();
rtbTerminal.SelectionColor = System.Drawing.Color.LightPink;
rtbTerminal.AppendText($"[{DateTime.Now:HH:mm:ss}] [System] Terminal Auto-Clear (RAM Limit erreicht). Vollständige Logs im Ordner /Logs.\n");
}
if (_autoScroll) rtbTerminal.ScrollToCaret();
}
rtbTerminal.ResumeLayout();
}
}
}