/**
 * Compiles a new shader, and creates an opengl_shader_t that will be put into the GL_shader vector
 * if compilation is successful.
 * This function is used for main (i.e. model rendering) and particle shaders, post processing shaders use their own infrastructure
 *
 * @param flags		Combination of SDR_* flags
 */
void opengl_compile_main_shader(int flags) {
	char *vert = NULL, *frag = NULL;

	mprintf(("Compiling new shader:\n"));

	bool in_error = false;
	opengl_shader_t new_shader;

	// choose appropriate files
	char vert_name[NAME_LENGTH];
	char frag_name[NAME_LENGTH];

	if (flags & SDR_FLAG_SOFT_QUAD) {
		strcpy_s( vert_name, "soft-v.sdr");
		strcpy_s( frag_name, "soft-f.sdr");
	} else {
		strcpy_s( vert_name, "main-v.sdr");
		strcpy_s( frag_name, "main-f.sdr");
	}

	// read vertex shader
	if ( (vert = opengl_load_shader(vert_name, flags)) == NULL ) {
		in_error = true;
		goto Done;
	}

	// read fragment shader
	if ( (frag = opengl_load_shader(frag_name, flags)) == NULL ) {
		in_error = true;
		goto Done;
	}

	Verify( vert != NULL );
	Verify( frag != NULL );

	new_shader.program_id = opengl_shader_create(vert, frag);

	if ( !new_shader.program_id ) {
		in_error = true;
		goto Done;
	}

	new_shader.flags = flags;

	opengl_shader_set_current( &new_shader );
	
	mprintf(("Shader features:\n"));

	//Init all the uniforms
	if (new_shader.flags & SDR_FLAG_SOFT_QUAD) {
		for (int j = 0; j < Particle_shader_flag_references; j++) {
			if (new_shader.flags == GL_Uniform_Reference_Particle[j].flag) {
				int k;

			// Equality check needed because the combination of SDR_FLAG_SOFT_QUAD and SDR_FLAG_DISTORTION define something very different
			// than just SDR_FLAG_SOFT_QUAD alone
				for (k = 0; k < GL_Uniform_Reference_Particle[j].num_uniforms; k++) {
					opengl_shader_init_uniform( GL_Uniform_Reference_Particle[j].uniforms[k] );
				}

				for (k = 0; k < GL_Uniform_Reference_Particle[j].num_attributes; k++) {
					opengl_shader_init_attribute( GL_Uniform_Reference_Particle[j].attributes[k] );
				}

				mprintf(("   %s\n", GL_Uniform_Reference_Particle[j].name));
			}
		}
	} else {
		for (int j = 0; j < Main_shader_flag_references; j++) {
			if (new_shader.flags & GL_Uniform_Reference_Main[j].flag) {
				if (GL_Uniform_Reference_Main[j].num_uniforms > 0) {
					for (int k = 0; k < GL_Uniform_Reference_Main[j].num_uniforms; k++) {
						opengl_shader_init_uniform( GL_Uniform_Reference_Main[j].uniforms[k] );
					}
				}

				if (GL_Uniform_Reference_Main[j].num_attributes > 0) {
					for (int k = 0; k < GL_Uniform_Reference_Main[j].num_attributes; k++) {
						opengl_shader_init_attribute( GL_Uniform_Reference_Main[j].attributes[k] );
					}
				}

				mprintf(("   %s\n", GL_Uniform_Reference_Main[j].name));
			}
		}
	}

	opengl_shader_set_current();

	// add it to our list of embedded shaders
	GL_shader.push_back( new_shader );

Done:
	if (vert != NULL) {
		vm_free(vert);
		vert = NULL;
	}

	if (frag != NULL) {
		vm_free(frag);
		frag = NULL;
	}

	if (in_error) {
		// shut off relevant usage things ...
		bool dealt_with = false;

		if (flags & SDR_FLAG_HEIGHT_MAP) {
			mprintf(("  Shader in_error!  Disabling height maps!\n"));
			Cmdline_height = 0;
			dealt_with = true;
		}

		if (flags & SDR_FLAG_NORMAL_MAP) {
			mprintf(("  Shader in_error!  Disabling normal maps and height maps!\n"));
			Cmdline_height = 0;
			Cmdline_normal = 0;
			dealt_with = true;
		}

		if (!dealt_with) {
			if (flags == 0) {
				mprintf(("  Shader in_error!  Disabling GLSL!\n"));

				Use_GLSL = 0;
				Cmdline_height = 0;
				Cmdline_normal = 0;

				GL_shader.clear();
			} else {
				// We died on a lighting shader, probably due to instruction count.
				// Drop down to a special var that will use fixed-function rendering
				// but still allow for post-processing to work
				mprintf(("  Shader in_error!  Disabling GLSL model rendering!\n"));
				Use_GLSL = 1;
				Cmdline_height = 0;
				Cmdline_normal = 0;
			}
		}
	}
}
/**
 * Compiles a new shader, and creates an opengl_shader_t that will be put into the GL_shader vector
 * if compilation is successful.
 *
 * @param sdr		Identifier defined with the program we wish to compile
 * @param flags		Combination of SDR_* flags
 */
int opengl_compile_shader(shader_type sdr, uint flags)
{
	int sdr_index = -1;
	int empty_idx;
	opengl_shader_t new_shader;

	Assert(sdr < NUM_SHADER_TYPES);

	opengl_shader_type_t *sdr_info = &GL_shader_types[sdr];

	mprintf(("Compiling new shader:\n"));
	mprintf(("	%s\n", sdr_info->description));

	// figure out if the variant requested needs a geometry shader
	bool use_geo_sdr = false;

	// do we even have a geometry shader?
	if ( sdr_info->geo != NULL ) {
		for (int i = 0; i < GL_num_shader_variants; ++i) {
			opengl_shader_variant_t *variant = &GL_shader_variants[i];

			if (variant->type_id == sdr && flags & variant->flag && variant->use_geometry_sdr) {
				use_geo_sdr = true;
				break;
			}
		}
	}

	auto vertex_content = opengl_get_shader_content(sdr_info->type_id, sdr_info->vert, flags);
	auto fragment_content = opengl_get_shader_content(sdr_info->type_id, sdr_info->frag, flags);
	SCP_vector<SCP_string> geom_content;

	if ( use_geo_sdr ) {
		if (!Is_Extension_Enabled(OGL_EXT_GEOMETRY_SHADER4)) {
			return -1;
		}

		// read geometry shader
		geom_content = opengl_get_shader_content(sdr_info->type_id, sdr_info->geo, flags);

		Current_geo_sdr_params = &sdr_info->geo_sdr_info;
	}

	new_shader.program_id = opengl_shader_create(vertex_content, fragment_content, geom_content);

	if (!new_shader.program_id) {
		return -1;
	}

	new_shader.shader = sdr_info->type_id;
	new_shader.flags = flags;

	opengl_shader_set_current(&new_shader);

	// initialize uniforms and attributes
	for ( int i = 0; i < sdr_info->num_uniforms; ++i ) {
		opengl_shader_init_uniform( sdr_info->uniforms[i] );
	}

	for (int i = 0; i < sdr_info->num_attributes; ++i) {
		opengl_shader_init_attribute(sdr_info->attributes[i]);
	}

	// if this shader is POST_PROCESS_MAIN, hack in the user-defined flags
	if ( sdr_info->type_id == SDR_TYPE_POST_PROCESS_MAIN ) {
		opengl_post_init_uniforms(flags);
	}

	mprintf(("Shader Variant Features:\n"));

	// initialize all uniforms and attributes that are specific to this variant
	for ( int i = 0; i < GL_num_shader_variants; ++i ) {
		opengl_shader_variant_t &variant = GL_shader_variants[i];

		if ( sdr_info->type_id == variant.type_id && variant.flag & flags ) {
			for (int j = 0; j < variant.num_uniforms; ++j) {
				opengl_shader_init_uniform(variant.uniforms[j]);
			}

			for (int j = 0; j < variant.num_attributes; ++j) {
				opengl_shader_init_attribute(variant.attributes[j]);
			}

			mprintf(("	%s\n", variant.description));
		}
	}

	opengl_shader_set_current();

	// add it to our list of embedded shaders
	// see if we have empty shader slots
	empty_idx = -1;
	for ( int i = 0; i < (int)GL_shader.size(); ++i ) {
		if ( GL_shader[i].shader == NUM_SHADER_TYPES ) {
			empty_idx = i;
			break;
		}
	}

	// then insert it at an empty slot or at the end
	if ( empty_idx >= 0 ) {
		GL_shader[empty_idx] = new_shader;
		sdr_index = empty_idx;
	} else {
		sdr_index = GL_shader.size();
		GL_shader.push_back(new_shader);
	}

	return sdr_index;
}