using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using CommunityToolkit.Mvvm.ComponentModel;
using System.IO;
using System.Windows.Media.Imaging;
using Emgu.CV.CvEnum;
using Emgu.CV;
using System.Drawing;
using Image = System.Windows.Controls.Image;
using Rectangle = System.Windows.Shapes.Rectangle;
using Size = System.Drawing.Size;
using Ookii.Dialogs.Wpf;
using Rect = System.Windows.Rect;
using System.ComponentModel;
using Newtonsoft.Json;
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
using System.ComponentModel;
using ClosedXML.Excel;
using Colors = System.Windows.Media.Colors;
using CtrEditor.FuncionesBase;
using System.Drawing;
using System.Windows.Shapes;
using System.Drawing.Imaging;
using Emgu.CV.Structure;

namespace CtrEditor.ObjetosSim.Extraccion_Datos
{
    /// <summary>
    /// Represents a template search control that identifies similar patterns in images and creates tag extraction clones.
    /// This class is designed to work with OCR extraction by finding visual patterns and creating copies of extraction tags
    /// at each matching location.
    /// </summary>
    /// <remarks>
    /// Key functionalities:
    /// - Template matching using OpenCV
    /// - Automatic tag cloning at found locations
    /// - OCR text extraction from matched regions
    /// - Export capabilities to Excel
    /// 
    /// Workflow:
    /// 1. User creates a search template by positioning and sizing the control over a pattern
    /// 2. Links extraction tags to this template using Id_Search_Templates
    /// 3. Activates search_templates to find similar patterns
    /// 4. The system automatically:
    ///    - Searches for visual matches in the image
    ///    - Creates clones of linked extraction tags at each match
    ///    - Assigns incremental copy_Number to organize rows in exports
    ///    - Performs OCR on each cloned tag location
    /// 
    /// Properties:
    /// - search_templates: Triggers the pattern search process
    /// - threshold: Minimum similarity threshold for pattern matching
    /// - coincidencias: Number of matches found (readonly)
    /// - show_debug_ocr: Shows debug windows during OCR process
    /// - export_ocr: Triggers OCR text export for all matches
    /// 
    /// Usage example:
    /// 1. Position the search template over a repeating pattern
    /// 2. Create extraction tags and link them to this template
    /// 3. Set threshold value (default usually works well)
    /// 4. Activate search_templates to find matches and create clones
    /// </remarks>

    public partial class osBuscarCoincidencias : osBase, IosBase
    {
        [JsonIgnore]
        public float offsetY;
        [JsonIgnore]
        public float offsetX;

        [ObservableProperty]
        List<Rect> search_rectangles;

        public static string NombreClase()
        {
            return "Search Templates";
        }
        private string nombre = NombreClase();

        public override string Nombre
        {
            get => nombre;
            set => SetProperty(ref nombre, value);
        }

        [ObservableProperty]
        [property: Category("Tag Extraction:")]
        bool search_templates;

        partial void OnSearch_templatesChanged(bool oldValue, bool newValue)
        {
            if (Search_templates)
                BuscarCoincidencias();
            Search_templates = false;
        }

        [ObservableProperty]
        [property: Category("Tag Extraction:")]
        bool export_ocr;

        [ObservableProperty]
        [property: Category("Tag Extraction:")]
        string text_export_ocr;

        partial void OnExport_ocrChanged(bool value)
        {
            if (Export_ocr)
            {
                Text_export_ocr = "";
                if (!string.IsNullOrEmpty(Nombre) && _mainViewModel != null)
                {
                    foreach (var objetoSimulable in _mainViewModel.ObjetosSimulables)
                    {
                        if (objetoSimulable != this && objetoSimulable.Group_Panel == Nombre)
                        {
                            if (objetoSimulable is osExtraccionTag osExtraccionTag)
                            {
                                osExtraccionTag.CaptureImageAreaAndDoOCR();
                                Text_export_ocr += osExtraccionTag.Tag_extract;
                            }
                        }
                    }
                }
            }
            Export_ocr = false;
        }

        public override void TopChanging(float oldValue, float newValue)
        {
            offsetY = newValue - oldValue;
        }
        public override void LeftChanging(float oldValue, float newValue)
        {
            offsetX = newValue - oldValue;
        }

        [ObservableProperty]
        [property: Category("Tag Extraction:")]
        string tag_extract;

        [ObservableProperty]
        [property: Category("Tag Extraction:")]
        string clase;

        [ObservableProperty]
        [property: Category("Tag Extraction:")]
        string tag_name;

        [ObservableProperty]
        float opacity_oculto;

        [ObservableProperty]
        [property: Category("Tag Extraction:")]
        bool show_debug_ocr;

        [ObservableProperty]
        [property: Category("Tag Extraction:")]
        float threshold;

        [ObservableProperty]
        [property: Category("Tag Extraction:")]
        [property: ReadOnly(true)]
        int coincidencias;

        public osBuscarCoincidencias()
        {
            Ancho = 1;
            Alto = 1;
            Angulo = 0;
            Opacity_oculto = 0.1f;
            Threshold = 0.6f;
        }

        private void ShowPreviewWindow(Stream imageStream)
        {
            // Create a new window for preview
            Window previewWindow = new Window
            {
                Title = "Preview Captured Image",
                Width = 500,
                Height = 500,
                Content = new Image
                {
                    Source = BitmapFrame.Create(imageStream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad),
                    Stretch = Stretch.Uniform
                }
            };

            previewWindow.ShowDialog();
        }

        public async void BuscarCoincidencias()
        {
            var progressDialog = new ProgressDialog
            {
                WindowTitle = "Procesando",
                Text = "Buscando coincidencias...",
                ShowTimeRemaining = true,
                ShowCancelButton = false
            };
            progressDialog.DoWork += (sender, e) => BuscarCoincidenciasAsync(progressDialog);
            progressDialog.RunWorkerCompleted += (sender, e) =>
            {
                if (e.Error != null)
                {
                    MessageBox.Show(e.Error.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
                }
            };

            progressDialog.Show();
        }

        private void BuscarCoincidenciasAsync(ProgressDialog progressDialog)
        {
            // Reset the Canvas children
            Application.Current.Dispatcher.Invoke(() =>
            {
                var clearShapes = _mainViewModel.MainCanvas.Children.OfType<System.Windows.Shapes.Shape>().Where(s => s.Tag as string == "BuscarCoincidencias").ToList();
                foreach (var shape in clearShapes)
                {
                    _mainViewModel.MainCanvas.Children.Remove(shape);
                }
                if (_mainViewModel?.MainCanvas.Children[0] is Image imagenDeFondo)
                {
                    // Asegurarse de que la imagen origen está disponible
                    if (imagenDeFondo.Source is BitmapSource bitmapSource)
                    {
                        progressDialog.ReportProgress(10);
                        // Obtener los DPI de la imagen original
                        float originalDpiX = (float)bitmapSource.DpiX;
                        float originalDpiY = (float)bitmapSource.DpiY;

                        // Estándar DPI en el que el Canvas renderiza la imagen
                        float canvasDpiX = 96; // WPF usually renders at 96 DPI
                        float canvasDpiY = 96;

                        // Calcular el ratio de escala entre el Canvas y la imagen original
                        float scaleFactorX = originalDpiX / canvasDpiX;
                        float scaleFactorY = originalDpiY / canvasDpiY;

                        // Ajustar las coordenadas de recorte en función del ratio de escala
                        int x = (int)MeterToPixels(Left * scaleFactorX);
                        int y = (int)MeterToPixels(Top * scaleFactorY);
                        int width = (int)MeterToPixels(Ancho * scaleFactorX);
                        int height = (int)MeterToPixels(Alto * scaleFactorY);

                        // Validar y ajustar el tamaño del recorte para que se mantenga dentro de los límites de la imagen
                        if (x < 0) x = 0;
                        if (y < 0) y = 0;
                        if (x + width > bitmapSource.PixelWidth) width = bitmapSource.PixelWidth - x;
                        if (y + height > bitmapSource.PixelHeight) height = bitmapSource.PixelHeight - y;

                        // Recortar el área deseada utilizando las coordenadas ajustadas
                        CroppedBitmap croppedBitmap = new CroppedBitmap(bitmapSource, new Int32Rect(x, y, width, height));

                        // Convertir CroppedBitmap a Mat directamente
                        Mat templateMat = BitmapSourceToMat(croppedBitmap);

                        int scale = 4;

                        // Convertir la plantilla a escala de grises y redimensionarla
                        Mat templateGray = new Mat();
                        CvInvoke.CvtColor(templateMat, templateGray, ColorConversion.Bgr2Gray);
                        Mat templateGrayResized = new Mat();
                        CvInvoke.Resize(templateGray, templateGrayResized, new Size(templateGray.Width / scale, templateGray.Height / scale), 0, 0, Inter.Linear);
                        progressDialog.ReportProgress(20);
                        // Cargar la imagen principal completa en un Mat
                        Mat mainImageMat = BitmapSourceToMat(bitmapSource);

                        // Convertir la imagen principal a escala de grises y redimensionarla
                        Mat mainImageGray = new Mat();
                        CvInvoke.CvtColor(mainImageMat, mainImageGray, ColorConversion.Bgr2Gray);
                        Mat mainImageGrayResized = new Mat();
                        CvInvoke.Resize(mainImageGray, mainImageGrayResized, new Size(mainImageGray.Width / scale, mainImageGray.Height / scale), 0, 0, Inter.Linear);

                        progressDialog.ReportProgress(50);

                        // Realizar la coincidencia de plantillas
                        Mat result = new Mat();
                        CvInvoke.MatchTemplate(mainImageGray, templateGray, result, TemplateMatchingType.CcoeffNormed);

                        // Establecer un umbral de coincidencia
                        if (Threshold < 0.4)
                            Threshold = 0.4f;
                        double threshold = Threshold;

                        int ConteoPositivos = 0;

                        // Lista para mantener áreas ya aceptadas
                        if (search_rectangles != null)
                            search_rectangles.Clear();
                        else
                            search_rectangles = new List<Rect>();

                        // Añadir el rectángulo usado por croppedBitmap a search_rectangles
                        //search_rectangles.Add(new Rect(x / scaleFactorX, y / scaleFactorY, width / scaleFactorX, height / scaleFactorY));

                        // Obtener los puntos que superan el umbral
                        float[] resultArray = result.GetData(false) as float[];
                        if (resultArray != null)
                        {
                            for (int i = 0; i < resultArray.Length; i++)
                            {
                                if (resultArray[i] >= threshold)
                                {
                                    int row = i / result.Cols;
                                    int col = i % result.Cols;

                                    Rect newRect = new Rect();
                                    newRect.X = col / scaleFactorX;
                                    newRect.Y = row / scaleFactorY;
                                    newRect.Width = width / scaleFactorX;
                                    newRect.Height = height / scaleFactorY;

                                    // Crear un rectángulo para la coincidencia actual
                                    Rectangle matchRect = new Rectangle
                                    {
                                        Stroke = new SolidColorBrush(Colors.Red),
                                        StrokeThickness = 2,
                                        Width = newRect.Width,
                                        Height = newRect.Height,
                                        Tag = "BuscarCoincidencias"
                                    };

                                    Canvas.SetLeft(matchRect, newRect.X);
                                    Canvas.SetTop(matchRect, newRect.Y);

                                    // Verificar si la coincidencia actual está dentro de algún rectángulo aceptado
                                    bool isOverlap = search_rectangles.Any(r =>
                                        r.IntersectsWith(newRect)
                                    );

                                    // Si no hay superposición, agregar el rectángulo al Canvas y a la lista de aceptados
                                    if (!isOverlap)
                                    {
                                        Canvas.SetZIndex(matchRect, 40);
                                        _mainViewModel.MainCanvas.Children.Add(matchRect);
                                        search_rectangles.Add(newRect);

                                        ConteoPositivos++;
                                        Coincidencias = ConteoPositivos;
                                        progressDialog.ReportProgress(90);
                                        if (ConteoPositivos > 20)
                                            return;
                                    }
                                }
                            }
                            PopularTagExtraction();
                        }
                    }
                }
            });
        }


        public void PopularTagExtraction()
        {
            var objetosSimulablesCopy = new List<osBase>(_mainViewModel.ObjetosSimulables);
            foreach (var obj in objetosSimulablesCopy)
                if (obj is osExtraccionTag objExtraccionTag)
                    if (objExtraccionTag.Id_Search_Templates == this.Nombre && objExtraccionTag.Cloned)
                        _mainViewModel.RemoverObjetoSimulable(objExtraccionTag);

            var objetosSimulables2Copy = new List<osBase>(_mainViewModel.ObjetosSimulables);
            // Saltar el primer rectángulo en el foreach

            int Row = 0;
            foreach (var rectangle in search_rectangles) //.Skip(1))
            {
                float offsetX = PixelsToMeters((float)rectangle.X) - Left;
                float offsetY = PixelsToMeters((float)rectangle.Y) - Top;

                osExtraccionTag newObj = null;

                foreach (var eTag in objetosSimulables2Copy)
                {
                    if (eTag is osExtraccionTag objExtraccionTag)
                    {
                        if (objExtraccionTag.Id_Search_Templates == this.Nombre)
                        {
                            newObj = (osExtraccionTag)_mainViewModel.DuplicarObjeto(objExtraccionTag, offsetX, offsetY);
                            if (newObj != null)
                            {
                                newObj.Cloned = true;
                                newObj.Cloned_from = objExtraccionTag.Id;
                                newObj.Copy_Number = Row;
                                newObj.Enable_On_All_Pages = false;
                                if (newObj.Extraer)
                                    objExtraccionTag.CaptureImageAreaAndDoOCR();
                            }
                        }
                    }
                }
                Row++;
            }
        }

        public static int FindFirstEmptyRow(IXLWorksheet worksheet)
        {
            var lastRowUsed = worksheet.LastRowUsed();
            return lastRowUsed == null ? 1 : lastRowUsed.RowNumber() + 1;
        }

        // Método para convertir BitmapSource a Mat
        private Mat BitmapSourceToMat(BitmapSource bitmapSource)
        {
            if (bitmapSource == null)
                throw new ArgumentNullException(nameof(bitmapSource));

            // Convierte BitmapSource a Bitmap
            Bitmap bitmap;
            using (MemoryStream outStream = new MemoryStream())
            {
                BitmapEncoder enc = new BmpBitmapEncoder();
                enc.Frames.Add(BitmapFrame.Create(bitmapSource));
                enc.Save(outStream);
                bitmap = new Bitmap(outStream);
            }

            // Convierte directamente a Mat usando Image<Bgr, byte>
            Image<Bgr, byte> image = bitmap.ToImage<Bgr, byte>();
            return image.Mat;
        }

        public override void ucLoaded()
        {
            // El UserControl ya se ha cargado y podemos obtener las coordenadas para 
            // crear el objeto de simulacion
            base.ucLoaded();

        }

    }

    public partial class ucBuscarCoincidencias : UserControl, IDataContainer
    {
        public osBase? Datos { get; set; }
        public int zIndex_fromFrames { get; set; }

        public ucBuscarCoincidencias()
        {
            InitializeComponent();
            this.Loaded += OnLoaded;
            this.Unloaded += OnUnloaded;
        }
        private void OnLoaded(object sender, RoutedEventArgs e)
        {
            Datos?.ucLoaded();
        }
        private void OnUnloaded(object sender, RoutedEventArgs e)
        {
            Datos?.ucUnLoaded();
        }
        public void Highlight(bool State) { }
        public ZIndexEnum ZIndex_Base()
        {
            return ZIndexEnum.Estaticos;
        }

    }
}