using UnityEngine;

namespace AwesomeTechnologies.Utility
{
    public class ShadowUtility
    {
        /// <summary>
        /// Check for shadow visibility, send minimum terrain height in worldspace as planeOrigin
        /// </summary>
        /// <param name="objectBounds"></param>
        /// <param name="lightDirection"></param>
        /// <param name="planeOrigin"></param>
        /// <param name="frustumPlanes"></param>
        /// <returns></returns>
        public static bool IsShadowVisible(Bounds objectBounds, Vector3 lightDirection, Vector3 planeOrigin, Plane[] frustumPlanes)
        {
            bool hitPlane;
            Bounds shadowBounds = GetShadowBounds(objectBounds, lightDirection, planeOrigin, out hitPlane);
            //return hitPlane && GeometryUtility.TestPlanesAABB(frustumPlanes, shadowBounds);
            return hitPlane && BoundsIntersectsFrustum(frustumPlanes, shadowBounds);
        }

        /// <summary>
        /// Test for bounds visible in frustum
        /// </summary>
        /// <param name="planes"></param>
        /// <param name="bounds"></param>
        /// <returns></returns>
        public static bool BoundsIntersectsFrustum(Plane[] planes, Bounds bounds)
        {
            var center = bounds.center;
            var extents = bounds.extents;

            for (int i = 0; i <= planes.Length -1; i++)
            {
                Vector3 planeNormal = planes[i].normal;
                float planeDistance = planes[i].distance;

                Vector3 abs = new Vector3(Mathf.Abs(planeNormal.x), Mathf.Abs(planeNormal.y), Mathf.Abs(planeNormal.z));             
                float r = extents.x * abs.x + extents.y * abs.y + extents.z * abs.z;
                float s = planeNormal.x * center.x + planeNormal.y * center.y + planeNormal.z * center.z;
                if (s + r < -planeDistance)
                {
                    return false;
                }
            }
            return true;
        }

        /// <summary>
        /// Get the projected bounds of the shadow of an object
        /// </summary>
        /// <param name="objectBounds"></param>
        /// <param name="lightDirection"></param>
        /// <param name="planeOrigin"></param>
        /// <param name="hitPlane"></param>
        /// <returns></returns>
        public static Bounds GetShadowBounds(Bounds objectBounds, Vector3 lightDirection, Vector3 planeOrigin, out bool hitPlane)
        {
            Ray p0 = new Ray(new Vector3(objectBounds.min.x, objectBounds.max.y, objectBounds.min.z), lightDirection);
            Ray p1 = new Ray(new Vector3(objectBounds.min.x, objectBounds.max.y, objectBounds.max.z), lightDirection);
            Ray p2 = new Ray(new Vector3(objectBounds.max.x, objectBounds.max.y, objectBounds.min.z), lightDirection);
            Ray p3 = new Ray(objectBounds.max, lightDirection);

            Vector3 hitPoint;
            hitPlane = false;

            if (IntersectPlane(p0, planeOrigin, out hitPoint))
            {
                objectBounds.Encapsulate(hitPoint);
                hitPlane = true;
            }

            if (IntersectPlane(p1, planeOrigin, out hitPoint))
            {
                objectBounds.Encapsulate(hitPoint);
                hitPlane = true;
            }

            if (IntersectPlane(p2, planeOrigin, out hitPoint))
            {
                objectBounds.Encapsulate(hitPoint);
                hitPlane = true;
            }

            if (IntersectPlane(p3, planeOrigin, out hitPoint))
            {
                objectBounds.Encapsulate(hitPoint);
                hitPlane = true;
            }
            return objectBounds;
        }

        public static bool IntersectPlane(Ray ray, Vector3 planeOrigin, out Vector3 hitPoint)
        {
            Vector3 planeNormal = -Vector3.up;
            float denominator = Vector3.Dot(ray.direction, planeNormal);
            if (denominator > 0.00001f)
            {
                float t = Vector3.Dot(planeOrigin - ray.origin, planeNormal) / denominator;
                hitPoint = ray.origin + ray.direction * t;
                return true;
            }

            hitPoint = Vector3.zero;
            return false;
        }
    }
}