/* valaccodeassignmentmodule.vala
 *
 * Copyright (C) 2006-2010  Jürg Billeter
 * Copyright (C) 2006-2008  Raffaele Sandrini
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.

 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.

 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 *
 * Author:
 * 	Jürg Billeter <j@bitron.ch>
 *	Raffaele Sandrini <raffaele@sandrini.ch>
 */

using GLib;

/**
 * The link between an assignment and generated code.
 */
public class Vala.CCodeAssignmentModule : CCodeMemberAccessModule {
	TargetValue emit_simple_assignment (Assignment assignment) {
		if (requires_destroy (assignment.left.value_type)) {
			/* unref old value */
			ccode.add_expression (destroy_value (assignment.left.target_value));
		}

		if (assignment.operator == AssignmentOperator.SIMPLE) {
			store_value (assignment.left.target_value, assignment.right.target_value, assignment.source_reference);
		} else {
			CCodeAssignmentOperator cop;

			switch (assignment.operator) {
			case AssignmentOperator.BITWISE_OR: cop = CCodeAssignmentOperator.BITWISE_OR; break;
			case AssignmentOperator.BITWISE_AND: cop = CCodeAssignmentOperator.BITWISE_AND; break;
			case AssignmentOperator.BITWISE_XOR: cop = CCodeAssignmentOperator.BITWISE_XOR; break;
			case AssignmentOperator.ADD: cop = CCodeAssignmentOperator.ADD; break;
			case AssignmentOperator.SUB: cop = CCodeAssignmentOperator.SUB; break;
			case AssignmentOperator.MUL: cop = CCodeAssignmentOperator.MUL; break;
			case AssignmentOperator.DIV: cop = CCodeAssignmentOperator.DIV; break;
			case AssignmentOperator.PERCENT:
				// FIXME Code duplication with CCodeBaseModule.visit_binary_expression()
				var cleft = get_cvalue (assignment.left);
				var cright = get_cvalue (assignment.right);
				if (assignment.value_type.equals (double_type)) {
					cfile.add_include ("math.h");
					var ccall = new CCodeFunctionCall (new CCodeIdentifier ("fmod"));
					ccall.add_argument (cleft);
					ccall.add_argument (cright);
					set_cvalue (assignment.right, ccall);
					cop = CCodeAssignmentOperator.SIMPLE;
				} else if (assignment.value_type.equals (float_type)) {
					cfile.add_include ("math.h");
					var ccall = new CCodeFunctionCall (new CCodeIdentifier ("fmodf"));
					ccall.add_argument (cleft);
					ccall.add_argument (cright);
					set_cvalue (assignment.right, ccall);
					cop = CCodeAssignmentOperator.SIMPLE;
				} else {
					cop = CCodeAssignmentOperator.PERCENT;
				}
				break;
			case AssignmentOperator.SHIFT_LEFT: cop = CCodeAssignmentOperator.SHIFT_LEFT; break;
			case AssignmentOperator.SHIFT_RIGHT: cop = CCodeAssignmentOperator.SHIFT_RIGHT; break;
			default: assert_not_reached ();
			}

			CCodeExpression codenode = new CCodeAssignment (get_cvalue (assignment.left), get_cvalue (assignment.right), cop);
			ccode.add_expression (codenode);
		}

		if (assignment.left.value_type is ArrayType && (((ArrayType) assignment.left.value_type).inline_allocated)) {
			unowned Variable variable = (Variable) assignment.left.symbol_reference;
			return load_variable (variable, assignment.left.target_value);
		} else {
			return assignment.left.target_value;
		}
	}

	public override void visit_assignment (Assignment assignment) {
		if (assignment.left.error || assignment.right.error) {
			assignment.error = true;
			return;
		}

		if (assignment.left.symbol_reference is Property) {
			unowned MemberAccess ma = (MemberAccess) assignment.left;
			unowned Property prop = (Property) assignment.left.symbol_reference;

			store_property (prop, ma.inner, assignment.right.target_value);
			assignment.target_value = assignment.right.target_value;
		} else if (assignment.left.symbol_reference is Variable && is_simple_struct_creation ((Variable) assignment.left.symbol_reference, assignment.right)) {
			// delegate to visit_object_creation_expression
		} else {
			assignment.target_value = emit_simple_assignment (assignment);
		}
	}

	public override void store_value (TargetValue lvalue, TargetValue value, SourceReference? source_reference = null) {
		var array_type = lvalue.value_type as ArrayType;

		if (array_type != null && array_type.fixed_length) {
			cfile.add_include ("string.h");

			// it is necessary to use memcpy for fixed-length (stack-allocated) arrays
			// simple assignments do not work in C
			var sizeof_call = new CCodeFunctionCall (new CCodeIdentifier ("sizeof"));
			sizeof_call.add_argument (new CCodeIdentifier (get_ccode_name (array_type.element_type)));
			var size = new CCodeBinaryExpression (CCodeBinaryOperator.MUL, get_ccodenode (array_type.length), sizeof_call);

			var ccopy = new CCodeFunctionCall (new CCodeIdentifier ("memcpy"));
			ccopy.add_argument (get_cvalue_ (lvalue));
			ccopy.add_argument (get_cvalue_ (value));
			ccopy.add_argument (size);
			ccode.add_expression (ccopy);

			return;
		}

		var cexpr = get_cvalue_ (value);
		if (get_ctype (lvalue) != null) {
			cexpr = new CCodeCastExpression (cexpr, get_ctype (lvalue));
		}

		ccode.add_assignment (get_cvalue_ (lvalue), cexpr);

		if (array_type != null && ((GLibValue) lvalue).array_length_cvalues != null) {
			var glib_value = (GLibValue) value;
			if (glib_value.array_length_cvalues != null) {
				for (int dim = 1; dim <= array_type.rank; dim++) {
					ccode.add_assignment (get_array_length_cvalue (lvalue, dim), get_array_length_cvalue (value, dim));
				}
			} else if (glib_value.array_null_terminated) {
				requires_array_length = true;
				var len_call = new CCodeFunctionCall (new CCodeIdentifier ("_vala_array_length"));
				len_call.add_argument (get_cvalue_ (value));

				ccode.add_assignment (get_array_length_cvalue (lvalue, 1), len_call);
			} else {
				for (int dim = 1; dim <= array_type.rank; dim++) {
					ccode.add_assignment (get_array_length_cvalue (lvalue, dim), new CCodeConstant ("-1"));
				}
			}

			if (array_type.rank == 1 && get_array_size_cvalue (lvalue) != null) {
				ccode.add_assignment (get_array_size_cvalue (lvalue), get_array_length_cvalue (lvalue, 1));
			}
		}

		var delegate_type = lvalue.value_type as DelegateType;
		if (delegate_type != null && delegate_type.delegate_symbol.has_target) {
			var lvalue_target = get_delegate_target_cvalue (lvalue);
			var rvalue_target = get_delegate_target_cvalue (value);
			if (lvalue_target != null) {
				if (rvalue_target != null) {
					ccode.add_assignment (lvalue_target, rvalue_target);
				} else {
					Report.error (source_reference, "Assigning delegate without required target in scope");
					ccode.add_assignment (lvalue_target, new CCodeInvalidExpression ());
				}
				var lvalue_destroy_notify = get_delegate_target_destroy_notify_cvalue (lvalue);
				var rvalue_destroy_notify = get_delegate_target_destroy_notify_cvalue (value);
				if (lvalue_destroy_notify != null) {
					if (rvalue_destroy_notify != null) {
						ccode.add_assignment (lvalue_destroy_notify, rvalue_destroy_notify);
					} else {
						ccode.add_assignment (lvalue_destroy_notify, new CCodeConstant ("NULL"));
					}
				}
			}
		}
	}

	public override void store_local (LocalVariable local, TargetValue value, bool initializer, SourceReference? source_reference = null) {
		if (!initializer && requires_destroy (local.variable_type)) {
			/* unref old value */
			ccode.add_expression (destroy_local (local));
		}

		store_value (get_local_cvalue (local), value, source_reference);
	}

	public override void store_parameter (Parameter param, TargetValue _value, bool capturing_parameter = false, SourceReference? source_reference = null) {
		var value = _value;

		bool capturing_parameter_in_coroutine = capturing_parameter && is_in_coroutine ();

		var param_type = param.variable_type.copy ();
		if (param.captured || is_in_coroutine ()) {
			if (!param_type.value_owned && !no_implicit_copy (param_type)) {
				// parameter value has been implicitly copied into a heap data structure
				// treat parameter as owned
				param_type.value_owned = true;

				var old_coroutine = is_in_coroutine ();
				if (old_coroutine) {
					current_method.coroutine = false;
				}

				if (requires_copy (param_type) && !capturing_parameter_in_coroutine) {
					// do not copy value when capturing parameter in coroutine
					// as the value was already copied on coroutine initialization
					value = copy_value (value, param);
				}

				if (old_coroutine) {
					current_method.coroutine = true;
				}
			}
		}

		if (requires_destroy (param_type)) {
			/* unref old value */
			ccode.add_expression (destroy_parameter (param));
		}

		store_value (get_parameter_cvalue (param), value, source_reference);
	}

	public override void store_field (Field field, TargetValue? instance, TargetValue value, SourceReference? source_reference = null,  bool initializer = false) {
		var lvalue = get_field_cvalue (field, instance);
		var type = lvalue.value_type;
		if (lvalue.actual_value_type != null) {
			type = lvalue.actual_value_type;
		}
		if (!initializer && (!(field.variable_type is DelegateType) || get_ccode_delegate_target (field)) && requires_destroy (type)) {
			/* unref old value */
			ccode.add_expression (destroy_field (field, instance));
		}
		if (initializer && instance != null && get_ccode_delegate_target (field) && get_delegate_target_cvalue (value) == null) {
			unowned DelegateType delegate_type = field.variable_type as DelegateType;
			if (delegate_type != null && delegate_type.delegate_symbol.has_target) {
				((GLibValue) value).delegate_target_cvalue = get_cvalue_ (instance);
			}
		}

		store_value (lvalue, value, source_reference);
	}
}
