﻿/*
MIT License

Copyright(c) 2019 Mitchel Thompson
www.angryarugula.com

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Arugula.Math;
#if UNITY_EDITOR
using UnityEditor;
#endif

namespace Arugula.Prototypical
{
    public class VolumetricSensor : Sensor
    {
        //editor shenanigans
        static bool _gizmosReady = false;
        static Mesh _cubeMesh;
        static Mesh _cylinderMesh;
        static Mesh _sphereMesh;
        static Color _emptyColor = new Color(1, 1, 1, 0.25f);
        static Color _activeColor = new Color(0, 1f, 1f, 0.25f);

        static void InitGizmos()
        {
            if (_gizmosReady)
                return;

            GameObject prim = GameObject.CreatePrimitive(PrimitiveType.Cube);
            _cubeMesh = prim.GetComponent<MeshFilter>().sharedMesh;
            if (Application.isPlaying)
                Destroy(prim);
            else
                DestroyImmediate(prim);

            prim = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
            _cylinderMesh = prim.GetComponent<MeshFilter>().sharedMesh;
            if (Application.isPlaying)
                Destroy(prim);
            else
                DestroyImmediate(prim);

            prim = GameObject.CreatePrimitive(PrimitiveType.Sphere);
            _sphereMesh = prim.GetComponent<MeshFilter>().sharedMesh;
            if (Application.isPlaying)
                Destroy(prim);
            else
                DestroyImmediate(prim);

            _gizmosReady = true;
        }


        public enum Shape
        {
            Plane, Sphere, Cube, Cone, Cylinder, Arc
        }

        public Shape shape;
        public Vector3 axis = Vector3.forward;
        [Range(0, 360)]
        public float angle = 30;
        public float radius = 1;
        public Vector3 min = Vector3.zero;
        public Vector3 max = Vector3.one;
        public Unit units = Unit.Meters;

        public float MinDistance
        {
            get
            {
                return min.z;
            }
            set
            {
                min.z = value;
            }
        }

        public float MaxDistance
        {
            get
            {
                return max.z;
            }
            set
            {
                max.z = value;
            }
        }

        public float Floor
        {
            get
            {
                return min.y;
            }
            set
            {
                min.y = value;
            }
        }

        public float Ceiling
        {
            get
            {
                return max.y;
            }
            set
            {
                max.y = value;
            }
        }

        private void Start()
        {
            InitGizmos();
        }

        public override bool Check()
        {
            DetectedObjects.Clear();
            switch (shape)
            {
                case Shape.Plane:
                    CheckPlane();
                    break;
                case Shape.Sphere:
                    CheckSphere();
                    break;
                case Shape.Cone:
                    CheckCone();
                    break;
                case Shape.Cylinder:
                    CheckCylinder();
                    break;
                case Shape.Cube:
                    CheckCube();
                    break;
                case Shape.Arc:
                    CheckArc();
                    break;

            }

            Checked();

            return DetectedObjects.Count > 0;
        }

        bool CheckPlane()
        {
            Vector3 pos = transform.position;
            Vector3 dir = transform.TransformDirection(axis.normalized);
            Plane p = new Plane(dir, pos + (dir * min.z));
            DetectedObjects.Clear();
            foreach (var s in MaskedSensibles)
            {
                if (p.GetSide(s.Position))
                {
                    if (p.GetDistanceToPoint(s.Position) + min.z/* - s.Size*/ < max.z)
                    {
                        DetectedObjects.Add(s);
                    }

                }
            }

            return DetectedObjects.Count > 0;
        }

        bool CheckSphere()
        {
            Vector3 pos = transform.position;
            Vector3 dir = transform.TransformDirection(axis);
            foreach (var s in MaskedSensibles)
            {
                float dist = Vector3.Distance(s.Position, pos) /*- s.Size*/;
                if (dist < max.z && dist > min.z)
                {
                    float ang = Vector3.Angle(s.Position - pos, dir);
                    if (angle == 0 || angle == 360 || ang <= angle / 2)
                    {
                        DetectedObjects.Add(s);
                    }

                }

            }

            return DetectedObjects.Count > 0;
        }

        bool CheckCone()
        {
            Vector3 pos = transform.position;
            Vector3 dir = transform.TransformDirection(axis);
            Plane nearPlane = new Plane(dir, pos + (dir * min.z));
            Plane farPlane = new Plane(-dir, pos + (dir * max.z));

            foreach (var s in MaskedSensibles)
            {
                if (nearPlane.GetSide(s.Position) && farPlane.GetSide(s.Position))
                {
                    //check angle
                    if (angle == 0 || Vector3.Angle(dir, s.Position - pos) < angle / 2)
                        DetectedObjects.Add(s);
                }
            }

            return DetectedObjects.Count > 0;
        }

        bool CheckCylinder()
        {
            Vector3 pos = transform.position;
            Vector3 dir = transform.TransformDirection(axis);
            Plane nearPlane = new Plane(dir, pos + (dir * min.z));
            Plane farPlane = new Plane(-dir, pos + (dir * max.z));

            foreach (var s in MaskedSensibles)
            {
                if (nearPlane.GetSide(s.Position) && farPlane.GetSide(s.Position))
                {
                    float distance = Vector3.Cross(dir, s.Position - pos).magnitude;
                    if (distance < radius)
                        DetectedObjects.Add(s);
                }


            }
            //float distance = Vector3.Cross(ray.direction, point - ray.origin).magnitude;


            return DetectedObjects.Count > 0;
        }

        bool CheckCube()
        {
            Vector3 pos = transform.position;

            Plane[] planes = new Plane[6];
            planes[0] = new Plane(transform.forward, transform.position + transform.rotation * (Vector3.forward * min.z));
            planes[1] = new Plane(-transform.forward, transform.position + transform.rotation * (Vector3.forward * max.z));
            planes[2] = new Plane(transform.right, transform.position + transform.rotation * (Vector3.right * min.x));
            planes[3] = new Plane(-transform.right, transform.position + transform.rotation * (Vector3.right * max.x));
            planes[4] = new Plane(transform.up, transform.position + transform.rotation * (Vector3.up * min.y));
            planes[5] = new Plane(-transform.up, transform.position + transform.rotation * (Vector3.up * max.y));

            foreach (var s in MaskedSensibles)
            {
                int i = 0;
                foreach (var p in planes)
                {
                    if (!p.GetSide(s.Position))
                        break;
                    i++;
                }

                if (i == 6)
                    DetectedObjects.Add(s);
            }

            return DetectedObjects.Count > 0;
        }

        bool CheckArc()
        {

            Plane floor = new Plane(transform.up, transform.position + transform.rotation * Vector3.up * min.y);
            Plane ceiling = new Plane(-transform.up, transform.position + transform.rotation * Vector3.up * max.y);

            foreach (var s in MaskedSensibles)
            {
                if (floor.GetSide(s.Position) && ceiling.GetSide(s.Position))
                {
                    Vector3 localFlatPos = s.Position - transform.position;
                    localFlatPos = Quaternion.Inverse(transform.rotation) * localFlatPos;
                    localFlatPos = localFlatPos.GetFlattened();
                    if (localFlatPos.magnitude < max.z && localFlatPos.magnitude > min.z)
                    {
                        //angle check
                        if (Vector3.Angle(Vector3.forward, localFlatPos) <= angle / 2)
                            DetectedObjects.Add(s);
                    }
                }
            }

            return DetectedObjects.Count > 0;
        }


#if UNITY_EDITOR
        private void OnDrawGizmos()
        {
            InitGizmos();

            if (!drawGizmos)
                return;

            if (Application.isEditor && !Application.isPlaying)
            {
                Check();
                foreach (var d in DetectedObjects)
                {
                    if (d == null)
                        continue;
                    d.DrawGizmos();
                }
            }

            Gizmos.color = DetectedObjects.Count > 0 ? _activeColor : _emptyColor;
            var handleColor = Gizmos.color;
            handleColor.a /= 4;

            Vector3 dir = transform.TransformDirection(axis.normalized);
            Quaternion rot = Quaternion.LookRotation(dir, transform.up);
            switch (shape)
            {
                case Shape.Sphere:
                    {
                        if (angle == 0 || angle == 360)
                        {
                            Gizmos.DrawMesh(_sphereMesh, transform.position, transform.rotation, Vector3.one * MaxDistance * 2);
                            //Gizmos.DrawSphere(transform.position, max.z);
                            Gizmos.color = new Color(0, 0, 0, 0.25f);
                            Gizmos.DrawMesh(_sphereMesh, transform.position, transform.rotation, Vector3.one * MinDistance * 2);
                            //Gizmos.DrawSphere(transform.position, min.z);
                        }
                        else
                        {
                            Vector3 a = transform.position;
                            Gizmos.DrawWireSphere(a, max.z);

                            Handles.color = handleColor;
                            Handles.DrawSolidArc(a, rot * Vector3.up, Quaternion.AngleAxis(angle / -2, rot * Vector3.up) * dir.normalized, angle, max.z);
                            Handles.DrawSolidArc(a, rot * Vector3.right, Quaternion.AngleAxis(angle / -2, rot * Vector3.right) * dir.normalized, angle, max.z);
                            Handles.color = new Color(0, 0, 0, 0.125f);
                            Handles.DrawSolidArc(a, rot * Vector3.up, Quaternion.AngleAxis(angle / -2, rot * Vector3.up) * dir.normalized, angle, min.z);
                            Handles.DrawSolidArc(a, rot * Vector3.right, Quaternion.AngleAxis(angle / -2, rot * Vector3.right) * dir.normalized, angle, min.z);
                        }



                    }

                    break;
                case Shape.Plane:
                    {
                        Vector3 a = transform.position + (dir * min.z);
                        Vector3 b = transform.position + (dir * max.z);
                        Gizmos.DrawMesh(_cubeMesh, a, rot, new Vector3(1, 1, 0));
                        Gizmos.DrawMesh(_cubeMesh, b, rot, new Vector3(1, 1, 0));
                        Gizmos.DrawLine(a, b);
                        Gizmos.DrawLine(b, b + rot * Quaternion.Euler(0, 25, 0) * Vector3.forward * -0.2f * (max.z - min.z));
                        Gizmos.DrawLine(b, b + rot * Quaternion.Euler(0, -25, 0) * Vector3.forward * -0.2f * (max.z - min.z));
                    }
                    break;
                case Shape.Cone:

                    {
                        Vector3 a = transform.position;
                        Vector3 b = a + (dir * max.z);
                        float ex = Mathf.Abs((1f / Mathf.Cos((angle / 2) * Mathf.Deg2Rad)));
                        Vector3 ca = a + (rot * Quaternion.Euler(0, angle / 2, 0) * Vector3.forward * min.z * ex);
                        Vector3 da = a + (rot * Quaternion.Euler(0, angle / -2, 0) * Vector3.forward * min.z * ex);
                        Vector3 cb = a + (rot * Quaternion.Euler(0, angle / 2, 0) * Vector3.forward * max.z * ex);
                        Vector3 db = a + (rot * Quaternion.Euler(0, angle / -2, 0) * Vector3.forward * max.z * ex);
                        Gizmos.DrawLine(ca, cb);
                        Gizmos.DrawLine(da, db);

                        Gizmos.DrawLine(a + Quaternion.AngleAxis(90, dir) * (ca - a), a + Quaternion.AngleAxis(90, dir) * (cb - a));
                        Gizmos.DrawLine(a + Quaternion.AngleAxis(90, dir) * (da - a), a + Quaternion.AngleAxis(90, dir) * (db - a));

                        rot *= Quaternion.Euler(90, 0, 0);
                        float radius = Vector3.Distance(ca, da);
                        Gizmos.DrawMesh(_cylinderMesh, a + (dir * min.z), rot, new Vector3(radius, 0, radius));
                        radius = Vector3.Distance(cb, db);
                        Gizmos.DrawMesh(_cylinderMesh, b, rot, new Vector3(radius, 0, radius));
                    }

                    break;
                case Shape.Cylinder:
                    {
                        Vector3 a = transform.position;
                        rot *= Quaternion.Euler(90, 0, 0);
                        Gizmos.DrawMesh(_cylinderMesh, a + (dir * (Mathf.Lerp(min.z, max.z, 0.5f))), rot, new Vector3(radius * 2, (max.z - min.z) / 2, radius * 2));
                    }
                    break;
                case Shape.Cube:
                    {
                        Vector3 pos = transform.position + transform.rotation * Vector3.Lerp(min, max, 0.5f);
                        Gizmos.DrawMesh(_cubeMesh, pos, transform.rotation, max - min);
                    }
                    break;
                case Shape.Arc:
                    //if (angle == 0 || angle == 360)
                    //{
                    //    Gizmos.DrawSphere(transform.position, max.z);
                    //    Gizmos.color = new Color(0, 0, 0, 0.25f);
                    //    Gizmos.DrawSphere(transform.position, min.z);
                    //}
                    //else
                    {
                        Vector3 a = transform.position;
                        Vector3 floor = a + (transform.rotation * Vector3.up * min.y);
                        Vector3 ceiling = a + (transform.rotation * Vector3.up * max.y);
                        Gizmos.color /= 3;
                        Gizmos.DrawMesh(_cylinderMesh, Vector3.Lerp(floor, ceiling, 0.5f), transform.rotation, new Vector3((max.z) * 2, (max.y - min.y) / 2, (max.z) * 2));
                        //Gizmos.DrawWireSphere(a, max.z);
                        Handles.color = handleColor;

                        Handles.DrawSolidArc(floor, transform.up, Quaternion.AngleAxis(angle / -2, transform.up) * transform.forward, angle, max.z);
                        Handles.DrawSolidArc(ceiling, transform.up, Quaternion.AngleAxis(angle / -2, transform.up) * transform.forward, angle, max.z);
                        //Handles.DrawSolidArc(floor, rot * Vector3.up, Quaternion.AngleAxis(angle / -2, rot * Vector3.up) * dir.normalized, angle, max.z);
                        Handles.color = new Color(0, 0, 0, 0.125f);
                        Handles.DrawSolidArc(floor, transform.up, Quaternion.AngleAxis(angle / -2, transform.up) * transform.forward, angle, min.z);
                        Handles.DrawSolidArc(ceiling, transform.up, Quaternion.AngleAxis(angle / -2, transform.up) * transform.forward, angle, min.z);
                        //Handles.DrawSolidArc(a, rot * Vector3.up, Quaternion.AngleAxis(angle / -2, rot * Vector3.up) * dir.normalized, angle, min.z);

                    }
                    break;
            }
        }
#endif
    }

}
