using System;
using System.Drawing;
using System.IO;
using System.Windows.Forms;
using OpenCvSharp;
using OpenCvSharp.Extensions;

namespace OpenCVStudio_v0
{
    public partial class MainForm : Form
    {
        private readonly ProjectManager projectManager;
        private readonly PipelineExecutor pipelineExecutor;

        private BlockLibraryController? blockLibraryController;
        private ProjectBlock? selectedProjectBlock;

        private AboutForm? aboutForm;
        public MainForm()
        {
            InitializeComponent();

            projectManager = new ProjectManager();
            pipelineExecutor = new PipelineExecutor(projectManager);

            ConfigurePreviewControls();
            SubscribeEvents();
            CreateBlockLibraryController();
            UpdateProjectInterface();
        }

        /// <summary>
        /// Настраивает элементы предпросмотра изображений.
        /// </summary>
        private void ConfigurePreviewControls()
        {
            pictureBoxPreviewL.SizeMode = PictureBoxSizeMode.Zoom;
            pictureBoxPreviewR.SizeMode = PictureBoxSizeMode.Zoom;

            Color previewBackground = Color.FromArgb(40, 40, 40);

            pictureBoxPreviewL.BackColor = previewBackground;
            pictureBoxPreviewR.BackColor = previewBackground;
        }

        /// <summary>
        /// Подписывает главную форму на события.
        /// </summary>
        private void SubscribeEvents()
        {
            projectManager.ProjectChanged += ProjectManager_ProjectChanged;
            propertyGridBlock.PropertyValueChanged += PropertyGridBlock_PropertyValueChanged;
        }

        /// <summary>
        /// Создаёт контроллер библиотеки блоков
        /// и подписывается на его события.
        /// </summary>
        private void CreateBlockLibraryController()
        {
            blockLibraryController = new BlockLibraryController(treeViewBlocks, panelPipeline, projectManager, propertyGridBlock);

            blockLibraryController.BlockAdded += BlockLibraryController_BlockAdded;
            blockLibraryController.BlockSelected += BlockLibraryController_BlockSelected;
            blockLibraryController.PreviewRequested += BlockLibraryController_PreviewRequested;
        }

        private void btnNewProject_Click(object sender, EventArgs e)
        {
            try
            {
                using FolderBrowserDialog dialog = new FolderBrowserDialog
                {
                    Description = "Выберите папку для хранения нового проекта",
                    UseDescriptionForTitle = true,
                    ShowNewFolderButton = true
                };
                if (dialog.ShowDialog() != DialogResult.OK)
                {
                    return;
                }
                string defaultName = "NewProject_" + DateTime.Now.ToString("yyyyMMdd_HHmmss");
                string projectName = Microsoft.VisualBasic.Interaction.InputBox("Введите имя нового проекта:", "Создание проекта", defaultName);
                if (string.IsNullOrWhiteSpace(projectName))
                {
                    return;
                }
                ClearSelectedBlock();
                projectManager.CreateNewProject(dialog.SelectedPath, projectName.Trim());
                blockLibraryController?.ReloadPipeline();
                ShowProjectImage();
                MessageBox.Show($"Проект «{projectName.Trim()}» создан.", "Новый проект", MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
            catch (Exception ex)
            {
                ShowError(ex, "Ошибка создания проекта");
            }
        }

        private void btnOpenProject_Click(object sender, EventArgs e)
        {
            try
            {
                using OpenFileDialog dialog = new OpenFileDialog
                {
                    Title = "Открыть проект OpenCV Studio",
                    Filter = "Проект OpenCV Studio (*.xml)|*.xml",
                    FileName = "project.xml",
                    CheckFileExists = true,
                    Multiselect = false
                };

                if (dialog.ShowDialog() != DialogResult.OK)
                    return;

                ClearSelectedBlock();

                projectManager.OpenProject(dialog.FileName);

                blockLibraryController?.ReloadPipeline();

                ShowProjectImage();
            }
            catch (Exception ex)
            {
                ShowError(ex, "Ошибка открытия проекта");
            }
        }

        private void btnSaveProject_Click(object sender, EventArgs e)
        {
            try
            {
                projectManager.SaveProject();

                MessageBox.Show("Проект сохранён.", "Сохранение", MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
            catch (Exception ex)
            {
                ShowError(ex, "Ошибка сохранения проекта");
            }
        }



        private void btnRun_Click(object sender, EventArgs e)
        {
            try
            {
                using PipelineExecutionResult result = pipelineExecutor.ExecuteAll();
                ShowPipelineInfo(result);
                ShowProcessedImage(result.Image);


                /*
                 * Полный запуск показывает настоящий
                 * результат pipeline без автоматической
                 * отрисовки данных контуров.
                 *
                 * Для получения контуров в итоговом изображении
                 * пользователь должен добавить DrawContours.
                 */
                ShowProcessedImage(result.Image);
            }
            catch (Exception ex)
            {
                ShowError(ex, "Ошибка выполнения pipeline");
            }
        }

        private void ProjectManager_ProjectChanged(object? sender, EventArgs e)
        {
            UpdateProjectInterface();
            /*
             * Обновляем изображения источников
             * после выбора нового файла.
             */
            ShowProjectImage();
        }

        private void UpdateProjectInterface()
        {
            string projectName = projectManager.CurrentProject?.ProjectName ?? "Проект не открыт";
            string modifiedMark = projectManager.IsModified ? " *" : string.Empty;

            Text = $"OpenCV Studio — {projectName}{modifiedMark}";

            bool hasProject = projectManager.HasProject;

            btnSaveProject.Enabled = hasProject;
            btnRun.Enabled = hasProject;
        }

        /// <summary>
        /// Сбрасывает выбранный блок и очищает PropertyGrid.
        /// </summary>
        private void ClearSelectedBlock()
        {
            selectedProjectBlock = null;
            propertyGridBlock.SelectedObject = null;
        }

        /// <summary>
        /// Показывает изображения блоков-источников.
        ///
        /// Слева — ImageSource.
        /// Справа — SecondImageSource, если он существует.
        /// Если второго источника нет, справа показывается
        /// копия основного изображения.
        /// </summary>
        private void ShowProjectImage()
        {
            string? mainImagePath = GetMainSourceImagePath();
            string? secondImagePath = GetSecondSourceImagePath();
            Bitmap? mainBitmap = LoadBitmapCopy(mainImagePath);
            Bitmap? rightBitmap;
            if (!string.IsNullOrWhiteSpace(secondImagePath))
            {
                rightBitmap = LoadBitmapCopy(secondImagePath);
            }
            else if (mainBitmap != null)
            {
                rightBitmap = new Bitmap(mainBitmap);
            }
            else
            {
                rightBitmap = null;
            }
            ReplacePictureBoxImage(pictureBoxPreviewL, mainBitmap);
            ReplacePictureBoxImage(pictureBoxPreviewR, rightBitmap);
        }

        /// <summary>
        /// Заменяет изображение PictureBox и освобождает старое.
        /// После вызова PictureBox владеет newImage.
        /// </summary>
        private static void ReplacePictureBoxImage(PictureBox pictureBox, Image? newImage)
        {
            ArgumentNullException.ThrowIfNull(pictureBox);

            Image? oldImage = pictureBox.Image;

            pictureBox.Image = newImage;

            oldImage?.Dispose();
        }

        /// <summary>
        /// Показывает Mat без дополнительной отрисовки.
        /// Исходный Mat не изменяется и не освобождается.
        /// </summary>
        private void ShowProcessedImage(Mat image)
        {
            ArgumentNullException.ThrowIfNull(image);

            if (image.Empty())
            {
                ReplacePictureBoxImage(pictureBoxPreviewR, null);
                return;
            }

            Bitmap bitmap = BitmapConverter.ToBitmap(image);

            ReplacePictureBoxImage(pictureBoxPreviewR, bitmap);
        }

        /// <summary>
        /// Создаёт цветную копию Mat,
        /// рисует на ней контуры
        /// и показывает результат.
        ///
        /// Исходный Mat и ContourData не изменяются.
        /// </summary>
        private void ShowProcessedImage(Mat image, ContourData contours)
        {
            ArgumentNullException.ThrowIfNull(image);
            ArgumentNullException.ThrowIfNull(contours);

            if (image.Empty())
            {
                ReplacePictureBoxImage(pictureBoxPreviewR, null);
                return;
            }

            using Mat previewImage = CreateContourPreviewImage(image);

            DrawContoursOnPreview(previewImage, contours);

            Bitmap bitmap = BitmapConverter.ToBitmap(previewImage);

            ReplacePictureBoxImage(pictureBoxPreviewR, bitmap);
        }

        private void BlockLibraryController_BlockAdded(object? sender, ProjectBlockEventArgs e)
        {
            UpdateProjectInterface();
        }

        private void BlockLibraryController_BlockSelected(object? sender, ProjectBlockEventArgs e)
        {
            selectedProjectBlock = e.Block;

            ShowSelectedBlockPreview();
        }


        /// <summary>
        /// Открывает отдельное окно OpenCV
        /// с предпросмотром выбранного блока.
        /// </summary>
        private void BlockLibraryController_PreviewRequested(object? sender, ProjectBlockEventArgs e)
        {
            try
            {
                string? sourceImagePath = projectManager.GetBlockImageFullPath(e.Block.Id);
                if (!string.IsNullOrWhiteSpace(sourceImagePath))
                {
                    ShowSourceBlockPreview(e.Block, sourceImagePath);
                    return;
                }

                bool hasSourceParameter = e.Block.Parameters?.Any(parameter => string.Equals(parameter.Name, "ImageRelativePath", StringComparison.OrdinalIgnoreCase)) == true;
                if (hasSourceParameter)
                {
                    throw new InvalidOperationException($"Для блока «{e.Block.Name}» ещё не выбрано изображение.\n\nНажмите кнопку 📁 и выберите файл.");
                }
                using PipelineExecutionResult result = pipelineExecutor.ExecuteToBlock(e.Block.Id);
                string windowName = $"Блок {e.Block.Order + 1} — {e.Block.Name}";
                bool contoursAlreadyDrawn = IsDrawContoursBlock(e.Block);
                if (result.Contours != null && !contoursAlreadyDrawn)
                {
                    using Mat previewImage = CreateContourPreviewImage(result.Image);
                    DrawContoursOnPreview(previewImage, result.Contours);
                    Cv2.ImShow(windowName, previewImage);
                }
                else
                {
                    Cv2.ImShow(windowName, result.Image);
                }
                Cv2.WaitKey(1);
            }
            catch (Exception ex)
            {
                ShowError(ex, "Ошибка предпросмотра");
            }
        }
        private static void ShowSourceBlockPreview(ProjectBlock block, string imagePath)
        {
            ArgumentNullException.ThrowIfNull(block);
            if (string.IsNullOrWhiteSpace(imagePath))
            {
                throw new ArgumentException("Не указан путь к изображению.", nameof(imagePath));
            }
            if (!File.Exists(imagePath))
            {
                throw new FileNotFoundException($"Файл изображения блока «{block.Name}» не найден.", imagePath);
            }
            /*
             * ImreadModes.Unchanged сохраняет исходное
             * число каналов:
             *
             * Gray остаётся Gray;
             * BGR остаётся BGR;
             * BGRA остаётся BGRA.
             */
            using Mat sourceImage = Cv2.ImRead(imagePath, ImreadModes.Unchanged);
            if (sourceImage.Empty())
            {
                throw new InvalidDataException($"Не удалось прочитать изображение:\n{imagePath}");
            }
            string windowName = $"Источник {block.Order + 1} — {block.Name}";
            Cv2.ImShow(windowName, sourceImage);
            Cv2.WaitKey(1);
        }

        private void PropertyGridBlock_PropertyValueChanged(object? sender, PropertyValueChangedEventArgs e)
        {
            try
            {
                propertyGridBlock.Refresh();

                ShowSelectedBlockPreview();
            }
            catch (Exception ex)
            {
                ShowError(ex, "Ошибка изменения параметра");
            }
        }

        private void ShowSelectedBlockPreview()
        {
            if (selectedProjectBlock == null)
            {
                return;
            }
            if (!projectManager.HasProject)
            {
                return;
            }
            try
            {
                string? sourceImagePath = projectManager.GetBlockImageFullPath(selectedProjectBlock.Id);
                bool isSourceBlock = string.Equals(selectedProjectBlock.BlockType, "ImageSource", StringComparison.OrdinalIgnoreCase) || string.Equals(selectedProjectBlock.BlockType, "SecondImageSource", StringComparison.OrdinalIgnoreCase);
                if (!string.IsNullOrWhiteSpace(sourceImagePath))
                {
                    Bitmap? bitmap = LoadBitmapCopy(sourceImagePath);
                    if (string.Equals(selectedProjectBlock.BlockType, "ImageSource", StringComparison.OrdinalIgnoreCase))
                    {
                        ReplacePictureBoxImage(pictureBoxPreviewL, bitmap);
                    }
                    else
                    {
                        ReplacePictureBoxImage(pictureBoxPreviewR, bitmap);
                    }
                    return;
                }
                if (isSourceBlock)
                {
                    if (string.Equals(selectedProjectBlock.BlockType, "ImageSource", StringComparison.OrdinalIgnoreCase))
                    {
                        ReplacePictureBoxImage(pictureBoxPreviewL, null);
                    }
                    else
                    {
                        ReplacePictureBoxImage(pictureBoxPreviewR, null);
                    }
                    return;
                }
                using PipelineExecutionResult result = pipelineExecutor.ExecuteToBlock(selectedProjectBlock.Id);
                ShowPipelineInfo(result);
                bool contoursAlreadyDrawn = IsDrawContoursBlock(selectedProjectBlock);
                if (result.Contours != null && !contoursAlreadyDrawn)
                {
                    ShowProcessedImage(result.Image, result.Contours);
                }
                else
                {
                    ShowProcessedImage(result.Image);
                }
            }
            catch (Exception ex)
            {
                ShowError(ex, "Ошибка предпросмотра блока");
            }
        }

        /// <summary>
        /// Определяет, нарисовал ли текущий блок
        /// контуры непосредственно на изображении.
        /// </summary>
        private static bool IsDrawContoursBlock(ProjectBlock block)
        {
            ArgumentNullException.ThrowIfNull(block);

            return string.Equals(block.BlockType, "DrawContours", StringComparison.Ordinal);
        }

        /// <summary>
        /// Создаёт трёхканальную BGR-копию изображения,
        /// пригодную для цветной отрисовки контуров.
        /// </summary>
        private static Mat CreateContourPreviewImage(Mat source)
        {
            ArgumentNullException.ThrowIfNull(source);

            if (source.Empty())
                throw new ArgumentException("Исходное изображение пустое.", nameof(source));

            Mat result = new Mat();

            try
            {
                switch (source.Channels())
                {
                    case 1:
                        Cv2.CvtColor(source, result, ColorConversionCodes.GRAY2BGR);
                        break;

                    case 3:
                        source.CopyTo(result);
                        break;

                    case 4:
                        Cv2.CvtColor(source, result, ColorConversionCodes.BGRA2BGR);
                        break;

                    default:
                        throw new InvalidOperationException($"Предпросмотр контуров поддерживает изображения с 1, 3 или 4 каналами. Получено каналов: {source.Channels()}.");
                }

                return result;
            }
            catch
            {
                result.Dispose();
                throw;
            }
        }

        /// <summary>
        /// Рисует контуры на подготовленном изображении.
        /// Переданный Mat изменяется.
        /// </summary>
        private static void DrawContoursOnPreview(Mat previewImage, ContourData contours)
        {
            ArgumentNullException.ThrowIfNull(previewImage);
            ArgumentNullException.ThrowIfNull(contours);

            if (previewImage.Empty())
                throw new ArgumentException("Изображение предпросмотра пустое.", nameof(previewImage));

            if (contours.IsEmpty)
                return;

            Cv2.DrawContours(previewImage, contours.Contours, -1, new Scalar(0, 255, 0), 2, LineTypes.AntiAlias);
        }

        /// <summary>
        /// Показывает стандартное окно ошибки.
        /// </summary>
        private static void ShowError(Exception exception, string title)
        {
            ArgumentNullException.ThrowIfNull(exception);

            MessageBox.Show(exception.Message, title, MessageBoxButtons.OK, MessageBoxIcon.Error);
        }

        private void MainForm_FormClosed(object sender, FormClosedEventArgs e)
        {
            projectManager.ProjectChanged -= ProjectManager_ProjectChanged;

            propertyGridBlock.PropertyValueChanged -= PropertyGridBlock_PropertyValueChanged;

            ReplacePictureBoxImage(pictureBoxPreviewL, null);
            ReplacePictureBoxImage(pictureBoxPreviewR, null);

            if (blockLibraryController != null)
            {
                blockLibraryController.BlockAdded -= BlockLibraryController_BlockAdded;
                blockLibraryController.BlockSelected -= BlockLibraryController_BlockSelected;
                blockLibraryController.PreviewRequested -= BlockLibraryController_PreviewRequested;

                blockLibraryController.Dispose();
                blockLibraryController = null;
            }

            selectedProjectBlock = null;

            try
            {
                Cv2.DestroyAllWindows();
            }
            catch
            {
                /*
                 * При закрытии программы ошибки уничтожения
                 * окон OpenCV отображать не требуется.
                 */
            }
        }

        private void ShowPipelineInfo(PipelineExecutionResult result)
        {
            if (string.IsNullOrWhiteSpace(result.ResultText))
            {
                toolStripStatusLabelInfo.Text = "Готов.";
                return;
            }

            toolStripStatusLabelInfo.Text = result.ResultText;
        }
        /// <summary>
        /// Показывает результат выполнения блока
        /// в нижней строке состояния.
        /// </summary>
        private void ShowPipelineStatus(PipelineExecutionResult result)
        {
            ArgumentNullException.ThrowIfNull(result);
            toolStripStatusLabelInfo.Text = string.IsNullOrWhiteSpace(result.ResultText) ? "Готов." : result.ResultText;
        }


        private ProjectBlock? FindSourceBlock(SourceImageSlot sourceSlot)
        {
            if (projectManager.CurrentProject == null)
            {
                return null;
            }
            string blockType = sourceSlot switch
            {
                SourceImageSlot.Main => "ImageSource",
                SourceImageSlot.Second => "SecondImageSource",
                _ => string.Empty
            };
            if (string.IsNullOrWhiteSpace(blockType))
            {
                return null;
            }
            return projectManager.CurrentProject.Blocks.FirstOrDefault(block => string.Equals(block.BlockType, blockType, StringComparison.OrdinalIgnoreCase));
        }
        private string? GetMainSourceImagePath()
        {
            ProjectBlock? sourceBlock = FindSourceBlock(SourceImageSlot.Main);
            if (sourceBlock == null)
            {
                return null;
            }
            return projectManager.GetBlockImageFullPath(sourceBlock.Id);
        }
        private string? GetSecondSourceImagePath()
        {
            ProjectBlock? sourceBlock = FindSourceBlock(SourceImageSlot.Second);
            if (sourceBlock == null)
            {
                return null;
            }
            return projectManager.GetBlockImageFullPath(sourceBlock.Id);
        }

        private static Bitmap? LoadBitmapCopy(string? imagePath)
        {
            if (string.IsNullOrWhiteSpace(imagePath) || !File.Exists(imagePath))
            {
                return null;
            }
            using Image sourceImage = Image.FromFile(imagePath);
            return new Bitmap(sourceImage);
        }

        private void toolStripButtonabout_Click(object sender, EventArgs e)
        {
            if (aboutForm == null || aboutForm.IsDisposed)
            {
                aboutForm = new AboutForm();

                aboutForm.FormClosed +=
                    (s, args) => aboutForm = null;

                aboutForm.Show(this);
            }
            else
            {
                aboutForm.Activate();
            }
        }
    }
}