/*    ____            _           _    __        __              _                            
 *   |  _ \ _ __ ___ (_) ___  ___| |_  \ \      / /_ _ _ __   __| | ___ _ __                  
 *   | |_) | '__/ _ \| |/ _ \/ __| __|  \ \ /\ / / _` | '_ \ / _` |/ _ \ '__|                 
 *   |  __/| | | (_) | |  __/ (__| |_    \ V  V / (_| | | | | (_| |  __/ |                    
 *   |_|   |_|  \___// |\___|\___|\__|    \_/\_/ \__,_|_| |_|\__,_|\___|_|                    
 *                 |__/
 * 
 * 
 * ENJMIN - Hits PlayTime 2013
 * 
 * This file is part of the Wander project, 
 * created and developped by :
 *   Louis Schnellbach
 *
 */
using UnityEngine;
using System.Collections;
using System.Collections.Generic;

[ExecuteInEditMode]
[RequireComponent(typeof(Camera))]
public class CameraPostProcess : MonoBehaviour
{
    RenderTexture m_finalTex;
    List<Source> m_list = new List<Source> ();
    public Shader shader;
    private Material m_materialPostProcess = null;
    private Material m_materialInitTexture = null;
    private int m_width;
    private int m_height;
    float CAMERA_NEAR;
    float CAMERA_FAR;
    float CAMERA_FOV;
    float CAMERA_ASPECT_RATIO;

    void Start ()
    {
        camera.depthTextureMode = DepthTextureMode.DepthNormals;

        // Disable if we don't support image effects
        if (!SystemInfo.supportsImageEffects) {
            enabled = false;
            return;
        }

        // Disable the image effect if the shader can't
        // run on the users graphics card
        if (!shader || !shader.isSupported)
            enabled = false;
    }

    //Free texture
    void OnDisable ()
    {
        m_list.Clear ();
        DestroyImmediate (m_finalTex);
        DestroyImmediate (m_materialPostProcess);
        DestroyImmediate (m_materialInitTexture);
    }

    void OnEnable ()
    {
        if (m_materialPostProcess == null) {
            m_materialPostProcess = new Material (shader);
            m_materialPostProcess.hideFlags = HideFlags.HideAndDontSave;
        }

        if (m_materialInitTexture == null) {
            m_materialInitTexture = new Material (Shader.Find ("Xleek/BlackTexture"));
            m_materialInitTexture.hideFlags = HideFlags.HideAndDontSave;
        }

        m_list.Clear ();
        camera.depthTextureMode = DepthTextureMode.DepthNormals;

        m_finalTex = new RenderTexture ((int)camera.pixelWidth, (int)camera.pixelHeight, 0);
    }

    public void AddSource (Source obj)
    {
        m_list.Add (obj);
        //Debug.Log ("Added Source. (" + m_list.Count.ToString () + ")");
    }

    public void RemoveSource (Source obj)
    {
        m_list.Remove (obj);
        //Debug.Log ("Removed Source. (" + m_list.Count.ToString () + ")");
    }


    // Called by camera to apply image effect
    void OnRenderImage (RenderTexture source, RenderTexture destination)
    {				
        //Clear FinalTexture before each display
        Graphics.Blit (null, m_finalTex, m_materialInitTexture);
        // Setup the texture and floating point values in the shader
        m_materialPostProcess.SetTexture ("_MainTex", source);

        //Send to shader the frustum corner
        m_materialPostProcess.SetMatrix ("_FrustumCornersWS", frustumCorner ());
        //And the camera position (computed once)
        m_materialPostProcess.SetVector ("_CameraWS", camera.transform.position);

        //Start rendering each source
        m_list.ForEach (delegate(Source src)
                {
                if (src != null) {
                m_materialPostProcess.SetTexture ("_FinalTex", m_finalTex);
                m_materialPostProcess.SetVector ("_Position", src.Position);
                m_materialPostProcess.SetColor ("_SourceColor", src.SourceColor);
                m_materialPostProcess.SetFloat ("_Radius", src.Radius);
                m_materialPostProcess.SetFloat ("_MaxRadius", src.MaxRadius);
                m_materialPostProcess.SetFloat ("_VisibleWidth", src.VisibleWidth);
                m_materialPostProcess.SetFloat ("_HaloWidth", src.HaloWidth);

                CustomGraphicBlit (source, m_finalTex, m_materialPostProcess);
                }
                });

        //Final (at least !) blit
        Graphics.Blit (m_finalTex, destination);
    }

    void CustomGraphicBlit (RenderTexture source, RenderTexture destination, Material material)
    {
        RenderTexture.active = destination;

        GL.PushMatrix ();
        GL.LoadOrtho ();	

        material.SetPass (0);
        GL.Begin (GL.QUADS);

        GL.MultiTexCoord2 (0, 0.0f, 0.0f); 
        GL.Vertex3 (0.0f, 0.0f, 3.0f); // BL

        GL.MultiTexCoord2 (0, 1.0f, 0.0f); 
        GL.Vertex3 (1.0f, 0.0f, 2.0f); // BR

        GL.MultiTexCoord2 (0, 1.0f, 1.0f); 
        GL.Vertex3 (1.0f, 1.0f, 1.0f); // TR

        GL.MultiTexCoord2 (0, 0.0f, 1.0f); 
        GL.Vertex3 (0.0f, 1.0f, 0.0f); // TL

        GL.End ();
        GL.PopMatrix ();
    }

    //Avoid to write this in the render method
    Matrix4x4 frustumCorner ()
    {
        Matrix4x4 frustumCorners = Matrix4x4.identity;
        CAMERA_NEAR = camera.nearClipPlane;
        CAMERA_FAR = camera.farClipPlane;
        CAMERA_FOV = camera.fieldOfView;
        CAMERA_ASPECT_RATIO = camera.aspect;

        float fovWHalf = CAMERA_FOV * 0.5f;

        Vector3 toRight = camera.transform.right * CAMERA_NEAR * Mathf.Tan (fovWHalf * Mathf.Deg2Rad) * CAMERA_ASPECT_RATIO;
        Vector3 toTop = camera.transform.up * CAMERA_NEAR * Mathf.Tan (fovWHalf * Mathf.Deg2Rad);

        Vector3 topLeft = (camera.transform.forward * CAMERA_NEAR - toRight + toTop);
        float CAMERA_SCALE = topLeft.magnitude * CAMERA_FAR / CAMERA_NEAR;	

        topLeft.Normalize ();
        topLeft *= CAMERA_SCALE;

        Vector3 topRight = (camera.transform.forward * CAMERA_NEAR + toRight + toTop);
        topRight.Normalize ();
        topRight *= CAMERA_SCALE;

        Vector3 bottomRight = (camera.transform.forward * CAMERA_NEAR + toRight - toTop);
        bottomRight.Normalize ();
        bottomRight *= CAMERA_SCALE;

        Vector3 bottomLeft = (camera.transform.forward * CAMERA_NEAR - toRight - toTop);
        bottomLeft.Normalize ();
        bottomLeft *= CAMERA_SCALE;

        frustumCorners.SetRow (0, topLeft); 
        frustumCorners.SetRow (1, topRight);		
        frustumCorners.SetRow (2, bottomRight);
        frustumCorners.SetRow (3, bottomLeft);

        return frustumCorners;
    }

}