/*
 * 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_constructor.c
 * slang constructor and vector swizzle assembler
 * \author Michal Krol
 */

#include "imports.h"
#include "slang_assemble.h"
#include "slang_storage.h"

/* _slang_is_swizzle() */

GLboolean _slang_is_swizzle (const char *field, GLuint rows, slang_swizzle *swz)
{
	GLuint i;
	GLboolean xyzw = GL_FALSE, rgba = GL_FALSE, stpq = GL_FALSE;

	/* the swizzle can be at most 4-component long */
	swz->num_components = slang_string_length (field);
	if (swz->num_components > 4)
		return GL_FALSE;

	for (i = 0; i < swz->num_components; i++)
	{
		/* mark which swizzle group is used */
		switch (field[i])
		{
		case 'x':
		case 'y':
		case 'z':
		case 'w':
			xyzw = GL_TRUE;
			break;
		case 'r':
		case 'g':
		case 'b':
		case 'a':
			rgba = GL_TRUE;
			break;
		case 's':
		case 't':
		case 'p':
		case 'q':
			stpq = GL_TRUE;
			break;
		default:
			return GL_FALSE;
		}

		/* collect swizzle component */
		switch (field[i])
		{
		case 'x':
		case 'r':
		case 's':
			swz->swizzle[i] = 0;
			break;
		case 'y':
		case 'g':
		case 't':
			swz->swizzle[i] = 1;
			break;
		case 'z':
		case 'b':
		case 'p':
			swz->swizzle[i] = 2;
			break;
		case 'w':
		case 'a':
		case 'q':
			swz->swizzle[i] = 3;
			break;
		}

		/* check if the component is valid for given vector's row count */
		if (rows <= swz->swizzle[i])
			return GL_FALSE;
	}

	/* only one swizzle group can be used */
	if ((xyzw && rgba) || (xyzw && stpq) || (rgba && stpq))
		return GL_FALSE;

	return GL_TRUE;
}

/* _slang_is_swizzle_mask() */

GLboolean _slang_is_swizzle_mask (const slang_swizzle *swz, GLuint rows)
{
	GLuint i, c = 0;

	/* the swizzle may not be longer than the vector dim */
	if (swz->num_components > rows)
		return GL_FALSE;

	/* the swizzle components cannot be duplicated */
	for (i = 0; i < swz->num_components; i++)
	{
		if ((c & (1 << swz->swizzle[i])) != 0)
			return GL_FALSE;
		c |= 1 << swz->swizzle[i];
	}

	return GL_TRUE;
}

/* _slang_multiply_swizzles() */

GLvoid _slang_multiply_swizzles (slang_swizzle *dst, const slang_swizzle *left,
	const slang_swizzle *right)
{
	GLuint i;

	dst->num_components = right->num_components;
	for (i = 0; i < right->num_components; i++)
		dst->swizzle[i] = left->swizzle[right->swizzle[i]];
}

/* _slang_assemble_constructor() */

static GLboolean sizeof_argument (slang_assemble_ctx *A, GLuint *size, slang_operation *op)
{
	slang_assembly_typeinfo ti;
	GLboolean result = GL_FALSE;
	slang_storage_aggregate agg, flat_agg;

	if (!slang_assembly_typeinfo_construct (&ti))
		return GL_FALSE;
	if (!_slang_typeof_operation (A, op, &ti))
		goto end1;

	if (!slang_storage_aggregate_construct (&agg))
		goto end1;
	if (!_slang_aggregate_variable (&agg, &ti.spec, 0, A->space.funcs, A->space.structs,
			A->space.vars, A->mach, A->file, A->atoms))
		goto end2;

	if (!slang_storage_aggregate_construct (&flat_agg))
		goto end2;
	if (!_slang_flatten_aggregate (&flat_agg, &agg))
		goto end;

	*size = flat_agg.count * 4;

	result = GL_TRUE;
end:
	slang_storage_aggregate_destruct (&flat_agg);
end2:
	slang_storage_aggregate_destruct (&agg);
end1:
	slang_assembly_typeinfo_destruct (&ti);
	return result;
}

static GLboolean constructor_aggregate (slang_assemble_ctx *A, const slang_storage_aggregate *flat,
	slang_operation *op, GLuint garbage_size)
{
	slang_assembly_typeinfo ti;
	GLboolean result = GL_FALSE;
	slang_storage_aggregate agg, flat_agg;

	if (!slang_assembly_typeinfo_construct (&ti))
		return GL_FALSE;
	if (!_slang_typeof_operation (A, op, &ti))
		goto end1;

	if (!slang_storage_aggregate_construct (&agg))
		goto end1;
	if (!_slang_aggregate_variable (&agg, &ti.spec, 0, A->space.funcs, A->space.structs,
			A->space.vars, A->mach, A->file, A->atoms))
		goto end2;

	if (!slang_storage_aggregate_construct (&flat_agg))
		goto end2;
	if (!_slang_flatten_aggregate (&flat_agg, &agg))
		goto end;

	if (!_slang_assemble_operation (A, op, slang_ref_forbid))
		goto end;

	/* TODO: convert (generic) elements */

	/* free the garbage */
	if (garbage_size != 0)
	{
		GLuint i;

		/* move the non-garbage part to the end of the argument */
		if (!slang_assembly_file_push_label (A->file, slang_asm_addr_push, 0))
			goto end;
		for (i = flat_agg.count * 4 - garbage_size; i > 0; i -= 4)
		{
			if (!slang_assembly_file_push_label2 (A->file, slang_asm_float_move,
				garbage_size + i, i))
			{
				goto end;
			}
		}
		if (!slang_assembly_file_push_label (A->file, slang_asm_local_free, garbage_size + 4))
			goto end;
	}

	result = GL_TRUE;
end:
	slang_storage_aggregate_destruct (&flat_agg);
end2:
	slang_storage_aggregate_destruct (&agg);
end1:
	slang_assembly_typeinfo_destruct (&ti);
	return result;
}

GLboolean _slang_assemble_constructor (slang_assemble_ctx *A, slang_operation *op)
{
	slang_assembly_typeinfo ti;
	GLboolean result = GL_FALSE;
	slang_storage_aggregate agg, flat;
	GLuint size, i;
	GLuint arg_sums[2];

	/* get typeinfo of the constructor (the result of constructor expression) */
	if (!slang_assembly_typeinfo_construct (&ti))
		return GL_FALSE;
	if (!_slang_typeof_operation (A, op, &ti))
		goto end1;

	/* create an aggregate of the constructor */
	if (!slang_storage_aggregate_construct (&agg))
		goto end1;
	if (!_slang_aggregate_variable (&agg, &ti.spec, 0, A->space.funcs, A->space.structs,
			A->space.vars, A->mach, A->file, A->atoms))
		goto end2;

	/* calculate size of the constructor */
	size = _slang_sizeof_aggregate (&agg);

	/* flatten the constructor */
	if (!slang_storage_aggregate_construct (&flat))
		goto end2;
	if (!_slang_flatten_aggregate (&flat, &agg))
		goto end;

	/* collect the last two constructor's argument size sums */
	arg_sums[0] = 0;	/* will hold all but the last argument's size sum */
	arg_sums[1] = 0;	/* will hold all argument's size sum */
	for (i = 0; i < op->num_children; i++)
	{
		GLuint arg_size;

		if (!sizeof_argument (A, &arg_size, &op->children[i]))
			goto end;
		if (i > 0)
			arg_sums[0] = arg_sums[1];
		arg_sums[1] += arg_size;
	}

	/* check if there are too many arguments */
	if (arg_sums[0] >= size)
	{
		/* TODO: info log: too many arguments in constructor list */
		goto end;
	}

	/* check if there are too few arguments */
	if (arg_sums[1] < size)
	{
		/* TODO: info log: too few arguments in constructor list */
		goto end;
	}

	/* traverse the children that form the constructor expression */
	for (i = op->num_children; i > 0; i--)
	{
		GLuint garbage_size;

		/* the last argument may be too big - calculate the unnecessary data size */
		if (i == op->num_children)
			garbage_size = arg_sums[1] - size;
		else
			garbage_size = 0;

		if (!constructor_aggregate (A, &flat, &op->children[i - 1], garbage_size))
			goto end;
	}

	result = GL_TRUE;
end:
	slang_storage_aggregate_destruct (&flat);
end2:
	slang_storage_aggregate_destruct (&agg);
end1:
	slang_assembly_typeinfo_destruct (&ti);
	return result;
}

/* _slang_assemble_constructor_from_swizzle() */

GLboolean _slang_assemble_constructor_from_swizzle (slang_assemble_ctx *A, const slang_swizzle *swz,
	slang_type_specifier *spec, slang_type_specifier *master_spec)
{
	GLuint master_rows, i;

	master_rows = _slang_type_dim (master_spec->type);
	for (i = 0; i < master_rows; i++)
	{
		switch (_slang_type_base (master_spec->type))
		{
		case slang_spec_bool:
			if (!slang_assembly_file_push_label2 (A->file, slang_asm_bool_copy,
					(master_rows - i) * 4, i * 4))
				return GL_FALSE;
			break;
		case slang_spec_int:
			if (!slang_assembly_file_push_label2 (A->file, slang_asm_int_copy,
					(master_rows - i) * 4, i * 4))
				return GL_FALSE;
			break;
		case slang_spec_float:
			if (!slang_assembly_file_push_label2 (A->file, slang_asm_float_copy,
					(master_rows - i) * 4, i * 4))
				return GL_FALSE;
			break;
		default:
			break;
		}
	}
	if (!slang_assembly_file_push_label (A->file, slang_asm_local_free, 4))
		return GL_FALSE;
	for (i = swz->num_components; i > 0; i--)
	{
		GLuint n = i - 1;

		if (!slang_assembly_file_push_label2 (A->file, slang_asm_local_addr, A->local.swizzle_tmp, 16))
			return GL_FALSE;
		if (!slang_assembly_file_push_label (A->file, slang_asm_addr_push, swz->swizzle[n] * 4))
			return GL_FALSE;
		if (!slang_assembly_file_push (A->file, slang_asm_addr_add))
			return GL_FALSE;
		switch (_slang_type_base (master_spec->type))
		{
		case slang_spec_bool:
			if (!slang_assembly_file_push (A->file, slang_asm_bool_deref))
				return GL_FALSE;
			break;
		case slang_spec_int:
			if (!slang_assembly_file_push (A->file, slang_asm_int_deref))
				return GL_FALSE;
			break;
		case slang_spec_float:
			if (!slang_assembly_file_push (A->file, slang_asm_float_deref))
				return GL_FALSE;
			break;
		default:
			break;
		}
	}

	return GL_TRUE;
}

