using System;
using System.Collections;
using System.Collections.Generic;
using ComputeShaderUtility;
using UnityEngine;
using UnityEngine.Rendering;

public struct Droplet
{
    public Vector3 position;
    public Vector3 velocity;
    public uint enabled;
    public uint airborne;
}

public class BloodComputeShader : MonoBehaviour
{
    public static BloodComputeShader Instance { get; private set; }
    public int numParticles = 1000;

    public ComputeShader bloodCompute;
    public Mesh mesh;
    public Material instancedMaterial;
    public float size;

    public float scoreMult = 1.0f;

    public int activeParticles = 0;
    public long score = 0;

    public int mop1Clean = 0;
    public int mop2Clean = 0;

    public float squeakVolume = 0.0f;
    public AudioSource squeakPlayer;

    public float splatterVolume = 0.0f;
    public AudioSource splatterPlayer;

    public float RumbleAmount = 100;

    ComputeBuffer particleBuffer;
    ComputeBuffer positionBuffer;
    ComputeBuffer numParticlesConsumedBuffer;
    ComputeBuffer argsBuffer;

    ComputeBuffer freeParticleBuffer;

    const int InitDustKernel = 0;
    const int UpdateDustKernel = 1;
    const int CollectAllKernel = 2;
    AsyncGPUReadbackRequest readbackRequest;

    AsyncGPUReadbackRequest freeBloodReadRequest;

    public Mop mop1;
    public Mop mop2;

    public float CleanRadius = 2f;

    [SerializeField]
    uint bufferLookPointer = 0;

    private void Awake()
    {
        if (Instance != null)
        {
            Destroy(gameObject);
            return;
        }
        Instance = this;
    }
    void Start()
    {
        ComputeHelper.CreateStructuredBuffer<Droplet>(ref particleBuffer, numParticles);
        ComputeHelper.CreateStructuredBuffer<Vector4>(ref positionBuffer, numParticles);

        Droplet[] particles = new Droplet[numParticles];
        for (int i = 0; i < numParticles; i++)
        {

            particles[i] = new Droplet() { position = Vector3.zero, velocity = Vector3.zero, enabled = 0, airborne = 0 };

        }

        particleBuffer.SetData(particles);
        bloodCompute.SetBuffer(UpdateDustKernel, "particles", particleBuffer);
        bloodCompute.SetBuffer(UpdateDustKernel, "positions", positionBuffer);

        // Init dust particle positions
        bloodCompute.SetBuffer(InitDustKernel, "particles", particleBuffer);
        bloodCompute.SetBuffer(InitDustKernel, "positions", positionBuffer);

        
        bloodCompute.SetBuffer(CollectAllKernel, "particles", particleBuffer);
        bloodCompute.SetBuffer(CollectAllKernel, "positions", positionBuffer);

        bloodCompute.SetInt("numParticles", numParticles);

        // Create args buffer
        uint[] args = new uint[5];
        args[0] = (uint)mesh.GetIndexCount(0);
        args[1] = (uint)numParticles;
        args[2] = (uint)mesh.GetIndexStart(0);
        args[3] = (uint)mesh.GetBaseVertex(0);
        args[4] = 0; // offset

        argsBuffer = new ComputeBuffer(1, 5 * sizeof(uint), ComputeBufferType.IndirectArguments);
        argsBuffer.SetData(args);

        ComputeHelper.CreateStructuredBuffer<uint>(ref numParticlesConsumedBuffer, 3);
        numParticlesConsumedBuffer.SetData(new uint[] { 0, 0, 0 });
        bloodCompute.SetBuffer(UpdateDustKernel, "numParticlesConsumed", numParticlesConsumedBuffer);

        // Initialize with empty data
        ComputeHelper.CreateStructuredBuffer<uint>(ref freeParticleBuffer, numParticles);

        RequestAsyncReadback();

        instancedMaterial.SetBuffer("positionBuffer", positionBuffer);
    }

    void RequestAsyncReadback()
    {
        readbackRequest = AsyncGPUReadback.Request(numParticlesConsumedBuffer);
    }

    void RequestAllBloodStates()
    {
        freeBloodReadRequest = AsyncGPUReadback.Request(freeParticleBuffer);
    }

    // Update is called once per frame
    void Update()
    {
        if (mop1 == null || mop2 == null) return;

        bloodCompute.SetFloat("deltaTime", Time.deltaTime);
        bloodCompute.SetInt("numParticles", numParticles);
        bloodCompute.SetFloat("size", size);
        bloodCompute.SetFloat("gravity", 9.8f);
        bloodCompute.SetVector("mop1Pos", mop1.transform.position);
        bloodCompute.SetVector("mop2Pos", mop2.transform.position);
        bloodCompute.SetFloat("CleanRadius", CleanRadius);

        // if (Input.GetKeyUp(KeyCode.Alpha9)) {
        //     cleanAllBlood();
        // }

        if (readbackRequest.hasError)
        {
            RequestAllBloodStates();
            Debug.Log("Async readback error");
            return;
        }

        bool putBuffer = false;
        uint[] bufferData = {0,0,0};

        if (readbackRequest.done)
        {
            bufferData = readbackRequest.GetData<uint>().ToArray();

            // Blood cleaned
            if (bufferData[0] > 0 || bufferData[1] > 0)
            {
                // Debug.Log("Cleaned " + bufferData[0]);

                uint totalBloodCleaned = bufferData[0] + bufferData[1];

                activeParticles -= (int)totalBloodCleaned;

                score += totalBloodCleaned;

                // this doesnt exist but ok
                // float mappedRumble = Convert.ToSingle(bufferData[0]).Remap(0, RumbleAmount, 0, 0.1f);
                //RumbleManager.StartRumble(-1, 0, mappedRumble, 0.1f);

                squeakVolume += 0.3f;

                mop1Clean += (int)bufferData[0];
                mop2Clean += (int)bufferData[1];

                // Reset counter
                putBuffer = true;
                bufferData[0] = 0;
                bufferData[1] = 0;

            }

            // Blood hitting the floor
            if (bufferData[1] > 0)
            {
                splatterVolume += bufferData[1] / 10.0f;

                // Debug.Log("splat x" + bufferData[1]);

                putBuffer = true;
                bufferData[2] = 0;
            }

            RequestAsyncReadback();
        }

        if (putBuffer)
        {
            numParticlesConsumedBuffer.SetData(bufferData);
            bloodCompute.SetBuffer(UpdateDustKernel, "numParticlesConsumed", numParticlesConsumedBuffer);

        }

        ComputeHelper.Dispatch(bloodCompute, numParticles, 1, 1, UpdateDustKernel);

        Graphics.DrawMeshInstancedIndirect(mesh, 0, instancedMaterial, new Bounds(Vector3.zero, Vector3.one * 1000), argsBuffer);

        splatterVolume *= 0.9f;
        splatterPlayer.volume = splatterVolume;

        squeakPlayer.volume = squeakVolume;
        squeakVolume *= 0.8f;

        if (splatterVolume < 0.001)
            splatterVolume = 0;

        if (squeakVolume < 0.001)
            squeakVolume = 0;

            mop1Clean = (int)(mop1Clean * 0.8f);
            mop2Clean = (int)(mop2Clean * 0.8f);
    }

    void FixedUpdate()
    {
    }

    public void cleanAllBlood() {
        ComputeHelper.Dispatch(bloodCompute, numParticles, 1, 1, CollectAllKernel);
    }

    public void createBlood(Vector3 wher, int muchies, float powah)
    {
        StartCoroutine(penisBlood(wher, muchies, powah));
    }

    IEnumerator penisBlood(Vector3 loc, int amount, float power)
    {
        RequestAllBloodStates();

        // Wait until we get the state of all the particles from the gpu
        while (!freeBloodReadRequest.done)
        {
            yield return new WaitForEndOfFrame();
        }

        // Find N particles which are disabled

        uint i = bufferLookPointer;
        int found = 0;

        Droplet[] particles = new Droplet[numParticles];
        var as_Particles = freeBloodReadRequest.GetData<Droplet>().ToArray();
        int length = as_Particles.Length;
        // particleBuffer.GetData(particles);

        uint[] particleIndeces = new uint[amount];

        // oof
        while (i < numParticles)
        {
            if (as_Particles[i % (length - 1)].enabled == 0)
            { // Found unused particle
                particleIndeces[found] = i;
                found++;

                if (found >= amount)
                    break;
            }

            i++;
        }

        bufferLookPointer = (uint)((bufferLookPointer + amount) % numParticles);

        // Debug.Log(string.Join(", ", particleIndeces));

        // send data to gpu
        freeParticleBuffer.SetData(particleIndeces);
        bloodCompute.SetBuffer(InitDustKernel, "freeParticles", freeParticleBuffer);


        // Test for race conditions
        // yield return new WaitForSeconds(1.0f);

        bloodCompute.SetFloat("particleVel", power / 4.0f);
        bloodCompute.SetVector("particleInitPos", loc);
        bloodCompute.SetInt("particlesToInitialize", found);

        Vector3 pow = UnityEngine.Random.insideUnitSphere * power;
        pow.z = Mathf.Abs(pow.z);
        bloodCompute.SetVector("initialVelocity", pow);

        ComputeHelper.Dispatch(bloodCompute, amount, 1, 1, InitDustKernel);

        activeParticles += found;

        yield return null;
    }

    void OnDestroy()
    {
        ComputeHelper.Release(particleBuffer, positionBuffer, argsBuffer, numParticlesConsumedBuffer, freeParticleBuffer);
    }

    public void createBloodTest(int amount)
    {
        createBlood(Vector3.zero, amount, 10.0f);
    }
}