#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

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; }

// copy sign of s into value x (https://www.shadertoy.com/view/Mtfyzl)
vec2 copysign(float x, vec2 s)
{
    uint ix = floatBitsToUint(x)&0x7fffffffu;
    return vec2( uintBitsToFloat((floatBitsToUint(s.x)&0x80000000u)|ix),
                 uintBitsToFloat((floatBitsToUint(s.y)&0x80000000u)|ix) );
}
// same as above, if we know x is non-negative
vec2 copysignp(float x, vec2 s)
{
    uint ix = floatBitsToUint(x);
    return vec2( uintBitsToFloat((floatBitsToUint(s.x)&0x80000000u)|ix),
                 uintBitsToFloat((floatBitsToUint(s.y)&0x80000000u)|ix) );
}

// https://www.shadertoy.com/view/Mtfyzl
uint octahedral_32( in vec3 nor, uint sh )
{
#if 0
    nor /= ( abs( nor.x ) + abs( nor.y ) + abs( nor.z ) );
    nor.xy = (nor.z >= 0.0) ? nor.xy : (1.0-abs(nor.yx))*sign(nor.xy);
    vec2 v = 0.5 + 0.5*nor.xy;
#else
	// Rune Stubbe's version, much faster than original
	// NOTE(kiero): For some cases, at least on AMD hd (Vulkan) some cases produce
	// oncorrect values when using version above. Probably some floating issue. Investigate... one day
    nor.xyz /= abs(nor.x) + abs(nor.y) + abs(nor.z);
    float t = max(-nor.z,0.0);
    nor.x += (nor.x>0.0) ? t : -t;
    nor.y += (nor.y>0.0) ? t : -t;
    vec2 v = nor.xy * .5 + .5;
#endif
    uint mu = (1u<<sh)-1u;
    uvec2 d = uvec2(floor(v*float(mu)+0.5));
    return (d.y<<sh)|d.x;
}

vec3 i_octahedral_32( uint data, uint sh )
{
    uint mu =(1u<<sh)-1u;
    
    uvec2 d = uvec2( data, data>>sh ) & mu;
    vec2 v = vec2(d)/float(mu);
    
    v = -1.0 + 2.0*v;
#if 1
    // Rune Stubbe's version, much faster than original
    vec3 nor = vec3(v, 1.0 - abs(v.x) - abs(v.y));
    float t = max(-nor.z,0.0);
    nor.x += (nor.x>0.0)?-t:t;
    nor.y += (nor.y>0.0)?-t:t;
#endif
#if 0
    // is there was a copysign() in GLSL...
    vec3 nor = vec3(v, 1.0 - abs(v.x) - abs(v.y));
    nor.xy -= copysignp(max(-nor.z,0.0),nor.xy);
#endif
#if 0
    // original
    vec3 nor;
    nor.z = 1.0 - abs(v.x) - abs(v.y);
    nor.xy = (nor.z>=0.0) ? v.xy : (1.0-abs(v.yx))*sign(v.xy);
#endif    
    return normalize( nor );
}

#if 0
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);
}
#endif

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);
}

#if 0
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 & ((1 << 31) - 1));
	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;
}
#else
uint encode_normal_material(vec3 n, int id)
{
	uint normal_material = octahedral_32(n, 15);
	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)
{
	data = data & (~(1 << 31u)); // mask out material
	vec3 n = i_octahedral_32(data, 15);
	return n;
}
#endif

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;
}

uint encode_component_tags(uint component_tags)
{
	return component_tags & 0xffffU;
}

void decode_component_tags(uint ct, out uint component_tags)
{
	component_tags = ct;
}

struct MetalnessRoughnessMeterialTags
{
	float metalness;
	float roughness;
	uint  material_index;
	uint  material_flag_overrides;
	uint  component_tags;
};

MetalnessRoughnessMeterialTags decode_metalness_roughness_material_tags(uvec4 v)
{
	MetalnessRoughnessMeterialTags o;
	decode_metalness_roughness_material(v.rg, o.metalness, o.roughness, o.material_index);
	decode_component_tags(v.b, o.component_tags);
	o.material_flag_overrides = v.a;
	return o;
}

// Each field is 16bit in the output
uvec4 encode_metalness_roughness_material_tags(MetalnessRoughnessMeterialTags v)
{
	uvec4 o;
	o.rg = encode_metalness_roughness_material(v.metalness, v.roughness, v.material_index);
	o.b  = encode_component_tags(v.component_tags);
	o.a  = v.material_flag_overrides;

	return o;
}

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

// NOTE: Some of these can be overriden by the material shader, but at the moment
// we are limited to just 4 attributes, so keep them at the beginning. These should
// be attributes which can actually be per-pixel, not per-material, like rt-reflectivity
#define MaterialFlag_Reflective 1
#define MaterialFlag_OverrideFlags 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
#define MaterialFlag_ParticleLighting 8192
#define MaterialFlag_SpecularVoxelLighting 16384
#define MaterialFlag_DiffuseVoxelLighting 32768
#define MaterialFlag_ReprojectLighting 65536

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;
	vec3  direction;	// world space
	vec3  position;		// world space
	vec3  up;			// ltc/area
	vec3  right;		// ltc/area
	vec2  dimensions;	// ltc/area

	uint  lighting_exclusion_tags;
	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   downsampled_shadowmap_sampler;	// only for lights without cascades
	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 (scalar, 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;
	mat4 mViewInv;
	vec3 vCameraPosition;
};

vec3 vector_transform_by_mat43(vec3 v, mat4 m)
{
	m[0][3] = 0.0;
	m[1][3] = 0.0;
	m[2][3] = 0.0;
	m[3][3] = 1.0;
	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;
}

vec4 vector_transform_by_mat_projection(vec3 v, mat4 m)
{
	// handle perspective and ortho projections
#if 1
	m[0][1] = 0.0;
	m[0][2] = 0.0;
	m[0][3] = 0.0;
	m[1][0] = 0.0;
	m[1][2] = 0.0;
	m[1][3] = 0.0;
	//m[2][0] = 0.0; // jitters
	//m[2][1] = 0.0;
#endif

	return (m * vec4(v, 1.0));
}

mat4 mat_identity()
{
	return mat4(1.0);
}

mat4 mat_mul_affine(mat4 ma, mat4 mb)
{
	return ma * mb;
}

#include "commons_geometry_information.glsl"

// NOTE: Is this optimization worth it even if buffer is deduced to be read only? Maybe on NV and maybe on very simple shaders?
// TODO: This should not be declared here...
#ifndef NO_GEOMETRY_INFORMATION_BINDING
#ifndef GEOMETRY_INFORMATION_STATIC
layout (row_major, scalar) buffer GeometryInformationBuffer {
	GeometryInformation geometry_information;
};
#else
layout (row_major, scalar) uniform GeometryInformationBuffer {
	GeometryInformation geometry_information;
};
#endif
#endif

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


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

struct ModifierFactor
{
	
	vec3  position;			// 3d position, need to decide if this will be world (and then modifiers have to be transformed) or local
	vec3  normal;			// only valid for vertices now, might make it also work for instances?

	// Provide vtx-attribute-like values here, this is to workaround how we handle lack of access to meshes. This bloats it though

	vec2  uv0;
	vec4  color;
	uint  custom0;			// as uint, can be float but then needs to be converted. no idea how to handle these customs yet, so yeah, here we are
	uint  custom1;

	//

	float factor;			// current value for the modifier factor. initially set to 1.0
	uint  id;				// can be either a vertex index or particle index in global buffer
	uint  hash;				// hash value (if available) trying to identify an instance independently of place in a buffer. more of a spatial hash value
	uint  instance_id;		// for components that support it in addition to the normal 'id'. like a second level
	bool  is_spawned;		// if the instance is being created during this frame/invocation

	bool  is_discarded;		// NOTE: This is experimental, for the instancing code as of this moment

	bool  has_world_position;
	vec3  world_position;	// some components might have this accessible without performing complex transforms...
};

ModifierFactor modifier_factor_defaults()
{
	ModifierFactor mf;
	mf.position           = vec3(0.0);
	mf.normal             = vec3(0.0, 1.0, 0.0);
	mf.uv0                = vec2(0.0, 0.0);
	mf.custom0            = 0;
	mf.custom1            = 0;
	mf.color              = vec4(1.0f);

	mf.factor             = 1.0;
	mf.id                 = 0;
	mf.hash               = 0;
	mf.instance_id        = 0;
	mf.is_spawned         = false;
	mf.is_discarded       = false;
	mf.has_world_position = false;
	mf.world_position     = vec3(0.0);

	return mf;
}

// Functions related to modifier factor functions. Currently in commons because we don't have better place for it:(
const uint MFF_Keep     = 0;	// don't touch the factor
const uint MFF_Override = 1;	// replace previous value
const uint MFF_Add      = 2;
const uint MFF_Subtract = 3;
const uint MFF_Multiply = 4;

void modifier_process_modifier_factor(inout ModifierFactor modifier_factor, uint modifier_factor_function, float factor)
{
	if( modifier_factor_function == MFF_Keep)
	{
	}
	if( modifier_factor_function == MFF_Override)
	{
		modifier_factor.factor = factor;
	}
	if( modifier_factor_function == MFF_Add)
	{
		modifier_factor.factor += factor;
	}
	if( modifier_factor_function == MFF_Subtract)
	{
		modifier_factor.factor -= factor;
	}
	if( modifier_factor_function == MFF_Multiply)
	{
		modifier_factor.factor *= factor;
	}
}

// composite code

const uint TB_Add = 0;
const uint TB_Blend = 1;

struct DeferredCompositeSetup
{
	vec4  ambient_color;
	vec4  fill_color;
	vec4  fog_color;

	float fog_range;
	float fog_height;
	float fog_height_density;
	float occlusion_strength;
	
	float occlusion_specular_from_diffuse;	// in case of missing info about specular occlusion, allow faking it
	float global_illumination_strength;
	float global_illumination_base_strength;
	float occlusion_base_strength;
	float ssr_strength;
	
	float sso_strength;
	float volumetric_fog_strength;
	float volumetric_fog_intensity;
	float rt_strength;
	
	float ibl_intensity;
	uint  transparency_blend;
	float taa_blend;
	float taa_gamma;
	float _pad0;
	float _pad1;
	float _pad2;

	vec2  taa_jitter;
};

//

struct CoordinateSystemTrasforms
{
	mat4 mat_local_to_model;
	mat4 mat_local_to_instance;
	mat4 mat_local_to_model_inv;
};

#endif