using System;
using System.Collections.Generic;
using UnityEngine;

namespace Streamingle.Gaze
{
    /// <summary>
    /// Row-major layout of the calibration grid. Down/Up describe gaze pitch and
    /// Left/Right describe gaze yaw from the character's point of view.
    /// </summary>
    public enum GazeCalibrationPoint
    {
        DownLeft = 0,
        DownCenter = 1,
        DownRight = 2,
        CenterLeft = 3,
        Center = 4,
        CenterRight = 5,
        UpLeft = 6,
        UpCenter = 7,
        UpRight = 8
    }

    public enum GazeChannelUsage
    {
        EyeLook = 0,
        Corrective = 1
    }

    public enum GazeCorrectiveBlendMode
    {
        AdditiveFromCenter = 0,
        Override = 1
    }

    public enum GazeCutResponse
    {
        Snap = 0,
        Smooth = 1
    }

    [Serializable]
    public sealed class GazeBlendShapeChannel
    {
        [SerializeField]
        private string blendShapeName;

        [SerializeField]
        private GazeChannelUsage usage;

        [SerializeField]
        [Tooltip("Corrective channels normally add their direction-dependent delta from the Center sample to the live facial value.")]
        private GazeCorrectiveBlendMode correctiveBlendMode;

        public GazeBlendShapeChannel(
            string blendShapeName,
            GazeChannelUsage usage = GazeChannelUsage.EyeLook,
            GazeCorrectiveBlendMode correctiveBlendMode = GazeCorrectiveBlendMode.AdditiveFromCenter)
        {
            this.blendShapeName = blendShapeName;
            this.usage = usage;
            this.correctiveBlendMode = correctiveBlendMode;
        }

        public string BlendShapeName
        {
            get => blendShapeName;
            set => blendShapeName = value;
        }

        public GazeChannelUsage Usage
        {
            get => usage;
            set => usage = value;
        }

        public GazeCorrectiveBlendMode CorrectiveBlendMode
        {
            get => correctiveBlendMode;
            set => correctiveBlendMode = value;
        }
    }

    [Serializable]
    public sealed class GazeCalibrationSample
    {
        [SerializeField]
        private GazeCalibrationPoint point;

        [SerializeField]
        private bool captured;

        [SerializeField]
        private float[] weights = Array.Empty<float>();

        public GazeCalibrationSample(GazeCalibrationPoint point)
        {
            this.point = point;
        }

        public GazeCalibrationPoint Point => point;

        public bool Captured => captured;

        public IReadOnlyList<float> Weights => weights;

        internal float GetWeight(int index)
        {
            return weights != null && index >= 0 && index < weights.Length ? weights[index] : 0f;
        }

        internal void SetWeights(IReadOnlyList<float> values, int channelCount)
        {
            var count = Mathf.Max(0, channelCount);
            if (weights == null || weights.Length != count)
                weights = new float[count];

            for (var i = 0; i < count; i++)
                weights[i] = values != null && i < values.Count ? values[i] : 0f;

            captured = true;
        }

        internal void Resize(int channelCount)
        {
            var count = Mathf.Max(0, channelCount);
            if (weights != null && weights.Length == count)
                return;

            var resized = new float[count];
            if (weights != null)
                Array.Copy(weights, resized, Mathf.Min(weights.Length, resized.Length));
            weights = resized;
            captured = false;
        }

        internal void Invalidate()
        {
            captured = false;
        }

        internal void SetPoint(GazeCalibrationPoint value)
        {
            point = value;
        }
    }

    /// <summary>
    /// Command passed from Timeline (or a future real-time source) to the final
    /// LateUpdate blendshape writer.
    /// </summary>
    public struct GazeTimelineState
    {
        public Vector3 TargetPosition;
        public bool HasTargetPosition;
        public Vector3 TargetDirection;
        public bool HasTargetDirection;
        public Transform Target;
        public bool UseMainCamera;
        public float Influence;
        public bool EnableCorrectives;
        public bool OverrideCutResponse;
        public GazeCutResponse CutResponse;
        public float CutTransitionDuration;
        public bool ResetSmoothing;
        public bool Deterministic;
    }

    public readonly struct GazeDirectionResult
    {
        public GazeDirectionResult(
            Vector3 worldDirection,
            Vector3 localDirection,
            float rawYaw,
            float rawPitch,
            float clampedYaw,
            float clampedPitch,
            float normalizedYaw,
            float normalizedPitch)
        {
            WorldDirection = worldDirection;
            LocalDirection = localDirection;
            RawYaw = rawYaw;
            RawPitch = rawPitch;
            ClampedYaw = clampedYaw;
            ClampedPitch = clampedPitch;
            NormalizedYaw = normalizedYaw;
            NormalizedPitch = normalizedPitch;
        }

        public Vector3 WorldDirection { get; }
        public Vector3 LocalDirection { get; }
        public float RawYaw { get; }
        public float RawPitch { get; }
        public float ClampedYaw { get; }
        public float ClampedPitch { get; }
        public float NormalizedYaw { get; }
        public float NormalizedPitch { get; }

        public bool WasClamped => !Mathf.Approximately(RawYaw, ClampedYaw)
                                  || !Mathf.Approximately(RawPitch, ClampedPitch);
    }
}
