using System;
using UnityEngine;
using UnityEngine.Rendering;

namespace NAK.BetterShadowClone;

public struct MeshShadowClone : IShadowClone
{
    // We technically don't need a clone mesh for MeshRenderer shadow clone handling,
    // but as the shadows are also utilized for UI culling, we need to have a clone mesh.
    // If we don't stick with UI culling, we can just set the shadowCastingMode to ShadowsOnly when player camera renders.
    
    // lame 2 frame init stuff
    private const int FrameInitCount = 0;
    private int _frameInitCounter;
    private bool _hasInitialized;
    
    // shadow is used to cull ui, clone always exists
    private readonly bool _shouldCastShadows;
    private readonly MeshRenderer _mainMesh;
    private readonly MeshRenderer _shadowMesh;
    private readonly MeshFilter _shadowMeshFilter;

    // material copying (unity is shit)
    private bool _hasShadowMaterials;
    private readonly Material[] _shadowMaterials;
    private readonly MaterialPropertyBlock _shadowMaterialBlock;
    
    #region IShadowClone Methods
    
    public bool IsValid => _mainMesh != null && _shadowMesh != null;
    
    public MeshShadowClone(MeshRenderer meshRenderer)
    {
        _mainMesh = meshRenderer;
        MeshFilter _mainMeshFilter = meshRenderer.GetComponent<MeshFilter>();
        
        if (_mainMesh == null
            || _mainMesh.sharedMaterials == null
            || _mainMesh.sharedMaterials.Length == 0
            || _mainMeshFilter == null
            || _mainMeshFilter.sharedMesh == null)
        {
            Dispose();
            return; // no mesh!
        }
        
        _shouldCastShadows = _mainMesh.shadowCastingMode != ShadowCastingMode.Off;
        _mainMesh.shadowCastingMode = ShadowCastingMode.Off; // visual mesh doesn't cast shadows

        (_shadowMesh, _shadowMeshFilter) = ShadowCloneManager.InstantiateShadowClone(_mainMesh);
        _shadowMesh.forceRenderingOff = true;
        
        // material copying shit
        int materialCount = _mainMesh.sharedMaterials.Length;
        Material shadowMaterial = ShadowCloneHelper.shadowMaterial;
        
        _shadowMaterialBlock = new MaterialPropertyBlock();
        _shadowMaterials = new Material[materialCount];
        for (int i = 0; i < materialCount; i++) _shadowMaterials[i] = shadowMaterial;
    }
    
    public bool Process()
    {
        bool shouldRender = _mainMesh.enabled && _mainMesh.gameObject.activeInHierarchy;
        
        // copying behaviour of SkinnedShadowClone, to visually be the same when a mesh toggles
        if (!shouldRender)
        {
            _frameInitCounter = 0;
            _hasInitialized = false;
            _shadowMesh.forceRenderingOff = true;
            return false;
        }
        
        if (_frameInitCounter >= FrameInitCount)
        {
            if (_hasInitialized) 
                return true;
            
            _hasInitialized = true;
            return true;
        }
        
        _frameInitCounter++;
        return false;
    }

    public void RenderForShadow()
    {
        _shadowMesh.shadowCastingMode = ShadowCastingMode.ShadowsOnly;
        _shadowMesh.forceRenderingOff = !_shouldCastShadows;
        
        // shadow casting needs clone to have original materials (uv discard)
        // we also want to respect material swaps... but this is fucking slow :(

        if (!ShadowCloneManager.s_CopyMaterialsToShadow)
            return;

        if (_hasShadowMaterials)
        {
            // NOTE: will not handle material swaps unless Avatar Overrender Ui is on
            _shadowMesh.sharedMaterials = _mainMesh.sharedMaterials;
            _hasShadowMaterials = false;
        }
        
        UpdateCloneMaterialProperties();
    }

    public void RenderForUiCulling()
    {
        _shadowMesh.shadowCastingMode = ShadowCastingMode.On;
        _shadowMesh.forceRenderingOff = false;
        
        // UI culling needs clone to have write-to-depth shader
        if (_hasShadowMaterials) return;
        _shadowMesh.sharedMaterials = _shadowMaterials;
        _hasShadowMaterials = true;
        
        // Not needed- MaterialPropertyBlock applied to renderer in RenderForShadow
        //UpdateCloneMaterialProperties();
    }
    
    public void Dispose()
    {
        if (_shadowMesh == null) 
            return; // uh oh
        
        // Cleanup instanced Mesh & Materials
        GameObject shadowMeshObject = _shadowMesh.gameObject;
        UnityEngine.Object.Destroy(_shadowMeshFilter.sharedMesh);
        UnityEngine.Object.Destroy(_shadowMeshFilter);
        if (!_hasShadowMaterials)
        {
            var materials = _shadowMesh.sharedMaterials;
            foreach (Material mat in materials) UnityEngine.Object.Destroy(mat);
        }
        UnityEngine.Object.Destroy(_shadowMesh);
        UnityEngine.Object.Destroy(shadowMeshObject);
    }
    
    #endregion

    #region Private Methods
    
    private void UpdateCloneMaterialProperties()
    {
        // copy material properties to shadow clone materials
        _mainMesh.GetPropertyBlock(_shadowMaterialBlock);
        _shadowMesh.SetPropertyBlock(_shadowMaterialBlock);
    }
    
    #endregion
}