#include "mk.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <memory.h>

static const MK_CHAR className[] = CLASS_INTERNAL_OBJECT;
static const MK_CHAR classSuper[] = "";

static
int mk_object_class( MK_VM_STRUCT *vm, MK_VM_FRAME_ITEM *target, MK_VM_FRAME_ITEM **result )
{
	MK_CLASS *targetClass = 
		mk_vm_get_class(vm, target );
	if( targetClass == NULL )
		return MK_VM_EXECUTE_EXPR_THROW;	// se error object in mk_vm_get_class()
	
	if( mk_find_item_hashtable( vm->global, targetClass->nameThis, (void*)result ) == 0 )
	{
		mk_raise_internal_error( vm, "", 0, MK_ERROR_TYPE_VM_ERROR | 1, targetClass->nameThis, "<global>", NULL );
		return MK_VM_EXECUTE_EXPR_THROW;	// se error object in mk_vm_get_class()
	}
	return MK_VM_EXECUTE_EXPR_RETURN_RETURN;
}

static
int mk_object_to_s( MK_VM_STRUCT *vm, MK_VM_FRAME_ITEM *target, MK_VM_FRAME_ITEM **result )
{
	unsigned int length = 0;
	MK_CHAR *className = NULL;
	MK_CLASS *pClass = NULL;
	*result = 
		mk_create_vm_frame_item_object( &vm->pFrameItemTable );
	(*result)->flags = 
		MK_TYPE_SET_ATTRIBUTE( (*result)->flags, MK_VM_FRAME_ITEM_TYPE_STRING_VALUE );

	pClass = mk_vm_get_class( vm, target );
	if( pClass == NULL )
		className = "Unknown";
	else
		className = pClass->nameThis;

	// #<className:0xaddress>"
	length = sizeof(MK_CHAR) * ( 2 + strlen( className ) + 1 + 2 + sizeof( void* ) * 2 + 1 + 1 );
	(*result)->stringTypeValue = malloc( length );
	sprintf( (*result)->stringTypeValue, "#<%s:0x%x>", className, target );
	return MK_VM_EXECUTE_EXPR_RETURN_RETURN;
}

int mk_object_new( MK_VM_STRUCT *vm, MK_VM_FRAME_ITEM *target, MK_VM_FRAME_ITEM **result )
{
	MK_HASHTABLE *classes = 
		vm->code->classes;
	MK_CLASS *thisClass = NULL, *currentClass = NULL;
	MK_VM_FRAME_ITEM *currentFrameItem = NULL, *previousFrameItem = NULL;
	*result = NULL;

	if( MK_TYPE_ATTRIBUTE(target->flags) != MK_VM_FRAME_ITEM_TYPE_CLASS )
		return MK_VM_EXECUTE_EXPR_THROW;	// todo: create exception object
	mk_find_item_hashtable( classes, target->classTypeValue.typeName, (void**)&thisClass );
	if( thisClass == NULL )
		return MK_VM_EXECUTE_EXPR_THROW;	// todo: create exception object

	currentClass = 
		thisClass;

	// setup variables
	while(currentClass)
	{
		const MK_CHAR *key = NULL;
		MK_VARIABLE* value = NULL;
		MK_VM_FRAME_ITEM *insertValue = NULL;
		void *iterator = NULL;

		currentFrameItem = 
			mk_create_vm_frame_item_object( &vm->pFrameItemTable );
		currentFrameItem->flags |= MK_VM_FRAME_ITEM_TYPE_CLASS;
		currentFrameItem->classTypeValue.typeName = mk_get_symbol_name_ptr( vm, currentClass->nameThis );
		currentFrameItem->classTypeValue.variables = 
			mk_allocate_vm_managed_hashtable( &vm->pHashTable );
		if( previousFrameItem != NULL )
		{
			MK_CHAR *nameSuper = 
				mk_get_symbol_name_ptr( vm, "super" );
			mk_insert_item_hashtable( 
				previousFrameItem->classTypeValue.variables, 
				nameSuper, 
				currentFrameItem );
			currentFrameItem->classTypeValue.child = previousFrameItem;

		}
		else
		{
			*result = currentFrameItem;
		}
		if( currentClass->variables != NULL )
		{
			iterator = mk_enum_item_hashtable_begin( currentClass->variables );
			while( iterator != NULL )
			{
				iterator = mk_enum_item_hashtable_next( currentClass->variables, iterator, &key, (void**)&value );
				if( value->flags & MK_TYPE_ATTRIBUTE_VARIABLE_METHOD )
				{
					insertValue = 
						mk_create_vm_frame_item_object( &vm->pFrameItemTable );
					insertValue->flags |= 
						MK_VM_FRAME_ITEM_TYPE_FINAL | MK_VM_FRAME_ITEM_TYPE_NODE_VALUE;
					insertValue->code.definedFrame = NULL;
					insertValue->code.node = (unsigned int*)value;
					mk_insert_item_hashtable( 
						currentFrameItem->classTypeValue.variables, 
						key, 
						insertValue );
				}
				else if( !( value->flags & MK_TYPE_ATTRIBUTE_VARIABLE_STATIC ) )
				{
					if( value->defaultValue != NULL )	// todo: set current frame to target class.
					{
						int retCodeDefaultValue = 
							mk_execute_expr( vm, value->defaultValue, 0 );
						if( retCodeDefaultValue == MK_VM_EXECUTE_EXPR_RETURN_NEXTSTEP )
						{
							retCodeDefaultValue = 
								mk_object_equal( vm, 
									insertValue, 
									(MK_VM_FRAME_ITEM*)mk_pop_vector( vm->localStack ), 
									&insertValue );
							if( retCodeDefaultValue != MK_VM_EXECUTE_EXPR_RETURN_RETURN )
								return retCodeDefaultValue;
						}
						else
						{
							return retCodeDefaultValue;
						}
					}
					else
					{
						insertValue = 
							mk_create_vm_frame_item_object( &vm->pFrameItemTable );
					}
					mk_insert_item_hashtable( 
						currentFrameItem->classTypeValue.variables, 
						key, 
						insertValue );
				}
			}
			mk_enum_item_hashtable_end(currentClass->variables, iterator );
		}
		previousFrameItem = currentFrameItem;
		mk_find_item_hashtable( classes, currentClass->nameSuper, (void**)&currentClass );
	}
	return MK_VM_EXECUTE_EXPR_RETURN_RETURN;
}

static 
int mk_object_equal_equal( MK_VM_STRUCT *vm, MK_VM_FRAME_ITEM *target, MK_VM_FRAME_ITEM *right, MK_VM_FRAME_ITEM **result )
{
	int isTrue = 0;
	int retCode = MK_VM_EXECUTE_EXPR_RETURN_RETURN;

	if( ( (INT_PTR)target & MK_VM_FRAME_ITEM_TYPE_DIRECT_VALUE ) == 0 &&
		( (INT_PTR)right & MK_VM_FRAME_ITEM_TYPE_DIRECT_VALUE ) == 1 )
	{
		MK_VM_FRAME_ITEM *swap = target;
		target = right;
		right = swap;
	}
	if( target == NULL || right == NULL )
	{
		if( target == NULL && right == NULL )
			isTrue = 1;
	}
	else if( (INT_PTR)target & MK_VM_FRAME_ITEM_TYPE_DIRECT_VALUE )
	{
		if( (INT_PTR)right & MK_VM_FRAME_ITEM_TYPE_DIRECT_VALUE )
		{
			isTrue = ( target == right );
		}
		else
		{
			switch( MK_TYPE_ATTRIBUTE(right->flags ) )
			{
			case MK_VM_FRAME_ITEM_TYPE_INT_VALUE:
				isTrue = 
					mk_vm_frame_item_to_int32( target) == mk_vm_frame_item_to_int32( right );
				break;

			case MK_VM_FRAME_ITEM_TYPE_CHARACTER_VALUE:
				// todo:
				break;

			case MK_VM_FRAME_ITEM_TYPE_BOOL_VALUE:
				// todo:
				break;

			case MK_VM_FRAME_ITEM_TYPE_FLOAT_VALUE:
				isTrue = 
					mk_vm_frame_item_to_float(target) == mk_vm_frame_item_to_float(right);
				break;
			}
		}
	}
	else if( MK_TYPE_ATTRIBUTE( target->flags ) == MK_TYPE_ATTRIBUTE( right->flags ) )
	{
		void *position = NULL;
		const MK_CHAR *key = NULL;
		MK_VM_FRAME_ITEM *value = NULL, *rightValue = NULL;
		switch( MK_TYPE_ATTRIBUTE(target->flags) )
		{
		case MK_VM_FRAME_ITEM_TYPE_CLASS:
			// get top level object.
			while( target->classTypeValue.child != NULL )
				target = target->classTypeValue.child;
			while( right->classTypeValue.child != NULL )
				right = right->classTypeValue.child;
			if( memcmp( &target->classTypeValue, 
						&right->classTypeValue, 
						sizeof( target->classTypeValue ) ) == 0 )
				isTrue = 1;
			break;

		case MK_VM_FRAME_ITEM_TYPE_NIL:
			isTrue = 1;
			break;

		case MK_VM_FRAME_ITEM_TYPE_INT_VALUE:
		case MK_VM_FRAME_ITEM_TYPE_CHARACTER_VALUE:
		case MK_VM_FRAME_ITEM_TYPE_BOOL_VALUE:
			isTrue =
				mk_vm_frame_item_to_int32( target ) == mk_vm_frame_item_to_int32( right );
			break;

		case MK_VM_FRAME_ITEM_TYPE_FLOAT_VALUE:
			isTrue =
				mk_vm_frame_item_to_float( target ) == mk_vm_frame_item_to_float( right );
			break;

		case MK_VM_FRAME_ITEM_TYPE_STRING_VALUE:
			if( strcmp( target->stringTypeValue, right->stringTypeValue ) == 0 )
				isTrue = 1;
			break;
		}
		while( 0 );
	}
	*result = mk_vm_create_bool_frame_item( isTrue );
	return retCode;
}

int mk_object_equal( MK_VM_STRUCT *vm, MK_VM_FRAME_ITEM *target, MK_VM_FRAME_ITEM *right, MK_VM_FRAME_ITEM **result )
{
	int retCode = MK_VM_EXECUTE_EXPR_RETURN_RETURN;
	if( target->flags & MK_VM_FRAME_ITEM_TYPE_FINAL )
		return mk_raise_internal_error( vm, "", 0, MK_ERROR_TYPE_VM_ERROR | 13, NULL );	// final object
	
	//todo: [interim] return Object class
	if( MK_TYPE_ATTRIBUTE(target->flags) == MK_VM_FRAME_ITEM_TYPE_STRING_VALUE )
	{
		if( !mk_is_ptr_in_memory_pool( target->stringTypeValue, vm->memoryPool ) )
		{
			free( target->stringTypeValue );
			target->stringTypeValue = NULL;
		}
	}
	while( ( (INT_PTR)right & MK_VM_FRAME_ITEM_TYPE_DIRECT_VALUE ) == 0 &&
		MK_TYPE_ATTRIBUTE( right->flags ) == MK_VM_FRAME_ITEM_TYPE_REFERENCE_VALUE )
		right = right->reference;

	if( (INT_PTR)right & MK_VM_FRAME_ITEM_TYPE_DIRECT_VALUE )
	{
		INT_PTR directValue = (INT_PTR)right;
		INT_PTR value = ( directValue & MK_VM_FRAME_ITEM_DIRECT_INT_VALUE_MASK ) >> 4;
		if( directValue & MK_VM_FRAME_ITEM_TYPE_INT_MINUS_VALUE )
			value *= -1;

		switch( directValue & MK_VM_FRAME_ITEM_TYPE_DIRECT_MASK )
		{
		case MK_VM_FRAME_ITEM_TYPE_DIRECT_INT:
			target->flags = 
				MK_TYPE_SET_ATTRIBUTE( target->flags, MK_VM_FRAME_ITEM_TYPE_INT_VALUE );
			break;

		case MK_VM_FRAME_ITEM_TYPE_DIRECT_BOOL:
			target->flags = 
				MK_TYPE_SET_ATTRIBUTE( target->flags, MK_VM_FRAME_ITEM_TYPE_BOOL_VALUE );
			break;

		case MK_VM_FRAME_ITEM_TYPE_DIRECT_CHAR:
			target->flags = 
				MK_TYPE_SET_ATTRIBUTE( target->flags, MK_VM_FRAME_ITEM_TYPE_CHARACTER_VALUE );
			break;
		}
		target->int32TypeValue = value;
	}
	else
	{
		target->flags = 
			MK_TYPE_SET_ATTRIBUTE( target->flags, right->flags );
		switch( MK_TYPE_ATTRIBUTE( right->flags ) )
		{
		case MK_VM_FRAME_ITEM_TYPE_CLASS:
			while( right->classTypeValue.child != NULL )
				right = right->classTypeValue.child;
			target->classTypeValue.typeName = 
				mk_get_symbol_name_ptr( vm, right->classTypeValue.typeName );
			target->classTypeValue.variables = right->classTypeValue.variables;
			break;

		case MK_VM_FRAME_ITEM_TYPE_NIL:
			break;

		case MK_VM_FRAME_ITEM_TYPE_INT_VALUE:
		case MK_VM_FRAME_ITEM_TYPE_CHARACTER_VALUE:
		case MK_VM_FRAME_ITEM_TYPE_BOOL_VALUE:
			target->int32TypeValue = right->int32TypeValue;
			break;

		case MK_VM_FRAME_ITEM_TYPE_FLOAT_VALUE:
			target->floatTypeValue = right->floatTypeValue;
			break;

		case MK_VM_FRAME_ITEM_TYPE_STRING_VALUE:
			mk_copy_string( &target->stringTypeValue, right->stringTypeValue );
			break;

		case MK_VM_FRAME_ITEM_TYPE_ARRAY_VALUE:
			target->arrayTypeValue = right->arrayTypeValue;
			break;

		case MK_VM_FRAME_ITEM_TYPE_NODE_VALUE:
			target->code.node = right->code.node;
			target->code.definedFrame = right->code.definedFrame;
			break;
		}
	}
	*result = target;
	return retCode;
}

static 
int mk_object_not_equal( MK_VM_STRUCT *vm, MK_VM_FRAME_ITEM *target, MK_VM_FRAME_ITEM *right, MK_VM_FRAME_ITEM **result )
{
	int retCode =
		mk_object_equal_equal( vm, target, right, result );
	if( retCode == MK_VM_EXECUTE_EXPR_RETURN_RETURN )
		*result = 
			mk_vm_create_bool_frame_item( !mk_vm_frame_item_is_true( *result ) );
	return retCode;
}

MK_CLASS *mk_create_object_class( MK_VM_STRUCT *vm )
{
	MK_CLASS *result =
		mk_create_object( MK_TYPE_CLASS );
	MK_VARIABLE *insertMethod = NULL;
	result->nameThis = mk_get_symbol_name_ptr( vm, className );
	result->nameSuper = mk_get_symbol_name_ptr( vm, classSuper );

	mk_register_variable(
		vm,
		mk_create_method(
			vm,
			"class",
			MK_TYPE_VARIABLE | 
				MK_TYPE_ATTRIBUTE_VARIABLE_METHOD | 
				MK_TYPE_ATTRIBUTE_VARIABLE_FINAL | 
				MK_TYPE_ATTRIBUTE_VARIABLE_STATIC |
				MK_TYPE_ATTRIBUTE_VARIABLE_METHOD_NATIVE,
			0,
			(INT_PTR)mk_object_class ),
		result );

	mk_register_variable(
		vm,
		mk_create_method(
			vm,
			"to_s",
			MK_TYPE_VARIABLE | 
				MK_TYPE_ATTRIBUTE_VARIABLE_METHOD | 
				MK_TYPE_ATTRIBUTE_VARIABLE_FINAL | 
				MK_TYPE_ATTRIBUTE_VARIABLE_STATIC |
				MK_TYPE_ATTRIBUTE_VARIABLE_METHOD_NATIVE,
			0,
			(INT_PTR)mk_object_to_s ),
		result );

	mk_register_variable(
		vm,
		mk_create_method(
			vm,
			"new",
			MK_TYPE_VARIABLE | 
				MK_TYPE_ATTRIBUTE_VARIABLE_METHOD | 
				MK_TYPE_ATTRIBUTE_VARIABLE_FINAL | 
				MK_TYPE_ATTRIBUTE_VARIABLE_METHOD_NATIVE,
			0,
			(INT_PTR)mk_object_new ),
		result );

	mk_register_variable(
		vm,
		mk_create_method(
			vm,
			"=",
			MK_TYPE_VARIABLE | 
				MK_TYPE_ATTRIBUTE_VARIABLE_METHOD | 
				MK_TYPE_ATTRIBUTE_VARIABLE_FINAL | 
				MK_TYPE_ATTRIBUTE_VARIABLE_METHOD_NATIVE |
				MK_TYPE_ATTRIBUTE_VARIABLE_METHOD_OPERATOR |
				MK_LEX_TYPE_RESERVED_MARK_EQUAL, 
			1,
			(INT_PTR)mk_object_equal ),
		result );

	mk_register_variable(
		vm,
		mk_create_method(
			vm,
			"==",
			MK_TYPE_VARIABLE | 
				MK_TYPE_ATTRIBUTE_VARIABLE_METHOD | 
				MK_TYPE_ATTRIBUTE_VARIABLE_FINAL | 
				MK_TYPE_ATTRIBUTE_VARIABLE_METHOD_NATIVE |
				MK_TYPE_ATTRIBUTE_VARIABLE_METHOD_OPERATOR |
				MK_LEX_TYPE_RESERVED_MARK_SAME, 
			1,
			(INT_PTR)mk_object_equal_equal ),
		result );

	mk_register_variable(
		vm,
		mk_create_method(
			vm,
			"!=",
			MK_TYPE_VARIABLE | 
				MK_TYPE_ATTRIBUTE_VARIABLE_METHOD | 
				MK_TYPE_ATTRIBUTE_VARIABLE_FINAL | 
				MK_TYPE_ATTRIBUTE_VARIABLE_METHOD_NATIVE |
				MK_TYPE_ATTRIBUTE_VARIABLE_METHOD_OPERATOR |
				MK_LEX_TYPE_RESERVED_MARK_NOT_SAME, 
			1,
			(INT_PTR)mk_object_not_equal ),
		result );

	return result;
}
