using OpenTS2.Content;
using OpenTS2.Engine;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(Camera))]
public class CameraReflection : MonoBehaviour
{
    [GameProperty(false)]
    public static float ReflectionResolutionFactor = 0.5f;
    public Color ReflectionSkyColor;
    public static CameraReflection Instance;
    public Camera reflectionCamera;
    private Camera _camera;
    public RenderTexture ReflectionTexture;
    public float m_ClipPlaneOffset = 0.07f;
    public GameObject ReflectionSurface;
    private void Awake()
    {
        Instance = this;
        ReflectionTexture = new RenderTexture((int)(Screen.width * ReflectionResolutionFactor), (int)(Screen.height * ReflectionResolutionFactor), 24);
        var reflectionCameraGameObject = new GameObject("Reflection Camera");
        reflectionCamera = reflectionCameraGameObject.AddComponent<Camera>();
        _camera = GetComponent<Camera>();
        reflectionCamera.CopyFrom(_camera);
        reflectionCamera.allowMSAA = false;
        reflectionCamera.cullingMask = 1 << Layers.Default | 1 << Layers.Terrain;
        reflectionCamera.targetTexture = ReflectionTexture;
        reflectionCamera.enabled = false;
        reflectionCamera.backgroundColor = ReflectionSkyColor;
    }
    private void Update()
    {
        var surfacePos = ReflectionSurface.transform.position;
        // Render reflection
        // Reflect camera around reflection plane
        float d = -Vector3.Dot(Vector3.up, surfacePos) - m_ClipPlaneOffset;
        Vector4 reflectionPlane = new Vector4(0, 1, 0, d);

        Matrix4x4 reflection = Matrix4x4.zero;
        CalculateReflectionMatrix(ref reflection, reflectionPlane);
        Vector3 oldpos = _camera.transform.position;
        Vector3 newpos = reflection.MultiplyPoint(oldpos);
        reflectionCamera.worldToCameraMatrix = _camera.worldToCameraMatrix * reflection;

        // Setup oblique projection matrix so that near plane is our reflection
        // plane. This way we clip everything below/above it for free.
        Vector4 clipPlane = CameraSpacePlane(reflectionCamera, surfacePos, Vector3.up, 1.0f);
        Matrix4x4 projection = _camera.projectionMatrix;
        CalculateObliqueMatrix(ref projection, clipPlane);
        reflectionCamera.projectionMatrix = projection;
        GL.invertCulling = true;
        reflectionCamera.transform.position = newpos;
        Vector3 euler = _camera.transform.eulerAngles;
        reflectionCamera.transform.eulerAngles = new Vector3(0, euler.y, euler.z);
        reflectionCamera.Render();
        reflectionCamera.transform.position = oldpos;
        GL.invertCulling = false;
    }

    private static float sgn(float a)
    {
        if (a > 0.0f) return 1.0f;
        if (a < 0.0f) return -1.0f;
        return 0.0f;
    }
    private static void CalculateObliqueMatrix(ref Matrix4x4 projection, Vector4 clipPlane)
    {
        Vector4 q = projection.inverse * new Vector4(
            sgn(clipPlane.x),
            sgn(clipPlane.y),
            1.0f,
            1.0f
        );
        Vector4 c = clipPlane * (2.0F / (Vector4.Dot(clipPlane, q)));
        // third row = clip plane - fourth row
        projection[2] = c.x - projection[3];
        projection[6] = c.y - projection[7];
        projection[10] = c.z - projection[11];
        projection[14] = c.w - projection[15];
    }

    // Calculates reflection matrix around the given plane
    private static void CalculateReflectionMatrix(ref Matrix4x4 reflectionMat, Vector4 plane)
    {
        reflectionMat.m00 = (1F - 2F * plane[0] * plane[0]);
        reflectionMat.m01 = (-2F * plane[0] * plane[1]);
        reflectionMat.m02 = (-2F * plane[0] * plane[2]);
        reflectionMat.m03 = (-2F * plane[3] * plane[0]);

        reflectionMat.m10 = (-2F * plane[1] * plane[0]);
        reflectionMat.m11 = (1F - 2F * plane[1] * plane[1]);
        reflectionMat.m12 = (-2F * plane[1] * plane[2]);
        reflectionMat.m13 = (-2F * plane[3] * plane[1]);

        reflectionMat.m20 = (-2F * plane[2] * plane[0]);
        reflectionMat.m21 = (-2F * plane[2] * plane[1]);
        reflectionMat.m22 = (1F - 2F * plane[2] * plane[2]);
        reflectionMat.m23 = (-2F * plane[3] * plane[2]);

        reflectionMat.m30 = 0F;
        reflectionMat.m31 = 0F;
        reflectionMat.m32 = 0F;
        reflectionMat.m33 = 1F;
    }
    private Vector4 CameraSpacePlane(Camera cam, Vector3 pos, Vector3 normal, float sideSign)
    {
        Vector3 offsetPos = pos + normal * m_ClipPlaneOffset;
        Matrix4x4 m = cam.worldToCameraMatrix;
        Vector3 cpos = m.MultiplyPoint(offsetPos);
        Vector3 cnormal = m.MultiplyVector(normal).normalized * sideSign;
        return new Vector4(cnormal.x, cnormal.y, cnormal.z, -Vector3.Dot(cpos, cnormal));
    }
}