/*
 * Mesa 3-D graphics library
 * Version:  6.5
 *
 * Copyright (C) 2005-2006  Brian Paul   All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * BRIAN PAUL BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
 * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

/**
 * \file slang_assemble_typeinfo.c
 * slang type info
 * \author Michal Krol
 */

#include "imports.h"
#include "slang_assemble.h"
#include "slang_compile.h"

/*
 * slang_type_specifier
 */

GLvoid slang_type_specifier_ctr (slang_type_specifier *self)
{
	self->type = slang_spec_void;
	self->_struct = NULL;
	self->_array = NULL;
}

GLvoid slang_type_specifier_dtr (slang_type_specifier *self)
{
	if (self->_struct != NULL)
	{
		slang_struct_destruct (self->_struct);
		slang_alloc_free (self->_struct);
	}
	if (self->_array != NULL)
	{
		slang_type_specifier_dtr (self->_array);
		slang_alloc_free (self->_array);
	}
}

GLboolean slang_type_specifier_copy (slang_type_specifier *x, const slang_type_specifier *y)
{
	slang_type_specifier z;

	slang_type_specifier_ctr (&z);
	z.type = y->type;
	if (z.type == slang_spec_struct)
	{
		z._struct = (slang_struct *) slang_alloc_malloc (sizeof (slang_struct));
		if (z._struct == NULL)
		{
			slang_type_specifier_dtr (&z);
			return GL_FALSE;
		}
		if (!slang_struct_construct (z._struct))
		{
			slang_alloc_free (z._struct);
			slang_type_specifier_dtr (&z);
			return GL_FALSE;
		}
		if (!slang_struct_copy (z._struct, y->_struct))
		{
			slang_type_specifier_dtr (&z);
			return GL_FALSE;
		}
	}
	else if (z.type == slang_spec_array)
	{
		z._array = (slang_type_specifier *) slang_alloc_malloc (sizeof (slang_type_specifier));
		if (z._array == NULL)
		{
			slang_type_specifier_dtr (&z);
			return GL_FALSE;
		}
		slang_type_specifier_ctr (z._array);
		if (!slang_type_specifier_copy (z._array, y->_array))
		{
			slang_type_specifier_dtr (&z);
			return GL_FALSE;
		}
	}
	slang_type_specifier_dtr (x);
	*x = z;
	return GL_TRUE;
}

GLboolean slang_type_specifier_equal (const slang_type_specifier *x, const slang_type_specifier *y)
{
	if (x->type != y->type)
		return 0;
	if (x->type == slang_spec_struct)
		return slang_struct_equal (x->_struct, y->_struct);
	if (x->type == slang_spec_array)
		return slang_type_specifier_equal (x->_array, y->_array);
	return 1;
}

/* slang_assembly_typeinfo */

GLboolean slang_assembly_typeinfo_construct (slang_assembly_typeinfo *ti)
{
	slang_type_specifier_ctr (&ti->spec);
	ti->array_len = 0;
	return GL_TRUE;
}

GLvoid slang_assembly_typeinfo_destruct (slang_assembly_typeinfo *ti)
{
	slang_type_specifier_dtr (&ti->spec);
}

/* _slang_typeof_operation() */

static GLboolean typeof_existing_function (const char *name, slang_operation *params,
	GLuint num_params, slang_assembly_name_space *space, slang_type_specifier *spec,
	slang_atom_pool *atoms)
{
	slang_atom atom;
	GLboolean exists;

	atom = slang_atom_pool_atom (atoms, name);
	if (!_slang_typeof_function (atom, params, num_params, space, spec, &exists, atoms))
		return GL_FALSE;
	return exists;
}

GLboolean _slang_typeof_operation (slang_assemble_ctx *A, slang_operation *op,
	slang_assembly_typeinfo *ti)
{
	return _slang_typeof_operation_ (op, &A->space, ti, A->atoms);
}

GLboolean _slang_typeof_operation_ (slang_operation *op, slang_assembly_name_space *space,
	slang_assembly_typeinfo *ti, slang_atom_pool *atoms)
{
	ti->can_be_referenced = GL_FALSE;
	ti->is_swizzled = GL_FALSE;

	switch (op->type)
	{
	case slang_oper_block_no_new_scope:
	case slang_oper_block_new_scope:
	case slang_oper_variable_decl:
	case slang_oper_asm:
	case slang_oper_break:
	case slang_oper_continue:
	case slang_oper_discard:
	case slang_oper_return:
	case slang_oper_if:
	case slang_oper_while:
	case slang_oper_do:
	case slang_oper_for:
	case slang_oper_void:
		ti->spec.type = slang_spec_void;
		break;
	case slang_oper_expression:
	case slang_oper_assign:
	case slang_oper_addassign:
	case slang_oper_subassign:
	case slang_oper_mulassign:
	case slang_oper_divassign:
	case slang_oper_preincrement:
	case slang_oper_predecrement:
		if (!_slang_typeof_operation_ (op->children, space, ti, atoms))
			return 0;
		break;
	case slang_oper_literal_bool:
	case slang_oper_logicalor:
	case slang_oper_logicalxor:
	case slang_oper_logicaland:
	case slang_oper_equal:
	case slang_oper_notequal:
	case slang_oper_less:
	case slang_oper_greater:
	case slang_oper_lessequal:
	case slang_oper_greaterequal:
	case slang_oper_not:
		ti->spec.type = slang_spec_bool;
		break;
	case slang_oper_literal_int:
		ti->spec.type = slang_spec_int;
		break;
	case slang_oper_literal_float:
		ti->spec.type = slang_spec_float;
		break;
	case slang_oper_identifier:
		{
			slang_variable *var;

			var = _slang_locate_variable (op->locals, op->a_id, GL_TRUE);
			if (var == NULL)
				return GL_FALSE;
			if (!slang_type_specifier_copy (&ti->spec, &var->type.specifier))
				return GL_FALSE;
			ti->can_be_referenced = GL_TRUE;
			ti->array_len = var->array_len;
		}
		break;
	case slang_oper_sequence:
		/* TODO: check [0] and [1] if they match */
		if (!_slang_typeof_operation_ (&op->children[1], space, ti, atoms))
			return GL_FALSE;
		ti->can_be_referenced = GL_FALSE;
		ti->is_swizzled = GL_FALSE;
		break;
	/*case slang_oper_modassign:*/
	/*case slang_oper_lshassign:*/
	/*case slang_oper_rshassign:*/
	/*case slang_oper_orassign:*/
	/*case slang_oper_xorassign:*/
	/*case slang_oper_andassign:*/
	case slang_oper_select:
		/* TODO: check [1] and [2] if they match */
		if (!_slang_typeof_operation_ (&op->children[1], space, ti, atoms))
			return GL_FALSE;
		ti->can_be_referenced = GL_FALSE;
		ti->is_swizzled = GL_FALSE;
		break;
	/*case slang_oper_bitor:*/
	/*case slang_oper_bitxor:*/
	/*case slang_oper_bitand:*/
	/*case slang_oper_lshift:*/
	/*case slang_oper_rshift:*/
	case slang_oper_add:
		if (!typeof_existing_function ("+", op->children, 2, space, &ti->spec, atoms))
			return GL_FALSE;
		break;
	case slang_oper_subtract:
		if (!typeof_existing_function ("-", op->children, 2, space, &ti->spec, atoms))
			return GL_FALSE;
		break;
	case slang_oper_multiply:
		if (!typeof_existing_function ("*", op->children, 2, space, &ti->spec, atoms))
			return GL_FALSE;
		break;
	case slang_oper_divide:
		if (!typeof_existing_function ("/", op->children, 2, space, &ti->spec, atoms))
			return GL_FALSE;
		break;
	/*case slang_oper_modulus:*/
	case slang_oper_plus:
		if (!_slang_typeof_operation_ (op->children, space, ti, atoms))
			return GL_FALSE;
		ti->can_be_referenced = GL_FALSE;
		ti->is_swizzled = GL_FALSE;
		break;
	case slang_oper_minus:
		if (!typeof_existing_function ("-", op->children, 1, space, &ti->spec, atoms))
			return GL_FALSE;
		break;
	/*case slang_oper_complement:*/
	case slang_oper_subscript:
		{
			slang_assembly_typeinfo _ti;

			if (!slang_assembly_typeinfo_construct (&_ti))
				return GL_FALSE;
			if (!_slang_typeof_operation_ (op->children, space, &_ti, atoms))
			{
				slang_assembly_typeinfo_destruct (&_ti);
				return GL_FALSE;
			}
			ti->can_be_referenced = _ti.can_be_referenced;
			if (_ti.spec.type == slang_spec_array)
			{
				if (!slang_type_specifier_copy (&ti->spec, _ti.spec._array))
				{
					slang_assembly_typeinfo_destruct (&_ti);
					return GL_FALSE;
				}
			}
			else
			{
				if (!_slang_type_is_vector (_ti.spec.type) && !_slang_type_is_matrix (_ti.spec.type))
				{
					slang_assembly_typeinfo_destruct (&_ti);
					return GL_FALSE;
				}
				ti->spec.type = _slang_type_base (_ti.spec.type);
			}
			slang_assembly_typeinfo_destruct (&_ti);
		}
		break;
	case slang_oper_call:
		{
			GLboolean exists;

			if (!_slang_typeof_function (op->a_id, op->children, op->num_children, space, &ti->spec,
					&exists, atoms))
				return GL_FALSE;
			if (!exists)
			{
				slang_struct *s = slang_struct_scope_find (space->structs, op->a_id, GL_TRUE);
				if (s != NULL)
				{
					ti->spec.type = slang_spec_struct;
					ti->spec._struct = (slang_struct *) slang_alloc_malloc (sizeof (slang_struct));
					if (ti->spec._struct == NULL)
						return GL_FALSE;
					if (!slang_struct_construct (ti->spec._struct))
					{
						slang_alloc_free (ti->spec._struct);
						ti->spec._struct = NULL;
						return GL_FALSE;
					}
					if (!slang_struct_copy (ti->spec._struct, s))
						return GL_FALSE;
				}
				else
				{
					const char *name;
					slang_type_specifier_type type;

					name = slang_atom_pool_id (atoms, op->a_id);
					type = slang_type_specifier_type_from_string (name);
					if (type == slang_spec_void)
						return GL_FALSE;
					ti->spec.type = type;
				}
			}
		}
		break;
	case slang_oper_field:
		{
			slang_assembly_typeinfo _ti;

			if (!slang_assembly_typeinfo_construct (&_ti))
				return GL_FALSE;
			if (!_slang_typeof_operation_ (op->children, space, &_ti, atoms))
			{
				slang_assembly_typeinfo_destruct (&_ti);
				return GL_FALSE;
			}
			if (_ti.spec.type == slang_spec_struct)
			{
				slang_variable *field;
				
				field = _slang_locate_variable (_ti.spec._struct->fields, op->a_id, GL_FALSE);
				if (field == NULL)
				{
					slang_assembly_typeinfo_destruct (&_ti);
					return GL_FALSE;
				}
				if (!slang_type_specifier_copy (&ti->spec, &field->type.specifier))
				{
					slang_assembly_typeinfo_destruct (&_ti);
					return GL_FALSE;
				}
				ti->can_be_referenced = _ti.can_be_referenced;
			}
			else
			{
				GLuint rows;
				const char *swizzle;
				slang_type_specifier_type base;

				/* determine the swizzle of the field expression */
				if (!_slang_type_is_vector (_ti.spec.type))
				{
					slang_assembly_typeinfo_destruct (&_ti);
					return GL_FALSE;
				}
				rows = _slang_type_dim (_ti.spec.type);
				swizzle = slang_atom_pool_id (atoms, op->a_id);
				if (!_slang_is_swizzle (swizzle, rows, &ti->swz))
				{
					slang_assembly_typeinfo_destruct (&_ti);
					return GL_FALSE;
				}
				ti->is_swizzled = GL_TRUE;
				ti->can_be_referenced = _ti.can_be_referenced && _slang_is_swizzle_mask (&ti->swz,
					rows);
				if (_ti.is_swizzled)
				{
					slang_swizzle swz;

					/* swizzle the swizzle */
					_slang_multiply_swizzles (&swz, &_ti.swz, &ti->swz);
					ti->swz = swz;
				}
				base = _slang_type_base (_ti.spec.type);
				switch (ti->swz.num_components)
				{
				case 1:
					ti->spec.type = base;
					break;
				case 2:
					switch (base)
					{
					case slang_spec_float:
						ti->spec.type = slang_spec_vec2;
						break;
					case slang_spec_int:
						ti->spec.type = slang_spec_ivec2;
						break;
					case slang_spec_bool:
						ti->spec.type = slang_spec_bvec2;
						break;
					default:
						break;
					}
					break;
				case 3:
					switch (base)
					{
					case slang_spec_float:
						ti->spec.type = slang_spec_vec3;
						break;
					case slang_spec_int:
						ti->spec.type = slang_spec_ivec3;
						break;
					case slang_spec_bool:
						ti->spec.type = slang_spec_bvec3;
						break;
					default:
						break;
					}
					break;
				case 4:
					switch (base)
					{
					case slang_spec_float:
						ti->spec.type = slang_spec_vec4;
						break;
					case slang_spec_int:
						ti->spec.type = slang_spec_ivec4;
						break;
					case slang_spec_bool:
						ti->spec.type = slang_spec_bvec4;
						break;
					default:
						break;
					}
					break;
				default:
					break;
				}
			}
			slang_assembly_typeinfo_destruct (&_ti);
		}
		break;
	case slang_oper_postincrement:
	case slang_oper_postdecrement:
		if (!_slang_typeof_operation_ (op->children, space, ti, atoms))
			return GL_FALSE;
		ti->can_be_referenced = GL_FALSE;
		ti->is_swizzled = GL_FALSE;
		break;
	default:
		return GL_FALSE;
	}

	return GL_TRUE;
}

/* _slang_typeof_function() */

GLboolean _slang_typeof_function (slang_atom a_name, slang_operation *params, GLuint num_params,
	slang_assembly_name_space *space, slang_type_specifier *spec, GLboolean *exists,
	slang_atom_pool *atoms)
{
	slang_function *fun;
	
	fun = _slang_locate_function (space->funcs, a_name, params, num_params, space, atoms);
	*exists = fun != NULL;
	if (fun == NULL)
		return GL_TRUE;
	return slang_type_specifier_copy (spec, &fun->header.type.specifier);
}

/* _slang_type_is_matrix() */

GLboolean _slang_type_is_matrix (slang_type_specifier_type ty)
{
	switch (ty)
	{
	case slang_spec_mat2:
	case slang_spec_mat3:
	case slang_spec_mat4:
		return GL_TRUE;
	default:
		return GL_FALSE;
	}
}

/* _slang_type_is_vector() */

GLboolean _slang_type_is_vector (slang_type_specifier_type ty)
{
	switch (ty)
	{
	case slang_spec_vec2:
	case slang_spec_vec3:
	case slang_spec_vec4:
	case slang_spec_ivec2:
	case slang_spec_ivec3:
	case slang_spec_ivec4:
	case slang_spec_bvec2:
	case slang_spec_bvec3:
	case slang_spec_bvec4:
		return GL_TRUE;
	default:
		return GL_FALSE;
	}
}

/* _slang_type_base_of_vector() */

slang_type_specifier_type _slang_type_base (slang_type_specifier_type ty)
{
	switch (ty)
	{
	case slang_spec_float:
	case slang_spec_vec2:
	case slang_spec_vec3:
	case slang_spec_vec4:
		return slang_spec_float;
	case slang_spec_int:
	case slang_spec_ivec2:
	case slang_spec_ivec3:
	case slang_spec_ivec4:
		return slang_spec_int;
	case slang_spec_bool:
	case slang_spec_bvec2:
	case slang_spec_bvec3:
	case slang_spec_bvec4:
		return slang_spec_bool;
	case slang_spec_mat2:
		return slang_spec_vec2;
	case slang_spec_mat3:
		return slang_spec_vec3;
	case slang_spec_mat4:
		return slang_spec_vec4;
	default:
		return slang_spec_void;
	}
}

/* _slang_type_dim */

GLuint _slang_type_dim (slang_type_specifier_type ty)
{
	switch (ty)
	{
	case slang_spec_float:
	case slang_spec_int:
	case slang_spec_bool:
		return 1;
	case slang_spec_vec2:
	case slang_spec_ivec2:
	case slang_spec_bvec2:
	case slang_spec_mat2:
		return 2;
	case slang_spec_vec3:
	case slang_spec_ivec3:
	case slang_spec_bvec3:
	case slang_spec_mat3:
		return 3;
	case slang_spec_vec4:
	case slang_spec_ivec4:
	case slang_spec_bvec4:
	case slang_spec_mat4:
		return 4;
	default:
		return 0;
	}
}

