using UnityEngine;
using UnityEngine.UI;

[ExecuteAlways]
/// <summary>
/// uGUIの2Passを実装するために、RenderTextureを自前で流し込むクラス
/// </summary>
public class Test2PassController : MonoBehaviour
{
    [SerializeField] private Image targetImage;
    [SerializeField] private Shader targetShader;
    [SerializeField, Range(0f, 0.5f)] private float ignoreRange; //画像描画の無視範囲を設定する

    private Texture2D sourceTexture;
    private Material createdMaterial;
    private RenderTexture tmpRenderTexture;

    //変更があったときだけ更新するためのキャッシュ
    private float _lastIgnoreRange;

    /// <summary>
    /// 初期化処理の必要性がある場合trueを返す
    /// </summary>
    private bool NeedsInitialization => sourceTexture == null || sourceTexture != targetImage.sprite.texture || 
                                        createdMaterial == null || targetImage.material != createdMaterial ||
                                        tmpRenderTexture == null;

    /// <summary>
    /// ignoreRangeのパラメータに変化があったかチェック
    /// </summary>
    private bool CahngedParameter => !Mathf.Approximately(ignoreRange, _lastIgnoreRange);

    /// <summary>
    /// コンポーネントをアタッチした時一度だけ呼ばれる
    /// </summary>
    private void Reset()
    {
        if(targetImage == null)
        {
            targetImage = GetComponent<Image>();
        }
    }

    /// <summary>
    /// 初期化処理
    /// </summary>
    private void Init()
    {
        Release();

        sourceTexture = targetImage.sprite.texture;
        createdMaterial = new (targetShader);
        createdMaterial.hideFlags = HideFlags.DontSave;
        targetImage.material = createdMaterial;            

        //Imageとアタッチされた画像と同じサイズでRenderTextureを作成
        tmpRenderTexture = new RenderTexture(sourceTexture.width, sourceTexture.height, 0);

        //事前にBlitで更新される予定のtmpRenderTextureを、_CustomTexの名前でシェーダー側と紐づけておく
        //後はCanvasRender内の処理で自動的にPass0を実行させて、その_CustomTexを画像に反映させる
        targetImage.material.SetTexture("_CustomTex", tmpRenderTexture);

        //初期化後は問答無用でUpdateTextureを呼ぶ
        UpdateTexture();
    }

    /// <summary>
    /// 最初にUnityが1回だけ呼ぶ処理
    /// </summary>
    private void Start()
    {
        if (NeedsInitialization)
        {
            Init();
        }
    }    

    /// <summary>
    /// Unityから毎フレーム呼ばれる処理
    /// </summary>
    private void Update()
    {
        if (targetImage == null || targetImage.sprite == null || targetShader == null)
        {
            return;    
        }

        if (NeedsInitialization)
        {
            Init();         
        }
        else if(CahngedParameter)
        {
            UpdateTexture();
        }
    }

    /// <summary>
    /// 対象のシェーダーの2Passを実行させる
    /// </summary>
    private void UpdateTexture()
    {
        _lastIgnoreRange = ignoreRange;

        //非表示領域をセット
        createdMaterial.SetFloat("_IgnoreRange", ignoreRange);
        
        //第1引数の画像を元に、Blitでシェーダー内のPass1を実行させ、結果を第2引数に書き込む
        //Blitの仕様上、この書き込み処理は、第3引数のMaterial内の_MainTexを通して行われる
        Graphics.Blit(sourceTexture, tmpRenderTexture, createdMaterial, 1);    
    }

    /// <summary>
    /// 作成したRenderTextureやMaterialのメモリを解放する
    /// </summary>
    private void Release()
    {
        if (tmpRenderTexture != null)
        {
            tmpRenderTexture.Release();
        }
        if (createdMaterial != null)
        {
            DestroyImmediate(createdMaterial);  
        }         
    }

    /// <summary>
    /// このGameObjectを非表示にした時の処理
    /// </summary>
    private void OnDisable()
    {
        Release();
    }
}