using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Threading;
using LensAdjustmentTool.Configuration;
using LensAdjustmentTool.Models;
using TIS.Imaging;

namespace LensAdjustmentTool.Processing
{
    public sealed class LensAdjustmentProcessor
    {
        private int _mode = (int)AdjustmentMode.Focus;
        private readonly object _focusMaxSync = new object();
        private readonly object _apertureRoiSync = new object();
        private double _focusMaximum;
        private Rectangle _apertureCalculationRoi = AppSettings.ApertureCalculationRoi;

        public AdjustmentMode Mode => (AdjustmentMode)Volatile.Read(ref _mode);

        public void SetMode(AdjustmentMode mode, bool resetFocusMaximum)
        {
            Interlocked.Exchange(ref _mode, (int)mode);
            if (mode == AdjustmentMode.Focus && resetFocusMaximum)
            {
                ResetFocusMaximum();
            }
        }

        public void ResetFocusMaximum()
        {
            lock (_focusMaxSync)
            {
                _focusMaximum = 0.0;
            }
        }

        public Rectangle MoveApertureCalculationRoi(int deltaX, int deltaY)
        {
            lock (_apertureRoiSync)
            {
                _apertureCalculationRoi = NormalizeApertureCalculationRoi(new Rectangle(
                    _apertureCalculationRoi.X + deltaX,
                    _apertureCalculationRoi.Y + deltaY,
                    _apertureCalculationRoi.Width,
                    _apertureCalculationRoi.Height));

                return _apertureCalculationRoi;
            }
        }

        public Rectangle SetApertureCalculationRoi(Rectangle roi)
        {
            lock (_apertureRoiSync)
            {
                _apertureCalculationRoi = NormalizeApertureCalculationRoi(roi);
                return _apertureCalculationRoi;
            }
        }

        public Rectangle GetApertureCalculationRoi()
        {
            lock (_apertureRoiSync)
            {
                return _apertureCalculationRoi;
            }
        }

        public FrameAnalysisResult Process(IFrameQueueBuffer frame)
        {
            if (frame == null) throw new ArgumentNullException(nameof(frame));

            AdjustmentMode mode = Mode;
            using (Bitmap wrapped = frame.CreateBitmapWrap())
            {
                if (wrapped.Width != AppSettings.ExpectedFrameWidth ||
                    wrapped.Height != AppSettings.ExpectedFrameHeight)
                {
                    throw new InvalidOperationException(
                        $"Unexpected frame size: {wrapped.Width}x{wrapped.Height}. " +
                        $"Expected {AppSettings.ExpectedFrameWidth}x{AppSettings.ExpectedFrameHeight}.");
                }

                return mode == AdjustmentMode.Focus
                    ? ProcessFocus(wrapped)
                    : ProcessAperture(wrapped);
            }
        }

        private FrameAnalysisResult ProcessFocus(Bitmap fullFrame)
        {
            Bitmap roi = Clone24(fullFrame, AppSettings.FocusRoi);
            try
            {
                double current = FocusAnalyzer.CalculateScore(roi);
                double maximum;

                lock (_focusMaxSync)
                {
                    if (current > _focusMaximum)
                    {
                        _focusMaximum = current;
                    }
                    maximum = _focusMaximum;
                }

                // System.Drawing Rotate90FlipNone is a 90-degree clockwise rotation.
                roi.RotateFlip(RotateFlipType.Rotate90FlipNone);

                Bitmap display = roi;
                roi = null;
                return new FrameAnalysisResult(
                    AdjustmentMode.Focus,
                    display,
                    current,
                    maximum);
            }
            finally
            {
                roi?.Dispose();
            }
        }

        private FrameAnalysisResult ProcessAperture(Bitmap fullFrame)
        {
            Bitmap display = Clone24(fullFrame, AppSettings.ApertureDisplayRoi);
            try
            {
                Rectangle calculationRoi = GetApertureCalculationRoi();
                Rectangle relativeCalc = new Rectangle(
                    calculationRoi.X - AppSettings.ApertureDisplayRoi.X,
                    calculationRoi.Y - AppSettings.ApertureDisplayRoi.Y,
                    calculationRoi.Width,
                    calculationRoi.Height);
                using (Bitmap calculation = Clone24(display, relativeCalc))
                {
                    double mean = ApertureAnalyzer.CalculateMeanLuminance(calculation);

                    using (Graphics g = Graphics.FromImage(display))
                    using (Pen pen = new Pen(Color.Red, 3.0f))
                    {
                        g.DrawRectangle(
                            pen,
                            relativeCalc.X,
                            relativeCalc.Y,
                            relativeCalc.Width - 1,
                            relativeCalc.Height - 1);
                    }

                    Bitmap ownedDisplay = display;
                    display = null;
                    return new FrameAnalysisResult(
                        AdjustmentMode.Aperture,
                        ownedDisplay,
                        mean,
                        mean - AppSettings.ApertureTarget);
                }
            }
            finally
            {
                display?.Dispose();
            }
        }

        private static Rectangle NormalizeApertureCalculationRoi(Rectangle roi)
        {
            Rectangle displayRoi = AppSettings.ApertureDisplayRoi;
            int width = Math.Min(roi.Width, displayRoi.Width);
            int height = Math.Min(roi.Height, displayRoi.Height);
            int x = Math.Max(displayRoi.X, Math.Min(roi.X, displayRoi.Right - width));
            int y = Math.Max(displayRoi.Y, Math.Min(roi.Y, displayRoi.Bottom - height));
            return new Rectangle(x, y, width, height);
        }

        private static Bitmap Clone24(Bitmap source, Rectangle roi)
        {
            Rectangle bounds = new Rectangle(0, 0, source.Width, source.Height);
            if (!bounds.Contains(roi))
            {
                throw new ArgumentOutOfRangeException(
                    nameof(roi),
                    $"ROI {roi} is outside image bounds {bounds}.");
            }

            return source.Clone(roi, PixelFormat.Format24bppRgb);
        }
    }
}
