using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Rendering.Universal.Internal;
using UnityEngine.UI;

public class BlendShaderController : MonoBehaviour
{
    private static string savePath = Application.streamingAssetsPath + '\\';
    private const string filename = "dsitMap";
    private const string ext = ".txt";

    public Shader DepthShader;
    public ComputeShader BlendShader;
    public Camera camera1;
    public Camera camera2;
    public int QuadResolution = 256;
    public float Multiplier;
    public RawImage Reciever;
    [Range(0, 1000)] public float OffsetMultiplier = 1000;

    public RenderTexture test;

    private RenderTexture depth1;
    private RenderTexture depth2;
    private RenderTexture cameraTexture1;
    private RenderTexture cameraTexture2;
    private RenderTexture Result;
    private int currentRes;

    private bool flag = false;

    private ComputeBuffer computeBuffer;
    private float[] resultArray;
    Thread savingThread;
    Task<string> task1;
    Task task2;

    // Start is called before the first frame update
    void Start()
    {
        InitializeTextures();
        InitializeComputeBuffer();
    }

    void InitializeComputeBuffer()
    {
        computeBuffer = new ComputeBuffer(currentRes * currentRes, 1 * sizeof(float));//2 dimensional array of 1 value type of double
    }

    void InitializeTextures()
    {
        currentRes = QuadResolution;
        RenderTexture[] defaultTextures = CreateTextures(2, currentRes, currentRes);
        cameraTexture1 = defaultTextures[0];
        cameraTexture2 = defaultTextures[1];

        RenderTexture[] depthTextures = CreateTextures(2, currentRes, currentRes, true);
        depth1 = depthTextures[0];
        depth2 = depthTextures[1];

        Result = CreateTextures(1, currentRes, currentRes)[0];

        Reciever.texture = Result;

        camera1.targetTexture = cameraTexture1;
        camera2.targetTexture = cameraTexture2;
    }

    public static RenderTexture[] CreateTextures(int count, int resX, int resY, bool depth = false)
    {
        RenderTexture[] result = new RenderTexture[count];
        for (int i = 0; i < count; i++)
        {
            result[i] = new RenderTexture(resX, resY, 32, depth ? RenderTextureFormat.Depth : RenderTextureFormat.ARGBFloat);
            result[i].enableRandomWrite = !depth;
            result[i].Create();
        }
        return result;
    }

    // Update is called once per frame
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.F))
        {
            SetFlag();
        }

        if (flag)
        {
            GenerateDepth();
        }

        UpdateShader(cameraTexture1, cameraTexture2);

        if(Input.GetKeyDown(KeyCode.S))
        {
            SaveTexture();
        }

        if (Input.GetKeyDown(KeyCode.J))
        {
            Debug.Log(GetDistanceByDepth());
        }
        if (Input.GetKeyDown(KeyCode.K))
        {
            Debug.Log(GetDistanceByRayCast());
        }

        if (Input.GetKeyDown(KeyCode.T))
        {
            camera1.targetTexture = test;
            camera1.RenderWithShader(DepthShader, "fragment");
            camera1.targetTexture = cameraTexture1;
        }
    }

    void UpdateShader(RenderTexture tex1, RenderTexture tex2)
    {
        BlendShader.SetTexture(0, "Camera1", tex1);
        BlendShader.SetTexture(0, "Camera2", tex2);
        BlendShader.SetTexture(0, "Result", Result);
        BlendShader.SetFloat(Shader.PropertyToID("Offset"), (int)(camera2.transform.localPosition.x * OffsetMultiplier));
        BlendShader.SetFloat(Shader.PropertyToID("ResX"), Result.width);
        BlendShader.SetBuffer(0, "MonoChannelResult", computeBuffer);
        //BlendShader.SetTexture(0, "Test", test);
        BlendShader.Dispatch(0, Result.width / 8, Result.height / 8, 1);
    }

    void GenerateDepth()
    {
        //camera1.RenderWithShader(DepthShader, "RenderType");
        //camera2.RenderWithShader(DepthShader, "RenderType");

        Graphics.Blit(depth1, cameraTexture1); //Depth generated due to format of render texture
        Graphics.Blit(depth2, cameraTexture2);
    }

    void SwapRenderTargets()
    {
        if (flag)
        {
            camera1.targetTexture = depth1;
            camera2.targetTexture = depth2;
        }
        else
        {
            camera1.targetTexture = cameraTexture1;
            camera2.targetTexture = cameraTexture2;
        }
    }

    public void SetFlag()
    {
        flag = !flag;
        SwapRenderTargets();
    }

    private async void SaveTexture()
    {
        Debug.Log("Start Saving");
        resultArray = new float[currentRes * currentRes];
        computeBuffer.GetData(resultArray);
        Debug.Log("Data copied");
        task1 = Task.Run(() => SerializeArray());
        await task1;
        string result = task1.Result;

        task2 = SavingProcessAsync(result);
        await task2;
    }

    private async Task SavingProcessAsync(string data)
    {
        int filesCount = Directory.EnumerateFiles(savePath, filename + '*' + ext).Count();
        await File.WriteAllTextAsync(savePath + filename + filesCount.ToString() + ext, data);
        Debug.Log("File saved");
    }

    private string SerializeArray()
    {
        string result = string.Empty;
        for (int i = 0; i < resultArray.Length; i += currentRes)
        {
            result += string.Join(", ", resultArray[i..(i + currentRes)]) + '\n';
        }
        return result;
    }

    private float GetDistanceByDepth()
    {
        resultArray = new float[currentRes * currentRes];
        computeBuffer.GetData(resultArray);
        int idx = (int)(currentRes * (0.5f + currentRes * 0.5f));
        Matrix4x4 porjMat = camera1.projectionMatrix;
        float zDepth = resultArray[idx];
        float far = camera1.farClipPlane;
        float near = camera1.nearClipPlane;
        return zDepth * (far - near) + near; 
    }

    private float GetDistanceByRayCast()
    {
        Vector3 position = camera1.transform.position + camera1.transform.right * camera2.transform.localPosition.x * 0.5f;
        if (Physics.Raycast(position, camera1.transform.forward, out var hit, 100))
        {
            return hit.distance;
        }

        return -1;
    }

    private void OnDisable()
    {
        computeBuffer.Release();
        computeBuffer = null;
    }
}
