#ifndef MATERIALS_COMMONS_H
#define MATERIALS_COMMONS_H

#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
#ifndef PI
#define PI 3.14159265359
#endif

#if 1
#define float16_t float
#define f16vec2   vec2
#define f16vec3   vec3
#define f16vec4   vec4
#endif

#ifndef HAS_16BIT_TYPES
#define float16_t float
#define f16vec2   vec2
#define f16vec3   vec3
#define f16vec4   vec4
#endif

#ifdef GL_EXT_shader_explicit_arithmetic_types_float16
#if 0
#define float float32_t
#define vec2 f32vec2
#define vec3 f32vec3
#define vec4 f32vec4
#define uint uint32_t
#define uvec2 u32vec2
#define uvec3 u32vec3
#define uvec4 u32vec4
#endif
#endif

#ifdef SPIRV_VULKAN
#define SYS_VertexIndex gl_VertexIndex
#define SYS_InstanceIndex gl_InstanceIndex
#else
#define SYS_VertexIndex gl_VertexID
#define SYS_InstanceIndex (gl_InstanceID + gl_BaseInstance)
#endif

// these are NOT bitmask. pixel can have only one attribute. 
#define ATTR_PARTICLE (1)
#define ATTR_BACKGROUND (1)
#define MATERIAL_ID_MASK_ATTR 0x1

// These are for the builtin geometry attributes. Exposed in <vtx_builtin_buffers>
#define VTXBuiltinAttribute_Coords 1
#define VTXBuiltinAttribute_Normals 2
#define VTXBuiltinAttribute_Albedo 4
#define VTXBuiltinAttribute_UV0 8
#define VTXBuiltinAttribute_UV1 16

uint packSnorm2x12(vec2 v)
{
	uvec2 d = uvec2(round( 2047.5f + v *  2047.5f));
	return d.x | (d.y << 12u);
}
uint packSnorm2x15(vec2 v) { uvec2 d = uvec2(round(16383.5f + v * 16383.5f)); return d.x | (d.y << 15u); }
uint packSnorm2x11(vec2 v) { uvec2 d = uvec2(round( 1023.5f + v *  1023.5f)); return d.x | (d.y << 11u); }
uint packSnorm2x8( vec2 v) { uvec2 d = uvec2(round(  127.5f + v *   127.5f)); return d.x | (d.y <<  8u); }
vec2 unpackSnorm2x8( uint d) { return vec2(uvec2(d, d >>  8) &   255u) /   127.5f - 1.0f; }
vec2 unpackSnorm2x12(uint d) { return vec2(uvec2(d, d >> 12) &  4095u) /  2047.5f - 1.0f; }
vec2 unpackSnorm2x15(uint d) { return vec2(uvec2(d, d >> 15) & 32767u) / 16383.5f - 1.0f; }
vec2 unpackSnorm2x11(uint d) { return vec2(uvec2(d, d >> 11) &  2047u) /  1023.5f - 1.0f; }

uint encode_normal_23bit(vec3 n)
{
	vec2 v = n.xy;// * 0.5 + 0.5;
	uint s = n.z > 0.0 ? (1 << 22u) : 0;
	return s | packSnorm2x11(v);
}

uint encode_normal_31bit(vec3 n)
{
	vec2 v = n.xy;// * 0.5 + 0.5;
	uint s = n.z > 0.0f ? (1 << 30u) : 0;
	return s | packSnorm2x15(v);
}

uint encode_normal_32bit(vec3 n)
{
	vec2 v = n.xy * 0.5 + 0.5;
	uint s = n.z > 0.0 ? (1 << 31u) : 0;
	return s | packSnorm2x15(v);
}

uint encode_normal_material(vec3 n, int id)
{
	uint normal_material = encode_normal_31bit(n);
	normal_material = (uint(id) << 31u) | normal_material;		// id into msb to make it compaticle when we just encode normal
	return normal_material;
}

vec3 decode_normal(uint data)
{
	vec2 v = unpackSnorm2x15(data);
	uint s = data & (1 << 30);

	vec3 n;
	n.xy = v;// * 2.0 - 1.0;
	n.z = sqrt(clamp(1.0 - dot(n.xy, n.xy), 0.0, 1.0)) * (s > 0 ? 1.0 : -1.0);

	return n;
}

vec3 decode_normal_32bit(uint data)
{
	vec2 v = unpackSnorm2x15(data);
	uint s = data & (1 << 31);

	vec3 n;
	n.xy = v * 2.0 - 1.0;
	n.z = sqrt(max(0.0, 1.0 - dot(n.xy, n.xy))) * (s > 0 ? 1.0 : -1.0);

	return n;
}

uvec2 encode_metalness_roughness_material(float metalness, float roughness, uint material)
{
	return uvec2(
		(uint(min(1.0, metalness) * 255.0) << 8) | (uint(min(1.0, roughness) * 255.0) << 0),
		material); 
}

void decode_metalness_roughness_material(uvec2 mrm, out float metalness, out float roughness, out uint material)
{
	metalness = float((mrm.r >> 8)) * (1.0 / 255.0);
	roughness = float((mrm.r >> 0) & 0xff) * (1.0 / 255.0);
	material = mrm.g;
}

uvec2 encode_component_tags(uint component_tags)
{
	return uvec2(component_tags & 0xffffU, component_tags >> 16);
}

void decode_component_tags(uvec2 ct, out uint component_tags)
{
	component_tags = ct.x | (ct.y << 16);
}

int decode_material(uint data)
{
	return int(data >> 31u);
}

#define MaterialFlag_Reflective 1
#define MaterialFlag_Raytrace 2
#define MaterialFlag_Reflect 4
#define MaterialFlag_ScreenspaceReflect 8
#define MaterialFlag_Voxelize 16
#define MaterialFlag_Transparent 32
#define MaterialFlag_Doublesided 64
#define MaterialFlag_Flat 128
#define MaterialFlag_ShadowCast 256
#define MaterialFlag_ShadowReceive 512
#define MaterialFlag_TriPlanarMapping 1024
#define MaterialFlag_RaytraceTerminate 2048
#define MaterialFlag_DisableLighting 4096

struct MaterialPropertiesGPU
{
	vec3  diffuse;
	float transparency;
	vec3  emissive;
	float roughness;
	vec3  triplanar_factor;
	float refraction;
	float normal_factor;
	float emissive_factor;
	float temporal_accumulation_factor;
	float shadowmap_bias;
	float metalness;
	int   albedo_sampler;
	int   emissive_sampler;
	int   normal_sampler;
	int   metalic_roughness_sampler;
	uint  flags;
	uint  _pad0;
	uint  _pad1;
};

struct TextureResourceParams	// keep 4*32bit aligned, as this will go directly to GPU. Sync with commons.glsl
{
	int   flip_y;				// = 0;
	int   clamp_range_01;		// = 0;
	float remap_range_start;	// = 0.0f;
	float remap_range_end;		// = 1.0f;
};

vec4 texture_sample_with_params(sampler2D smp, vec2 uv, TextureResourceParams params)
{
	if (params.flip_y != 0)
		uv.y = 1.0 - uv.y;
	vec4 v = texture(smp, uv);
	v = (v - params.remap_range_start) / (params.remap_range_end - params.remap_range_start);
	if (params.clamp_range_01 != 0)
		v = clamp(v, vec4(0.0), vec4(1.0));
	return v;
}

vec4 texture_sample_lod_with_params(sampler2D smp, vec2 uv, float lod, TextureResourceParams params)
{
	if (params.flip_y != 0)
		uv.y = 1.0 - uv.y;
	vec4 v = textureLod(smp, uv, lod);
	v = (v - params.remap_range_start) / (params.remap_range_end - params.remap_range_start);
	if (params.clamp_range_01 != 0)
		v = clamp(v, vec4(0.0), vec4(1.0));
	return v;
}

vec4 texture_remap_with_params(vec4 v, TextureResourceParams params)
{
	v = (v - params.remap_range_start) / (params.remap_range_end - params.remap_range_start);
	if (params.clamp_range_01 != 0)
		v = clamp(v, vec4(0.0), vec4(1.0));
	return v;
}

vec3 texture_remap_with_params(vec3 v, TextureResourceParams params)
{
	v = (v - params.remap_range_start) / (params.remap_range_end - params.remap_range_start);
	if (params.clamp_range_01 != 0)
		v = clamp(v, vec3(0.0), vec3(1.0));
	return v;
}

// Keep them in sync with sceneplayer.h

#ifdef MATERIAL_PROPERTIES_BINDING
layout (std140) uniform MaterialPropertiesDataBuffer {
//layout (std140) readonly buffer MaterialPropertiesDataBuffer {
	MaterialPropertiesGPU material_properties[512];
} materials;

uniform sampler2D material_textures[64];

#endif

struct LightProperties
{
	vec4  diffuse;
	vec4  direction;	// world space
	vec4  position;		// world space
	vec4  up;			// ltc/area
	vec4  right;		// ltc/area
	vec2  dimensions;	// ltc/area
	vec2  _pad1;
	float intensity;	// ltc/area
	float range;
	float cutoff;
	float roughness_modifier;
	int   is_area;
	int   type;			// LightType_ bitmask
	
	int   projector_sampler;
	float projector_intensity;
	int   shadowmap_sampler0;	// for each cascade. 'unrolled' as we index manually anyway
	int   shadowmap_sampler1;	//
	int   shadowmap_sampler2;	//
	int   shadowmap_sampler3;	//
	float cascade_distance0;	//
	float cascade_distance1;	//
	float cascade_distance2;	//
	float cascade_distance3;	//

	mat4 mat_shadow_mv;
	mat4 mat_shadow_p[4];
	mat4 mat_shadow_mvp[4];
};

#define LightType_Directional   1
#define LightType_Spot          2
#define LightType_Point         4
#define LightType_Shadowcasting 8
#define LightType_Projector     16
#define LightType_LTC           32
#define LightType_Volumetric    64
#define LightType_Attenuating   128

#ifdef LIGHT_PROPERTIES_BINDING

layout (std140, row_major) uniform LightPropertiesBuffer {
	LightProperties light_properties[16];
} lights;

uniform sampler2DShadow LightShadowmapCmpSamplers[16];
uniform sampler2D       LightShadowmapSamplers[16]; // TODO: Combine with all other textures...
uniform sampler2D       LightProjectorSamplers[16];

#endif

// length as integer into alpha, then decode from that
uint color_convert_rgb_float3_uint(vec3 v)
{
	uint vi;
	float v_mag = ceil(length(v.xyz));
	v.xyz = v.xyz / v_mag;
	v_mag = min(255.0, v_mag);

	vi = uint(v.x * 255.0) | (uint(v.y * 255.0) << 8) |( uint(v.z * 255.0) << 16);
	vi |= uint(v_mag) * (1 << 24); 
	return vi;
}

vec3 color_convert_uint_to_float3(uint vi)
{
	vec3 v = vec3(0.0);
	uint v_mag = vi >> 24;
	float v_mag_rcp = (1.0 / 255.0) * float(v_mag);

	v.x = float((vi >> 0) & 0xff) * v_mag_rcp;
	v.y = float((vi >> 8) & 0xff) * v_mag_rcp;
	v.z = float((vi >> 16) & 0xff) * v_mag_rcp;
	return v;
}

vec4 color_convert_rgb_rgbm(vec3 v)
{
	v = sqrt(v);
	v = v / 128.0;
	vec4 rgbm;
	rgbm.a = clamp(max(max(v.r, v.g), max(v.b,1e-6)), 0.0, 1.0);
	rgbm.a = ceil(rgbm.a * 256.0) / 255.0;
	rgbm.rgb = v / rgbm.a;

	return rgbm;
}

vec3 color_convert_rgbm_rgb(vec4 v)
{
	v.rgb = v.rgb * v.a * 128.0;
	return v.rgb * v.rgb;
}

vec4 quaternion_from_axis_angle(vec3 axis, float angle_in_radians)
{
	vec4 v = vec4(-axis * sin(angle_in_radians * 0.5f), cos(angle_in_radians * 0.5));
	return v;
}

vec3 vector_rotate_by_quaternion(vec3 v, vec4 q)
{
    // Extract the vector part of the quaternion
    vec3 u = q.xyz;

    // Extract the scalar part of the quaternion
    float s = q.w;

    // Do the math
    return 2.0 * dot(u, v) * u
         + (s*s - dot(u, u)) * v
         + 2.0 * s * cross(u, v);
}

struct EntityTransformParams
{
	mat4 mModel;
	mat4 mView;
	mat4 mModelNormal;
	mat4 mModelView;
	mat4 mModelViewInvTrans;
	mat4 mProjection;
	mat4 mModelInv;
	vec3 vCameraPosition;
};

vec3 vector_transform_by_mat43(vec3 v, mat4 m)
{
	return (m * vec4(v, 1.0)).xyz;
}

vec3 vector_transform_by_mat33(vec3 v, mat4 m)
{
	return (m * vec4(v, 0.0)).xyz;
}

vec2 vector_transform_by_mat22(vec2 v, mat4 m)
{
	return (m * vec4(v, 0.0, 0.0)).xy;
}

struct GeometryInformation
{
	uint vtx_num;
	uint surfaces_num;
	uint builtin_attribute_mask;
	uint _pad1;
	uint faces_num_per_surface[64];		// don't pass via uniform buffer until we can get rid of packing
};

// NOTE: Is this optimization worth it even if buffer is deduced to be read only? Maybe on NV and maybe on very simple shaders?
#ifndef GEOMETRY_INFORMATION_STATIC
layout (std430, row_major) buffer GeometryInformationBuffer {
	GeometryInformation geometry_information;
};
#else
layout (std140, row_major) uniform GeometryInformationBuffer {
	GeometryInformation geometry_information;
};
#endif

struct GlobalVariables
{
	float time;
	float global_time;
	float time_step;
	int   monotonic;
};


layout (std140, row_major) uniform GlobalVariablesBuffer {
	GlobalVariables globals;
};

#endif