// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using Microsoft.MixedReality.OpenXR.ARSubsystems;
using System;
using Unity.Collections;
using UnityEngine;
using UnityEngine.XR.ARFoundation;

namespace Microsoft.MixedReality.OpenXR
{
    /// <summary>
    /// Types of markers that can be tracked.
    /// </summary>
    public enum ARMarkerType
    {
        /// <summary>
        /// A marker of QRCode type.
        /// Equivalent to XR_SCENE_MARKER_TYPE_QR_CODE_MSFT.
        /// </summary>
        QRCode = 1
    }

    /// <summary>
    /// Represents types of QRCodes that can be detected.
    /// </summary>
    public enum QRCodeType
    {
        /// <summary>
        /// A QRCode type of QRCode.
        /// Equivalent to XR_SCENE_MARKER_QR_CODE_SYMBOL_TYPE_QR_CODE_MSFT.
        /// </summary>
        QRCode = 1,

        /// <summary>
        /// A QRCode type of MicroQRCode.
        /// Equivalent to XR_SCENE_MARKER_QR_CODE_SYMBOL_TYPE_MICRO_QR_CODE_MSFT.
        /// </summary>
        MicroQRCode = 2
    }

    /// <summary>
    /// Types of transforms that can be applied to markers.
    /// </summary>
    public enum TransformMode
    {
        /// <summary>
        /// Marker centered at origin.
        /// </summary>
        MostStable = 0,

        /// <summary>
        /// Marker centered at geometric center.
        /// </summary>
        Center = 1,
    }

    /// <summary>
    /// Represents properties of detected QRCodes.
    /// </summary>
    /// <remarks>See https://github.com/yansikeim/QR-Code/blob/master/ISO%20IEC%2018004%202015%20Standard.pdf for more information.</remarks>
    public struct QRCodeProperties
    {
        /// <summary>
        /// Version of the QRCode.
        /// </summary>
        /// <remarks>
        /// The version is in the range of 1-40 if the type is QRCode.
        /// The version is in the range or 1-4 if the type is microQRCode.
        /// </remarks>
        public uint version;

        /// <summary>
        /// Type of the QRCode.
        /// </summary>
        public QRCodeType type;
    }

    /// <summary>
    /// Represents a marker detected by an AR device.
    /// </summary>
    /// <remarks>
    /// Generated by the <see cref="ARMarkerManager"/> when an AR device detects
    /// a marker in the environment.
    /// </remarks>
    [DefaultExecutionOrder(ARUpdateOrder.k_Plane)]
    [DisallowMultipleComponent]
    public sealed class ARMarker : ARTrackable<XRMarker, ARMarker>
    {
        /// <summary>
        /// The time when the marker was last seen. Comparable to <see cref="Time.realtimeSinceStartup"/>.
        /// </summary>
        public float lastSeenTime => sessionRelativeData.lastSeenTime;

        /// <summary>
        /// The center relative to the pose in the X, Y plane.
        /// </summary>
        public Vector2 center => sessionRelativeData.center;

        /// <summary>
        /// The physical size (dimensions) of the marker in meters.
        /// </summary>
        public Vector2 size => sessionRelativeData.size;

        /// <summary>
        /// The type of marker. Currently we only support markert os QRCode type.
        /// </summary>
        public ARMarkerType markerType => sessionRelativeData.markerType;

        /// <summary>
        /// The type of transform to apply on the marker.
        /// </summary>
        public TransformMode transformMode
        {
            get => sessionRelativeData.transformMode;
            set
            {
                if (ARMarkerManager.Instance != null)
                {
                    ARMarkerManager.Instance.SetTransformMode(trackableId, value);
                }
            }
        }

        /// <summary>
        /// Get a native pointer associated with this marker.
        /// </summary>
        /// <remarks>
        /// The data pointed to by this member is implementation defined.
        /// The lifetime of the pointed to object is also
        /// implementation defined, but should be valid at least until the next
        /// <see cref="ARSession"/> update.
        /// </remarks>
        public IntPtr nativePtr => sessionRelativeData.nativePtr;

        /// <summary>
        /// Get the decoded string associated with this marker.
        /// </summary>
        public string GetDecodedString()
        {
            if (ARMarkerManager.Instance != null)
            {
                return ARMarkerManager.Instance.GetDecodedString(trackableId);
            }
            return string.Empty;
        }

        /// <summary>
        /// Get the raw data associated with this marker.
        /// </summary>
        public NativeArray<byte> GetRawData(Allocator allocator)
        {
            if (ARMarkerManager.Instance != null)
            {
                return ARMarkerManager.Instance.GetRawData(trackableId, allocator);
            }
            return new NativeArray<byte>(0, allocator);
        }

        /// <summary>
        /// Get the properties associated with this QRCode marker.
        /// </summary>
        public QRCodeProperties GetQRCodeProperties()
        {
            if (markerType == ARMarkerType.QRCode)
            {
                if (ARMarkerManager.Instance != null)
                {
                    return ARMarkerManager.Instance.GetQRCodeProperties(trackableId);
                }
                return new QRCodeProperties();
            }
            else
            {
                throw new InvalidOperationException("GetQRCodeProperties() is only valid when markerType == ARMarkerType.QRCode");
            }
        }
    }
}
