using MainShell.Common;
using MainShell.Hardware;
using MainShell.Log;
using MainShell.Models;
using MaxwellFramework.Core.Attributes;
using MwFramework.Device;
using MwFramework.Device.Model;
using System;
using System.Threading;
using System.Threading.Tasks;
using AppCameraType = MainShell.Common.CameraType;

namespace MainShell.Vision
{
    /// <summary>
    /// ???????????????? HardwareManager ??????????????????????????§Ö???????
    /// </summary>
    [Singleton]
    public class ImageCaptureService : IImageCaptureService
    {
        private readonly HardwareManager _hardwareManager;
        private readonly object _upCameraCaptureLock = new object();
        private readonly object _downCameraCaptureLock = new object();
        private readonly object _mapCameraCaptureLock = new object();
        private readonly object _upWsCameraCaptureLock = new object();
        private readonly object _upWideCameraCaptureLock = new object();

        public ImageCaptureService(HardwareManager hardwareManager)
        {
            _hardwareManager = hardwareManager ?? throw new ArgumentNullException(nameof(hardwareManager));
        }

        /// <summary>
        /// ??????????????????????
        /// </summary>
        public Task<ImageCaptureResult> CaptureAsync(
            AppCameraType source,
            int timeoutMilliseconds,
            CameraCaptureMode captureMode = CameraCaptureMode.Stream,
            CancellationToken cancellationToken = default(CancellationToken))
        {
            CameraCaptureOptions options = new CameraCaptureOptions();
            options.TimeoutMilliseconds = timeoutMilliseconds;
            options.CaptureMode = captureMode;

            return CaptureAsync(source, options, cancellationToken);
        }

        /// <summary>
        /// ?????????????????
        /// ????????????????????????????????????????????????????????§¹??
        /// </summary>
        public Task<ImageCaptureResult> CaptureAsync(
            AppCameraType source,
            CameraCaptureOptions options,
            CancellationToken cancellationToken = default(CancellationToken))
        {
            if (options == null)
            {
                throw new ArgumentNullException(nameof(options));
            }

            int timeoutMilliseconds = options.TimeoutMilliseconds;
            if (timeoutMilliseconds <= 0)
            {
                timeoutMilliseconds = 5000;
            }

            MwCamera camera = GetCamera(source);
            if (camera == null)
            {
                string message = string.Format("ImageCaptureService: camera '{0}' not found in HardwareManager.", source);
                message.LogSysError();
                return Task.FromResult(ImageCaptureResult.Failure(
                    ImageCaptureStatus.CameraNotFound,
                    VisionErrorCode.CameraNotFound,
                    VisionFailureCategory.Capture,
                    VisionAlarmIds.CameraNotFound,
                    MessageKey.VisionCameraNotFound,
                    source,
                    options.CaptureMode,
                    message));
            }

            if (!camera.IsOpen)
            {
                string message = string.Format("ImageCaptureService: camera '{0}' is not open.", source);
                message.LogSysError();
                return Task.FromResult(ImageCaptureResult.Failure(
                    ImageCaptureStatus.CameraNotOpen,
                    VisionErrorCode.CameraNotOpen,
                    VisionFailureCategory.Capture,
                    VisionAlarmIds.CameraNotOpen,
                    MessageKey.VisionCameraNotOpen,
                    source,
                    options.CaptureMode,
                    message));
            }

            if (!camera.IsGrabbing)
            {
                string message = string.Format(
                    "ImageCaptureService: camera '{0}' is not grabbing. Capture mode '{1}' requires grabbing to be prepared by caller.",
                    source,
                    options.CaptureMode);
                message.LogSysError();
                return Task.FromResult(ImageCaptureResult.Failure(
                    ImageCaptureStatus.CameraNotGrabbing,
                    VisionErrorCode.CameraNotGrabbing,
                    VisionFailureCategory.Capture,
                    VisionAlarmIds.CameraNotGrabbing,
                    MessageKey.VisionCameraNotGrabbing,
                    source,
                    options.CaptureMode,
                    message));
            }

            string.Format(
                "ImageCaptureService: capturing from '{0}' (mode={1}, timeout={2}ms).",
                source,
                options.CaptureMode,
                timeoutMilliseconds).LogInfo();

            object captureLock = GetCaptureLock(source);

            try
            {
                CameraData data;

                lock (captureLock)
                {
                    cancellationToken.ThrowIfCancellationRequested();

                    switch (options.CaptureMode)
                    {
                        case CameraCaptureMode.SoftTrigger:
                            data = ExecuteSoftTriggerSync(camera, source, timeoutMilliseconds, out bool softTriggerFailed);
                            if (softTriggerFailed)
                            {
                                string softTriggerFailureMessage = string.Format(
                                    "ImageCaptureService: soft trigger capture failed for camera '{0}' (timeout={1}ms).",
                                    source,
                                    timeoutMilliseconds);
                                softTriggerFailureMessage.LogSysError();
                                return Task.FromResult(ImageCaptureResult.Failure(
                                    ImageCaptureStatus.SoftTriggerFailed,
                                    VisionErrorCode.SoftTriggerFailed,
                                    VisionFailureCategory.Driver,
                                    VisionAlarmIds.SoftTriggerFailed,
                                    MessageKey.VisionSoftTriggerFailed,
                                    source,
                                    options.CaptureMode,
                                    softTriggerFailureMessage));
                            }
                            break;

                        case CameraCaptureMode.Stream:
                            data = camera.SnapImage(timeoutMilliseconds);
                            break;

                        default:
                            throw new NotSupportedException(string.Format("ImageCaptureService: capture mode '{0}' is not supported.", options.CaptureMode));
                    }
                }

                cancellationToken.ThrowIfCancellationRequested();

                if (data == null)
                {
                    string message = string.Format(
                        "ImageCaptureService: no frame received from '{0}' with capture mode '{1}'.",
                        source,
                        options.CaptureMode);
                    message.LogSysError();
                    ImageCaptureStatus noFrameStatus = options.CaptureMode == CameraCaptureMode.SoftTrigger
                        ? ImageCaptureStatus.Timeout
                        : ImageCaptureStatus.NoFrame;
                    VisionErrorCode noFrameErrorCode = options.CaptureMode == CameraCaptureMode.SoftTrigger
                        ? VisionErrorCode.CaptureTimeout
                        : VisionErrorCode.NoFrame;
                    int? noFrameAlarmId = options.CaptureMode == CameraCaptureMode.SoftTrigger
                        ? (int?)VisionAlarmIds.CaptureTimeout
                        : (int?)VisionAlarmIds.NoFrame;
                    MessageKey noFrameMessageKey = options.CaptureMode == CameraCaptureMode.SoftTrigger
                        ? MessageKey.VisionCaptureTimeout
                        : MessageKey.VisionNoFrameReturned;

                    return Task.FromResult(ImageCaptureResult.Failure(
                        noFrameStatus,
                        noFrameErrorCode,
                        VisionFailureCategory.Capture,
                        noFrameAlarmId,
                        noFrameMessageKey,
                        source,
                        options.CaptureMode,
                        message));
                }

                return Task.FromResult(ImageCaptureResult.Success(source, options.CaptureMode, new MxImage(data)));
            }
            catch (OperationCanceledException ex)
            {
                string message = string.Format("ImageCaptureService: capture from '{0}' was cancelled by caller.", source);
                message.LogInfo();
                return Task.FromResult(ImageCaptureResult.Failure(
                    ImageCaptureStatus.Cancelled,
                    VisionErrorCode.OperationCancelled,
                    VisionFailureCategory.Cancelled,
                    null,
                    MessageKey.VisionOperationCancelled,
                    source,
                    options.CaptureMode,
                    message,
                    ex));
            }
            catch (Exception ex)
            {
                string message = string.Format("ImageCaptureService: capture from '{0}' failed. {1}", source, ex.Message);
                message.LogSysError();
                return Task.FromResult(ImageCaptureResult.Failure(
                    ImageCaptureStatus.DriverError,
                    VisionErrorCode.DriverError,
                    VisionFailureCategory.Driver,
                    VisionAlarmIds.DriverError,
                    MessageKey.VisionDriverError,
                    source,
                    options.CaptureMode,
                    message,
                    ex));
            }
        }

        private static CameraData ExecuteSoftTriggerSync(MwCamera camera, AppCameraType source, int timeoutMilliseconds, out bool softTriggerFailed)
        {
            softTriggerFailed = false;
            CameraData data = new CameraData();
            DriverLibResult result = camera.ExcuteSoftTrigger(ref data);
            if (result != DriverLibResult.DriverLibNoError)
            {
                softTriggerFailed = true;
                string.Format(
                    "ImageCaptureService: soft trigger capture failed for camera '{0}' (timeout={1}ms), driver result={2}.",
                    source,
                    timeoutMilliseconds,
                    result).LogSysError();
                return null;
            }

            if (data == null)
            {
                string.Format(
                    "ImageCaptureService: soft trigger capture returned null frame for camera '{0}' (timeout={1}ms).",
                    source,
                    timeoutMilliseconds).LogSysError();
                return null;
            }

            return data;
        }

        private MwCamera GetCamera(AppCameraType source)
        {
            return _hardwareManager.GetCamera(source).Camera;
        }

        private object GetCaptureLock(AppCameraType source)
        {
            switch (source)
            {
                case AppCameraType.TopPositionCamera:
                    return _upCameraCaptureLock;

                case AppCameraType.TopWideCamera:
                    return _mapCameraCaptureLock;

                case AppCameraType.MapCamera:
                    return _downCameraCaptureLock;
                case AppCameraType.TopWsCamera:
                    return _upWsCameraCaptureLock;
                case AppCameraType.TopWideWsCamera:
                        return _upWideCameraCaptureLock;
                default:
                    throw new ArgumentOutOfRangeException(nameof(source), source, "ImageCaptureService: unknown camera type for capture lock.");
            }
        }
    }
}
