using System.Collections.Generic;
using UnityEngine;

namespace pdinklag
{
    /// <summary>
    /// Utility for raycasting (skinned or static) meshes.
    /// </summary>
    public class MeshRaycast
    {
        private Mesh _bakedMesh;
        private GameObject _go;

        public MeshCollider Collider { get; private set; }

        /// <summary>
        /// The bounds of the combined mesh.
        /// </summary>
        public Bounds Bounds => Collider.bounds;
        
        /// <summary>
        /// The transform of the raycast target object.
        /// </summary>
        public Transform Transform => _go.transform;

        /// <summary>
        /// Constructs a mesh raycaster.
        /// 
        /// This will find and combine all mesh (including skinned meshes, using the current pose) in the target's hierarchy and combine them to a single mesh.
        /// Said combined mesh is stored on an internal, hidden game object as a mesh collider, making it available for raycasting.
        /// 
        /// The MeshRaycast object must therefore be destroyed using <see cref="Destroy"/> when it is no longer needed.
        /// </summary>
        /// <param name="target"></param>
        public MeshRaycast(Transform target)
        {
            _go = new GameObject(target.gameObject.name + " (Raycast Target)");
            _go.hideFlags = HideFlags.HideAndDontSave;

            _bakedMesh = target.BakeMeshes();
            Collider = _go.AddComponent<MeshCollider>();
            Collider.convex = false;
            Collider.sharedMesh = _bakedMesh;
        }

        public bool Raycast(Ray ray, float maxDistance, out RaycastHit hit)
        {
            return Collider.Raycast(ray, out hit, maxDistance);
        }

        public bool Raycast(Vector3 start, Vector3 direction, float maxDistance, out RaycastHit hit)
        {
            return Collider.Raycast(new Ray(start, direction), out hit, maxDistance);
        }

        public Vector3 Raycast(Vector3 start, Vector3 direction, float maxDistance)
        {
            Collider.Raycast(new Ray(start, direction), out var hit, maxDistance);
            return hit.point;
        }

        public void Scan(Vector3 startPos, Vector3 endPos, int numPoints, Vector3 direction, float maxDistance, IList<RaycastHit> outHits)
        {
            outHits.Clear();

            var d = (endPos - startPos) / (numPoints - 1);
            for (var i = 0; i < numPoints; i++)
            {
                if (Raycast(startPos + i * d, direction, maxDistance, out var hit))
                {
                    outHits.Add(hit);
                }
            }
        }

        public void Destroy()
        {
            Object.DestroyImmediate(_go);
            Object.DestroyImmediate(_bakedMesh);
        }
    }
}
