using Common;
using ImageMagick;
using Microsoft.Win32;
using Ocr.Gui.Model;
using Ocr.Tesseract;
using Ocr.Tesseract.Configuration;
using Ocr.Tesseract.Models;
using Ocr.Tesseract.Screenshots;
using Ocr.Tesseract.Screenshots.Configuration;
using Ocr.Tesseract.Screenshots.Threshold;
using Process.Abstract.Configuration;
using Process.Interface;
using Serilog;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using Ocr.Tesseract.Extensions;

namespace Ocr.Gui.Views;

internal class ImageViewModel : INotifyPropertyChanged
{
    /// <summary>
    /// Tesseract engine configuration
    /// </summary>
    public static readonly ITesseractConfiguration TesseractConfig =
        new TesseractScreenshotConfiguration
        {
            DataPath = "tessdata",
            Languages = new[] { "eng", "deu" }
        };

    public ScreenshotProcessorConfiguration ProcessorConfig { get; } = new();

    /// <summary>
    /// <see cref="Regex"/> expression for extracting whole words from scan results
    /// </summary>
    public static readonly Regex WordRegex = new(
        @"[\w'\-]{2,}",
        RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.IgnoreCase
    );

    public ImageViewModel()
    {
        ProcessorConfig.PropertyChanged += (sender, args) => Task.Run(UpdateImage);

        OpenFileCommand = new Command(OpenFile);
        SaveEditedImageCommand = new Command(SaveEditedImage);
    }

    public ImageViewModel(MagickImage image) : this()
    {
        Image = image;
    }

    private IProcessorChain<MagickImage, ScanResult> MakeProcessor()
    {
        var threshold =
            new ThresholdAdaptiveProcessor(
                ProcessorConfig.ThresholdWidth,
                ProcessorConfig.ThresholdHeight
            );

        var chainConfig = new ProcessorChainConfiguration<MagickImage, MagickImage>()
            .Use(new CloneImageProcessor());

        if (ProcessorConfig.EnableResizing)
        {
            chainConfig
                .Use(new ResizeProcessor(FilterType.Lanczos2Sharp, PixelInterpolateMethod.Mesh))
                .Use(new ProcessingEvent<MagickImage>(OnProcessing));
        }

        chainConfig = chainConfig
            .Use(new NormalizeProcessor())
            .Use(new ProcessingEvent<MagickImage>(OnProcessing));

        if (ProcessorConfig.EnableThresholding)
        {
            chainConfig = chainConfig
                .Use(threshold)
                .Use(new ProcessingEvent<MagickImage>(OnProcessing));
        }

        var preprocessing = chainConfig
            .Use(new AddBorderProcessor(ProcessorConfig.Border))
            .Use(new BinarizeProcessor())
            .Use(new ProcessingEvent<MagickImage>(OnProcessing))
            .Complete(new NegateCloneProcessor());

        var postprocessing = new ProcessorChainConfiguration<ScanResult, ScanResult>()
            .Use(new ConfidenceFilter(50))
            .Use(new ToLowerProcessor())
            .Use(new DuplicateFilter())
            .Complete(new RegexFilter(WordRegex));

        var scan = new TesseractProcessor(TesseractConfig);

        return new ProcessorChainConfiguration<MagickImage, ScanResult>()
            .Use(preprocessing)
            .Use(scan)
            .Use(new ProcessingEvent<ScanResult>(OnProcessed))
            .Complete(postprocessing);
    }

    #region Overrides of Scanner

    /// <inheritdoc />
    protected void OnProcessing(IProcessor sender, ICollection<MagickImage> inputs)
    {
        Application.Current.Dispatcher.Invoke(() =>
        {
            foreach (var image in inputs)
            {
                Edited.Add(image.CloneImage());
            }
        });
    }

    /// <inheritdoc />
    protected void OnProcessed(IProcessor sender, ICollection<ScanResult> inputs)
    {
        var wordStr = string.Join("\", \"", inputs);
        ScannedText = $"{inputs.Count} words:{Environment.NewLine}[ \"{wordStr}\" ]";
    }

    /// <inheritdoc />
    public void Clear()
    {
        Application.Current.Dispatcher.Invoke(() =>
            {
                ScannedText = string.Empty;
                Words.Clear();
                Edited.Clear();
            }
        );
    }

    #endregion

    #region File Handling

    private void OpenFile()
    {
        var dialog = new OpenFileDialog()
        {
            InitialDirectory = Directory.GetCurrentDirectory()
        };
        if (dialog.ShowDialog() == true)
        {
            Image = new MagickImage(dialog.FileName);
            UpdateImage();
        }
    }

    private void SaveEditedImage()
    {
        var basePath = AppDomain.CurrentDomain.BaseDirectory;

        for (var i = 0; i < Edited.Count; i++)
        {
            Edited[i].Write(Path.Combine(basePath, $"edited_{i}.png"));
        }

        Log.Information($"Saved image to '{basePath}'");
        System.Diagnostics.Process.Start(
            "explorer.exe",
            Path.GetDirectoryName(basePath) ?? string.Empty
        );
    }

    #endregion

    #region Updating data

    private void UpdateImage()
    {
        Task.Run(() =>
        {
            IsIdle = false;
            var scanner = new ScreenshotScanner(MakeProcessor());

            Clear();
            
            if (Image != null)
            {
                scanner.Process(new[] { Image });
            }

            Application.Current.Dispatcher.Invoke(() =>
                {
                    var confidence = 0f;
                    foreach (var word in scanner.Lookup.Keys)
                    {
                        confidence += word.Confidence;
                        Words.Add(word);
                    }

                    Confidence = confidence / scanner.Lookup.Keys.Count;
                }
            );

            IsIdle = true;
        });
    }

    #endregion

    #region Properties

    private float _confidence;
    private MagickImage? _image;
    private bool _isIdle;

    private string _scannedText = string.Empty;

    public string ScannedText
    {
        get => _scannedText;
        set
        {
            if (value == _scannedText)
            {
                return;
            }

            _scannedText = value;
            OnPropertyChanged();
        }
    }

    public bool IsIdle
    {
        get => _isIdle;
        set
        {
            if (value == _isIdle)
            {
                return;
            }

            _isIdle = value;
            OnPropertyChanged();
        }
    }

    public float Confidence
    {
        get => _confidence;
        set
        {
            _confidence = value;
            OnPropertyChanged();
        }
    }

    public ObservableCollection<MagickImage> Edited { get; } = new();

    public MagickImage? Image
    {
        get => _image;
        set
        {
            _image = value;
            OnPropertyChanged();
        }
    }

    public ObservableCollection<Word> Words { get; } = new();

    #endregion Properties

    #region Commands

    public ICommand OpenFileCommand { get; private set; }
    public ICommand SaveEditedImageCommand { get; private set; }

    #endregion Commands

    #region INotifyPropertyChanged

    public event PropertyChangedEventHandler? PropertyChanged;

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

    #endregion INotifyPropertyChanged
}