using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using com.sun.org.glassfish.gmbal;
using FastReport.DataVisualization.Charting;
using Microsoft.Win32;
using NPOI.XWPF.Usermodel;

namespace InABox.Wpf.Console;

public static class ItemsControlExtensions
{
    public static void ScrollIntoView(
        this ItemsControl control,
        object item)
    {
        var framework =
            control.ItemContainerGenerator.ContainerFromItem(item)
                as FrameworkElement;
        if (framework == null) return;
        framework.BringIntoView();
    }

    public static void ScrollIntoView(this ItemsControl control)
    {
        var count = control.Items.Count;
        if (count == 0) return;
        var item = control.Items[count - 1];
        control.ScrollIntoView(item);
    }
}


/// <summary>
///     Interaction logic for Console.xaml
/// </summary>
public partial class ConsoleControl : UserControl, INotifyPropertyChanged
{
    public static readonly DependencyProperty EnabledProperty
        = DependencyProperty.Register(nameof(Enabled), typeof(bool), typeof(ConsoleControl));

    private CollectionViewSource _filtered;
    public CollectionViewSource Filtered
    {
        get => _filtered;
        set
        {
            _filtered = value;
            OnPropertyChanged();
        }
    }

    public readonly ObservableCollection<LogEntry> LogEntries;

    private readonly TimeSpan regexTimeOut = TimeSpan.FromMilliseconds(100);

    private Regex? searchRegex;

    public event PropertyChangedEventHandler? PropertyChanged;

    public event Action? OnLoadLog;
    public event Action? OnCloseLog;

    public bool _allowLoadLogButton = true;
    public bool AllowLoadLogButton
    {
        get => _allowLoadLogButton;
        set
        {
            _allowLoadLogButton = value;
            OnPropertyChanged();
            OnPropertyChanged(nameof(ShowLoadLogButton));
            OnPropertyChanged(nameof(ShowCloseLogButton));
        }
    }

    private bool _loadedLog = false;
    public bool LoadedLog
    {
        get => _loadedLog;
        set
        {
            _loadedLog = value;
            OnPropertyChanged();
            OnPropertyChanged(nameof(ShowLoadLogButton));
            OnPropertyChanged(nameof(ShowCloseLogButton));
        }
    }

    public bool ShowLoadLogButton => !LoadedLog && AllowLoadLogButton;
    public bool ShowCloseLogButton => LoadedLog && AllowLoadLogButton;

    public bool Enabled
    {
        get => (bool)GetValue(EnabledProperty);
        set => SetValue(EnabledProperty, value);
    }

    public ConsoleControl()
    {
        InitializeComponent();

        Filtered = new CollectionViewSource();
        LogEntries = new ObservableCollection<LogEntry>();
        Filtered.Source = LogEntries;
        Filtered.Filter += (sender, args) =>
        {
            var logEntry = (LogEntry)args.Item;
            if (ShowImportant.IsChecked == true && !IsImportant(logEntry))
            {
                args.Accepted = false;
                return;
            }

            if (UseRegEx.IsChecked == true && searchRegex != null)
                args.Accepted = string.IsNullOrWhiteSpace(Search.Text)
                                || searchRegex.IsMatch(logEntry.DateTime)
                                || searchRegex.IsMatch(logEntry.Type)
                                || searchRegex.IsMatch(logEntry.User)
                                || searchRegex.IsMatch(logEntry.Message);
            else
                args.Accepted = string.IsNullOrWhiteSpace(Search.Text)
                                || logEntry.DateTime.Contains(Search.Text)
                                || logEntry.Type.Contains(Search.Text)
                                || logEntry.User.Contains(Search.Text)
                                || logEntry.Message.Contains(Search.Text);
        };
    }

    protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    private static bool IsImportant(LogEntry entry)
    {
        return entry.Type == "IMPTNT";
    }

    // DATE  TRANSACTION  TYPE  USERID  MESSAGE
    [GeneratedRegex("(\\d\\d:\\d\\d:\\d\\d\\.\\d\\d\\d)\\s*([0-9a-zA-Z]{8}(?:\\-[0-9a-zA-Z]{4}){3}\\-[0-9a-zA-Z]{12})?\\s*(\\S+) (.+)", RegexOptions.Singleline)]
    internal static partial Regex LogEntryParseRegex();

    public static LogEntry? ParseLogMessage(string logLine)
    {
        var match = LogEntryParseRegex().Match(logLine);

        if (match.Success)
        {
            var date = match.Groups[1].Value;

            if (!Guid.TryParse(match.Groups[2].Value, out var transaction))
            {
                transaction = Guid.Empty;
            }
            var type = match.Groups[3].Value;
            var remaining = match.Groups[4].Value;

            var user = remaining.Length >= 12 ? remaining[..12] : "";
            var msg = remaining.Length >= 12 ? remaining[13..] : "";
            return new LogEntry
            {
                DateTime = date,
                Type = type,
                User = user,
                Message = msg,
                Transaction = transaction
            };
        }
        else
        {
            return null;
        }
    }

    public void LoadLogEntries(IEnumerable<string> lines)
    {
        var logEntries = new List<LogEntry>();

        var numberSkipped = 0;

        foreach (var line in lines)
        {
            var logEntry = ParseLogMessage(line ?? "");
            if(logEntry is not null)
            {
                var logType = logEntry.Type;
                if (logType == "ERROR" || logType == "INFO" || logType == "IMPTNT")
                    logEntries.Add(logEntry);
                else if (string.IsNullOrWhiteSpace(logType)) numberSkipped++;
            }
        }

        if (numberSkipped > 0)
        {
            if (logEntries.Count == 0)
                SetErrorMessage("File does not contain valid log information!");
            else
                SetErrorMessage(string.Format("Skipped {0} lines that did not contain valid log information", numberSkipped));
        }

        Filtered.Source = logEntries;
    }
    public void LoadLogEntry(string line)
    {
        var logEntry = ParseLogMessage(line);
        if(logEntry is not null)
        {
            var logType = logEntry.Type;
            if (logType == "INFO" || logType == "ERROR" || logType == "IMPTNT")
            {
                LogEntries.Insert(0, logEntry);
            }
        }
    }


    private void Search_KeyDown(object sender, KeyEventArgs e)
    {
        if (e.Key == Key.Enter && UseRegEx.IsChecked == true)
        {
            try
            {
                searchRegex = new Regex(Search.Text, RegexOptions.Compiled, regexTimeOut);
            }
            catch (ArgumentException)
            {
                searchRegex = null;
            }

            Filtered.View.Refresh();
            SetSearchStyleNormal();
        }
    }

    private void SetSearchStyleChanged()
    {
        Search.Background = Brushes.White;
    }

    private void SetSearchStyleNormal()
    {
        Search.Background = Brushes.LightYellow;
    }

    private void Search_TextChanged(object sender, TextChangedEventArgs e)
    {
        if (UseRegEx.IsChecked != true)
        {
            Filtered.View.Refresh();
        }
        else
        {
            if (string.IsNullOrWhiteSpace(Search.Text))
            {
                searchRegex = null;
                Filtered.View.Refresh();
                SetSearchStyleNormal();
            }
            else
            {
                SetSearchStyleChanged();
            }
        }
    }

    private void UseRegEx_Checked(object sender, RoutedEventArgs e)
    {
        try
        {
            searchRegex = new Regex(Search.Text, RegexOptions.Compiled, regexTimeOut);
        }
        catch (ArgumentException ex)
        {
            searchRegex = null;
        }

        Filtered.View.Refresh();
    }

    private void UseRegEx_Unchecked(object sender, RoutedEventArgs e)
    {
        searchRegex = null;
        Filtered.View.Refresh();

        SetSearchStyleNormal();
    }

    public void SetErrorMessage(string? error)
    {
        if (string.IsNullOrWhiteSpace(error))
        {
            Error.Content = "";
            Error.Visibility = Visibility.Collapsed;
        }
        else
        {
            Error.Content = error;
            Error.Visibility = Visibility.Visible;
        }
    }

    private void CloseLog_Click(object sender, RoutedEventArgs e)
    {
        Filtered.Source = LogEntries;

        OnCloseLog?.Invoke();
    }

    private void ShowImportant_Checked(object sender, RoutedEventArgs e)
    {
        Filtered.View.Refresh();
    }

    private void ShowImportant_Unchecked(object sender, RoutedEventArgs e)
    {
        Filtered.View.Refresh();
    }

    private void LoadLog_Click(object sender, RoutedEventArgs e)
    {
        OnLoadLog?.Invoke();
    }
}

public abstract class Console : Window
{
    public ConsoleControl ConsoleControl { get; set; }

    private readonly string Description;

    public Console(string description)
    {
        ConsoleControl = new ConsoleControl();
        ConsoleControl.OnLoadLog += ConsoleControl_OnLoadLog;
        ConsoleControl.OnCloseLog += ConsoleControl_OnCloseLog;
        Content = ConsoleControl;

        Height = 800;
        Width = 1200;

        Loaded += Console_Loaded;
        Closing += Console_Closing;

        Title = description;
        Description = description;
    }

    private void ConsoleControl_OnCloseLog()
    {
        Title = Description;
        ConsoleControl.LoadedLog = false;
    }

    private void ConsoleControl_OnLoadLog()
    {
        var dialog = new OpenFileDialog
        {
            InitialDirectory = GetLogDirectory()
        };
        if (dialog.ShowDialog() == true)
        {
            var lines = File.ReadLines(dialog.FileName);
            ConsoleControl.LoadLogEntries(lines);
            ConsoleControl.LoadedLog = true;

            Title = dialog.FileName;
        }
    }

    protected virtual void OnLoaded()
    {
    }
    protected virtual void OnClosing()
    {
    }

    private void Console_Closing(object? sender, CancelEventArgs e)
    {
        OnClosing();
    }

    private void Console_Loaded(object sender, RoutedEventArgs e)
    {
        OnLoaded();
    }

    protected abstract string GetLogDirectory();
}