#ifndef COMMONS_INDIRECT_H
#define COMMONS_INDIRECT_H

#include <shaders/materials/commons_instancing_buffers.h>
#include <shaders/materials/commons_geometry_information.glsl>
#include <shaders/geometry_partitioning/raytrace_buffers.glsl>

// NOTE: Add more defines here for utility functions
#if defined(MAIN_BUILD_DRAW_ELEMENTS_INDIRECT_FROM_BUFFER) || defined(MAIN_BUILD_DRAW_ARRAYS_INDIRECT_FROM_BUFFER) || defined(MAIN_BUILD_DISPATCH_INDIRECT_FROM_BUFFER) || defined(MAIN_BUILD_DISPATCH_INDIRECT_FROM_TRANSFORMED_GEOMETRY)
#version 430
layout (local_size_x = 32, local_size_y = 1) in;
#endif

struct DrawElementsIndirectParams
{
	uint  count;
	uint  instance_count;
	uint  first_index;
	uint  base_vertex;
	uint  base_instance;
};

struct DrawArraysIndirectParams
{
	uint  count;
	uint  instance_count;
	uint  first_index;
	uint  base_instance;
};

struct DispatchIndirectParams
{
	uint x;
	uint y;
	uint z;
};

// DrawElementsIndirect support

#ifdef MAIN_BUILD_DRAW_ELEMENTS_INDIRECT_FROM_BUFFER

// Take data from some arbitrary buffer location, transform it a bit and build
// DrawIndirect call from it. Takes instances buffer

layout (std430) buffer DrawElementsIndirectParamsBuffer {
	DrawElementsIndirectParams draw_indirect_params[];
};

layout (std430) buffer DrawIndirectSourceBuffer {
	uint draw_indirect_src_data[];
};

layout (std140, row_major) uniform DrawIndirectInstanceParamsBuffer {
	InstanceParams draw_indirect_instance_params;
};

struct BuildDrawIndirectFromBufferParams
{
	uint indirect_buffer_idx;
	uint src_buffer_offset;
	uint src_buffer_data_numerator;
	uint src_buffer_data_denominator;
};

layout(std140, row_major) uniform BuildDrawIndirectFromBufferParamsBuffer {
	BuildDrawIndirectFromBufferParams params;
};

void MAIN_BUILD_DRAW_ELEMENTS_INDIRECT_FROM_BUFFER()
{
	uint idx = gl_GlobalInvocationID.x;

	if (idx == 0)
	{
		draw_indirect_params[params.indirect_buffer_idx].count = 
			draw_indirect_src_data[params.src_buffer_offset] * params.src_buffer_data_numerator / params.src_buffer_data_denominator;

		draw_indirect_params[params.indirect_buffer_idx].instance_count = min(draw_indirect_instance_params.buffer_capacity, draw_indirect_instance_params.instance_count);
		draw_indirect_params[params.indirect_buffer_idx].first_index    = 0;
		draw_indirect_params[params.indirect_buffer_idx].base_vertex    = 0;
		draw_indirect_params[params.indirect_buffer_idx].base_instance  = 0;
	}
}

#endif

#ifdef MAIN_BUILD_DRAW_TRIANGLES_ELEMENTS_INDIRECT_FROM_GEOMETRY

// Take data from GeometryInformation buffer location and build
// DrawIndirect call from it. Handles multiple surfaces in a single call (to not have to multi-dispatch)

layout (std430) buffer DrawElementsIndirectParamsBuffer {
	DrawElementsIndirectParams draw_indirect_params[];
};

layout (std140, row_major) uniform DrawIndirectInstanceParamsBuffer {
	InstanceParams draw_indirect_instance_params;
};

layout (scalar, row_major) uniform DrawIndirectGeometryInformationParamsBuffer {
	GeometryInformation draw_indirect_geometry_information;
};

struct BuildDrawTrianglesIndirectFromGeometryInformationParams
{
	uint indirect_buffer_idx;
	uint first_surface_idx;
	uint last_surface_idx;
	uint _pad0;
};

layout(std140, row_major) uniform BuildDrawTrianglesIndirectFromGeometryInformationParamsBuffer {
	BuildDrawTrianglesIndirectFromGeometryInformationParams params;
};

void MAIN_BUILD_DRAW_TRIANGLES_ELEMENTS_INDIRECT_FROM_GEOMETRY()
{
	uint idx = gl_GlobalInvocationID.x;

	if (idx == 0)
	{
		uint indirect_buffer_idx = params.indirect_buffer_idx;
		for(uint si = params.first_surface_idx; si <= params.last_surface_idx; si++)
		{
			draw_indirect_params[indirect_buffer_idx].count = draw_indirect_geometry_information.faces_num_per_surface[si] * 3;

			draw_indirect_params[indirect_buffer_idx].instance_count = min(draw_indirect_instance_params.buffer_capacity, draw_indirect_instance_params.instance_count);
			draw_indirect_params[indirect_buffer_idx].first_index    = draw_indirect_geometry_information.idx_buffer_offset;
			draw_indirect_params[indirect_buffer_idx].base_vertex    = 0;
			draw_indirect_params[indirect_buffer_idx].base_instance  = 0;
			indirect_buffer_idx++;
		}
	}
}

#endif // MAIN_BUILD_DRAW_TRIANGLES_ELEMENTS_INDIRECT_FROM_GEOMETRY

#ifdef MAIN_BUILD_DRAW_POINTS_ARRAYS_INDIRECT_FROM_GEOMETRY

// Take data from GeometryInformation buffer location and build
// DrawIndirect call from it. Handles multiple surfaces in a single call (to not have to multi-dispatch)

layout (std430) buffer DrawArraysIndirectParamsBuffer {
	DrawArraysIndirectParams draw_indirect_params[];
};

layout (std140, row_major) uniform DrawIndirectInstanceParamsBuffer {
	InstanceParams draw_indirect_instance_params;
};

layout (std140, row_major) uniform DrawIndirectGeometryInformationParamsBuffer {
	GeometryInformation draw_indirect_geometry_information;
};

struct BuildDrawPointsIndirectFromGeometryInformationParams
{
	uint indirect_buffer_idx;
	uint _pad0;
	uint _pad1;
	uint _pad2;
};

layout(std140, row_major) uniform BuildDrawPointsIndirectFromGeometryInformationParamsBuffer {
	BuildDrawPointsIndirectFromGeometryInformationParams params;
};

void MAIN_BUILD_DRAW_POINTS_ARRAYS_INDIRECT_FROM_GEOMETRY()
{
	uint idx = gl_GlobalInvocationID.x;

	if (idx == 0)
	{
		uint indirect_buffer_idx = params.indirect_buffer_idx;
		draw_indirect_params[indirect_buffer_idx].count = draw_indirect_geometry_information.vtx_num;

		draw_indirect_params[indirect_buffer_idx].instance_count = min(draw_indirect_instance_params.buffer_capacity, draw_indirect_instance_params.instance_count);
		draw_indirect_params[indirect_buffer_idx].first_index    = 0;// don't actually need it for arrays! draw_indirect_geometry_information.idx_buffer_offset;
		draw_indirect_params[indirect_buffer_idx].base_instance  = 0;
	}
}

#endif // MAIN_BUILD_DRAW_POINTS_ARRAYS_INDIRECT_FROM_GEOMETRY



// DrawArraysIndirect support

#ifdef MAIN_BUILD_DRAW_ARRAYS_INDIRECT_FROM_BUFFER

// Take data from some arbitrary buffer location, transform it a bit and build
// DrawIndirect call from it. For now only single instance

layout (std430) buffer DrawArraysIndirectParamsBuffer {
	DrawArraysIndirectParams draw_indirect_params[];
};

layout (std430) buffer DrawIndirectSourceBuffer {
	uint draw_indirect_src_data[];
};

layout (std140, row_major) uniform DrawIndirectInstanceParamsBuffer {
	InstanceParams draw_indirect_instance_params;
};

struct BuildDrawIndirectFromBufferParams
{
	uint indirect_buffer_idx;
	uint src_buffer_offset;
	uint src_buffer_data_numerator;
	uint src_buffer_data_denominator;
};

layout(std140, row_major) uniform BuildDrawIndirectFromBufferParamsBuffer {
	BuildDrawIndirectFromBufferParams params;
};

void MAIN_BUILD_DRAW_ARRAYS_INDIRECT_FROM_BUFFER()
{
	uint idx = gl_GlobalInvocationID.x;

	if (idx == 0)
	{
		draw_indirect_params[params.indirect_buffer_idx].count = 
			draw_indirect_src_data[params.src_buffer_offset] * params.src_buffer_data_numerator / params.src_buffer_data_denominator;

		draw_indirect_params[params.indirect_buffer_idx].instance_count = min(draw_indirect_instance_params.buffer_capacity, draw_indirect_instance_params.instance_count);
		draw_indirect_params[params.indirect_buffer_idx].first_index    = 0;
		draw_indirect_params[params.indirect_buffer_idx].base_instance  = 0;
	}
}

#endif



// DispatchIndirect support

#ifdef MAIN_BUILD_DISPATCH_INDIRECT_FROM_BUFFER

// Take data from some arbitrary buffer location, transform it a bit and build
// DispatchIndirect call from it. Takes instance buffer

layout (std430) buffer DispatchIndirectParamsBuffer {
	DispatchIndirectParams dispatch_indirect_params[];
};

layout (std430) buffer DispatchIndirectSourceBuffer {
	uint dispatch_indirect_src_data[];
};

layout (std140, row_major) uniform DispatchIndirectInstanceParamsBuffer {
	InstanceParams dispatch_indirect_instance_params;
};

struct BuildDispatchIndirectFromBufferParams
{
	uint indirect_buffer_idx;
	uint src_buffer_offset_x;
	uint src_buffer_data_numerator_x;
	uint src_buffer_data_denominator_x;
	uint src_buffer_offset_y;
	uint src_buffer_data_numerator_y;		// not evaluated if 0. outputs 1 into dispatch
	uint src_buffer_data_denominator_y;
	uint src_buffer_offset_z;
	uint src_buffer_data_numerator_z;		// not evaluated if 0. outputs 1 into dispatch
	uint src_buffer_data_denominator_z;
};

layout(std140, row_major) uniform BuildDispatchIndirectFromBufferParamsBuffer {
	BuildDispatchIndirectFromBufferParams params;
};

void MAIN_BUILD_DISPATCH_INDIRECT_FROM_BUFFER()
{
	uint idx = gl_GlobalInvocationID.x;

	if (idx == 0)
	{
		dispatch_indirect_params[params.indirect_buffer_idx].x = 
			(
			    dispatch_indirect_src_data[params.src_buffer_offset_x]
			  * params.src_buffer_data_numerator_x
			  * min(dispatch_indirect_instance_params.buffer_capacity, dispatch_indirect_instance_params.instance_count)
			  + params.src_buffer_data_denominator_x - 1
			) / params.src_buffer_data_denominator_x;

		dispatch_indirect_params[params.indirect_buffer_idx].y = 1;
		dispatch_indirect_params[params.indirect_buffer_idx].z = 1;

		if( params.src_buffer_data_numerator_y > 0)
		{
			dispatch_indirect_params[params.indirect_buffer_idx].y = 
				(dispatch_indirect_src_data[params.src_buffer_offset_y] * params.src_buffer_data_numerator_y + params.src_buffer_data_denominator_y - 1) / params.src_buffer_data_denominator_y;
		}

		if (params.src_buffer_data_numerator_z > 0)
		{
			dispatch_indirect_params[params.indirect_buffer_idx].z = 
				(dispatch_indirect_src_data[params.src_buffer_offset_z] * params.src_buffer_data_numerator_z + params.src_buffer_data_denominator_z - 1) / params.src_buffer_data_denominator_z;
		}
	}
}

#endif

#ifdef MAIN_BUILD_DISPATCH_INDIRECT_FROM_TRANSFORMED_GEOMETRY

// Take data from some raytracer's transformed geometry buffer location, transform it a bit and build
// DispatchIndirect call from it. Takes instance buffer

layout (std430) buffer DispatchIndirectParamsBuffer {
	DispatchIndirectParams dispatch_indirect_params[];
};

layout (std140, row_major) uniform DispatchIndirectInstanceParamsBuffer {
	InstanceParams dispatch_indirect_instance_params;
};

struct BuildDispatchIndirectFromTransformedGeometryParams
{
	uint indirect_buffer_idx;
	uint transformed_geometry_idx;
	uint numerator;
	uint denominator;
};

layout(std140, row_major) uniform BuildDispatchIndirectFromTransformedGeometryParamsBuffer {
	BuildDispatchIndirectFromTransformedGeometryParams params;
};

uint transformed_geometry_get_first_face_idx(uint tdl_idx)
{
	if (tdl_idx > 0)
		return transformed_data_location[tdl_idx - 1].last_face_idx + 1;

	return 0;
}

uint transformed_geometry_get_last_face_idx(uint tdl_idx)
{
	return transformed_data_location[tdl_idx].last_face_idx;
}

void MAIN_BUILD_DISPATCH_INDIRECT_FROM_TRANSFORMED_GEOMETRY()
{
	uint idx = gl_GlobalInvocationID.x;

	if (idx == 0)
	{
		dispatch_indirect_params[params.indirect_buffer_idx].x = 
			(
			    (transformed_geometry_get_last_face_idx(params.transformed_geometry_idx) - transformed_geometry_get_first_face_idx(params.transformed_geometry_idx) + 1)
			  * params.numerator
			  * min(dispatch_indirect_instance_params.buffer_capacity, dispatch_indirect_instance_params.instance_count)
			  + params.denominator - 1
			) / params.denominator;

		dispatch_indirect_params[params.indirect_buffer_idx].y = 1;
		dispatch_indirect_params[params.indirect_buffer_idx].z = 1;
	}
}

#endif


#endif
