/****************************************************************************
 * KONOHA COPYRIGHT, LICENSE NOTICE, AND DISCRIMER  
 * 
 * Copyright (c) 2005-2008, Kimio Kuramitsu <kimio at ynu.ac.jp>
 *           (c) 2008-      Konoha Software Foundation  
 * All rights reserved.
 * 
 * You may choose one of the following two licenses when you use konoha. 
 * See www.konohaware.org/license.html for further information.
 * 
 * (1) GNU General Public License 2.0      (with    KONOHA_UNDER_GPL2)
 * (2) Konoha Software Foundation License 1.0
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *  
 ****************************************************************************/

/* ************************************************************************ */

#include"commons.h"

/* ************************************************************************ */

#ifdef __cplusplus 
extern "C" {
#endif

#ifdef KNH_DBGMODE
	#define DEBUG_STMT0(ctx, stmt)
	#define DEBUG_STMT(ctx, stmt) \
		DEBUG("level=%d", level); \
		knh_Stmt__dump(ctx, stmt, KNH_STDOUT, knh_String_EMPTY()); \
		knh_flush(ctx, KNH_STDOUT); \

#else
	#define DEBUG_STMT(ctx, stmt)
#endif


	
/* ======================================================================== */ 

static 
Object* knh_Mapper_fInterface(Ctx *ctx, Object *o, Mapper *mpr)
{
	return o;
}

/* ------------------------------------------------------------------------ */

static
Mapper* new_Mapper__interface(Ctx *ctx, knh_class_t scid, knh_class_t tcid)
{
	return new_Mapper(ctx, KNH_FLAG_MMF_INTERFACE, scid, tcid, knh_Mapper_fInterface, KNH_NULL);
}

/* ------------------------------------------------------------------------ */

static
knh_bool_t knh_MethodField_equalsType(MethodField *o, MethodField *o2)
{
	knh_index_t i;
	if(DP(o)->size != DP(o2)->size) return 0;
	for(i = 0; i < DP(o)->size; i++) {
		if(DP(o)->params[i].type != DP(o2)->params[i].type) return 0;
	}
	if(knh_MethodField_isVarArgs(o)) {
		return knh_MethodField_isVarArgs(o2) ? 1 : 0;
	}
	else {
		return knh_MethodField_isVarArgs(o2) ? 0 : 1;
	}
}

/* ------------------------------------------------------------------------ */

void knh_class_addInterface(Ctx *ctx, knh_class_t cid, knh_class_t icid)
{
	KNH_ASSERT(!knh_class_isInterface(icid));
	if(knh_class_instanceof(cid, icid)) {
		return ;
	}
	else {
		knh_class_t isupcid = icid;
		int allchecked = 1;
		while(isupcid != CLASS_Object) {
			ClassStruct *cs = knh_tClass[isupcid].cstruct;
			int i;
			for(i = 0; i < knh_Array_size(DP(cs)->methods); i++) {
				Method *imtd = (Method*)knh_Array_n(DP(cs)->methods, i);
				if(knh_Method_isPrivate(imtd)) {
					continue;
				}
				else {
					Method *mtd = knh_Class_getMethod(ctx, cid, DP(imtd)->mn);
					if(IS_NOTNULL(mtd)) {
						if(!knh_MethodField_equalsType(DP(mtd)->mf, DP(imtd)->mf)) {
							char bufcm[CLASSNAME_BUFSIZ];
							knh_format_cmethodn(bufcm, sizeof(bufcm), DP(imtd)->cid, DP(imtd)->mn);
							DBG2_P("mismatch!! %s", bufcm);
							allchecked = 0;
						}
					}
				}
			}
			isupcid = knh_tClass[isupcid].supcid;
		}
		if(allchecked) {
			isupcid = icid;
			while(isupcid != CLASS_Object) {
				ClassStruct *cs = knh_tClass[isupcid].cstruct;
				int i;
				for(i = 0; i < knh_Array_size(DP(cs)->methods); i++) {
					Method *imtd = (Method*)knh_Array_n(DP(cs)->methods, i);
					if(knh_Method_isPrivate(imtd)) {
						continue;
					}
					else {
						Method *mtd = knh_Class_getMethod(ctx, cid, DP(imtd)->mn);
						if(IS_NULL(mtd)) {
							mtd = new_Method(ctx, 0, cid, DP(imtd)->mn, NULL);
							KNH_SETv(ctx, DP(mtd)->mf, DP(imtd)->mf);
							KNH_ASSERT(IS_NOTNULL(DP(mtd)->mf));
							knh_Class_addMethod(ctx, cid, mtd);
						}
					}
				}
				isupcid = knh_tClass[isupcid].supcid;
			}
			DBG2_P("add interface %s to %s", CLASSN(icid), CLASSN(cid));
			knh_ClassMap_add(ctx, knh_tClass[cid].cmap, new_Mapper__interface(ctx, cid, icid));
		}
	}
}

/* ------------------------------------------------------------------------ */

static
Object *knh_tClass_finitvalue__OBJECT(Ctx *ctx, knh_class_t cid)
{
	return new_Object__init(ctx, knh_tClass[cid].oflag, cid);
}

/* ------------------------------------------------------------------------ */
#ifndef StmtMACRO
#define StmtCLASS_class(stmt)           DP(stmt)->tokens[0]
#define StmtCLASS_superclass(stmt)      DP(stmt)->tokens[1]
#define StmtCLASS_interface(stmt)       DP(stmt)->stmts[2]
#define StmtCLASS_instmt(stmt)          DP(stmt)->stmts[3]
#endif

void knh_StmtCLASS_decl(Ctx *ctx, Stmt *stmt, Compiler *cpr, NameSpace *ns, int level)
{
	char bufn[CLASSNAME_BUFSIZ];
	knh_snprintf(bufn, sizeof(bufn), "%s.%s", knh_String_tochar(DP(ns)->nsname), knh_Token_tochar(StmtCLASS_class(stmt)));
	{
		knh_tClass_t *TC = NULL;
		knh_class_t cid  = knh_NameSpace_getClass(ctx, ns, B(bufn));
		knh_class_t supcid = CLASS_unknown, oldcid = CLASS_Object;
		if(DP(StmtCLASS_superclass(stmt))->tt == TT_ASIS) {
			supcid = CLASS_Object;
		}
		else {
			supcid = knh_NameSpace_getClass(ctx, ns, knh_Token_tobytes(StmtCLASS_superclass(stmt)));
			if(supcid == CLASS_unknown && cid == CLASS_unknown) {
				knh_Compiler_perror(ctx, cpr, KMSG_UCLASSN, NULL);
				supcid = CLASS_Object;
				knh_Token_perrata(ctx, StmtCLASS_superclass(stmt), CLASSN(supcid));
			}
			if(supcid != CLASS_unknown) {
				if(knh_class_isFinal(supcid)) {
					knh_Compiler_perror(ctx, cpr, KMSG_EEXTENDS, CLASSN(supcid));
					knh_Stmt_done(ctx, stmt);
					return ;
				}
			}
		}
		if(cid == CLASS_unknown) {
			cid = knh_tClass_newId(ctx);
			KNH_ASSERT(knh_tClass[cid].class == NULL);
		}
		else {
			DEBUG3("redefine %s", CLASSN(cid));
			DEBUG_ASSERT_cid(cid);
			if(knh_tClass[cid].bcid != CLASS_Object) {
				knh_Compiler_perror(ctx, cpr, KMSG_EOVERRIDE, CLASSN(cid));
				knh_Stmt_done(ctx, stmt);
				return;
			}
			if(!knh_StmtMETA_isOverride(stmt)) {
				knh_Compiler_perror(ctx, cpr, KMSG_AOVERRIDE, CLASSN(cid));
				knh_Stmt_done(ctx, stmt);
				return;
			}
			if(knh_tClass[cid].supcid != supcid) {
				knh_Compiler_perror(ctx, cpr, KMSG_DIFFCLASSN, NULL);
				supcid = knh_tClass[cid].supcid;
				knh_Token_perrata(ctx, StmtCLASS_superclass(stmt), CLASSN(supcid));
			}
			oldcid = cid;
			cid = knh_tClass_newId(ctx);
			KNH_ASSERT(knh_tClass[cid].class == NULL);
		}
		
		TC = (knh_tClass_t*)&(knh_tClass[cid]);
		TC->cflag  = knh_Stmt_metaflag__class(ctx, stmt);
		TC->oflag  = KNH_FLAG_CF2OF(TC->cflag);

		TC->bcid   = CLASS_Object;
		TC->supcid = supcid; 
		if(supcid == CLASS_Object) {
			TC->offset = 0; 
		}else {
			KNH_ASSERT(supcid < cid);
			TC->offset = knh_tClass[supcid].bsize;
		}
		
		KNH_ASSERT(TC->class == NULL);
		KNH_TCLASS_NAME(ctx, cid, new_String(ctx, B(bufn), NULL));
		KNH_INITv(TC->class, new_ClassStruct0(ctx, 0));
		KNH_INITv(TC->cmap, new_ClassMap0(ctx, 0));

		knh_NameSpace_setLocalName(ctx, ns, cid);
		
		if(oldcid != CLASS_Object) {
			knh_class_addInterface(ctx, cid, oldcid);
		}
		TC->finitvalue = knh_tClass_finitvalue__OBJECT;
		/* implements */ {
			Stmt *istmt = StmtCLASS_interface(stmt);
			if(DP(istmt)->stt != STT_DONE) {
				int i, n = DP(istmt)->size;
				for(i = 0; i < n; i++) {
					Token *tk = DP(stmt)->tokens[i];
					knh_class_t icid = knh_NameSpace_getClass(ctx, ns, knh_Token_tobytes(tk));
					if(icid == CLASS_unknown) {
						knh_Token_perror(ctx, tk, KMSG_UCLASSN);
						continue;
					}
					if(!knh_class_isInterface(icid)) {
						knh_Token_perror(ctx, tk, KMSG_EIMPLEMENTS);
						continue;
					}
					knh_class_addInterface(ctx, cid, icid);
				}
			}
		}
	}
}


/* ------------------------------------------------------------------------ */

static
void knh_Compiler_declareClass(Ctx *ctx, Compiler *cpr, knh_class_t cid)
{
	knh_tClass_t *TC = (knh_tClass_t*)(&knh_tClass[cid]);
	KNH_ASSERT(DP(TC->cstruct)->fields == NULL);
	{
		int i, fsize = DP(cpr)->vars_size;
		knh_cfield_t *cf = (knh_cfield_t*)KNH_MALLOC(ctx, sizeof(knh_cfield_t) * fsize);
		for(i = 0; i < fsize; i++) {
			cf[i].flag = DP(cpr)->vars[i].flag;
			cf[i].type = DP(cpr)->vars[i].type;
			cf[i].fn = DP(cpr)->vars[i].fn;
			KNH_INITv(cf[i].value, DP(cpr)->vars[i].value)
		}
		DP(TC->cstruct)->fields =cf;
		DP(TC->cstruct)->fsize = fsize;
		DP(TC->cstruct)->sid = BSIZE_TOSID(fsize);
		TC->sid = BSIZE_TOSID(fsize);
		TC->bsize = fsize + TC->offset;
		TC->size = sizeof(Object*) * TC->bsize;
		knh_NameSpace_setClass(ctx, knh_rootNameSpace, TC->lname, cid);
		DBG2_({
				DBG2_P("HERE IS DEFINED STRUCT");
				knh_cfield_dump(ctx, cf, 0, fsize, KNH_STDOUT);
		})
	}
}

/* ------------------------------------------------------------------------ */

void knh_StmtCLASS_name(Ctx *ctx, Stmt *stmt, Compiler *cpr, NameSpace *ns, int level)
{
	knh_class_t prev_cid = DP(cpr)->this_cid;
	knh_class_t this_cid = knh_Token_toclass(ctx, StmtCLASS_class(stmt), CLASS_unknown, ns);
	KNH_ASSERT(this_cid != CLASS_unknown);
	DEBUG_ASSERT_cid(this_cid);
	DP(cpr)->this_cid = this_cid;
	{
		Stmt *instmt = StmtCLASS_instmt(stmt);
		KNH_ASSERT(IS_Stmt(instmt));
		knh_Compiler_initDecl(ctx, cpr);
		while(IS_Stmt(instmt)) {
			if(DP(instmt)->stt == STT_DECL) {
				DP(cpr)->line = DP(instmt)->line;
				knh_StmtDECL_name(ctx, instmt, cpr, ns, 1);
				knh_Stmt_done(ctx, instmt);
			}
			else if(DP(instmt)->stt == STT_LET) {
				DP(cpr)->line = DP(instmt)->line;
				knh_StmtLET_name(ctx, instmt, cpr, ns, 1);
				knh_Stmt_done(ctx, instmt);
			}
			instmt = DP(instmt)->next;
		}
		knh_Compiler_declareClass(ctx, cpr, this_cid);
		
		instmt = StmtCLASS_instmt(stmt);
		while(IS_Stmt(instmt)) {
			if(DP(instmt)->stt == STT_METHOD) {
				DP(cpr)->line = DP(instmt)->line;
				knh_StmtMETHOD_name(ctx, instmt, cpr, ns, 1);
			}
			else if(DP(instmt)->stt == STT_FORMAT) {
				DP(cpr)->line = DP(instmt)->line;
				knh_StmtFORMAT_name(ctx, instmt, cpr, ns, 1);
			}
			else {

			}
			instmt = DP(instmt)->next;
		}
	}
	DP(cpr)->this_cid = prev_cid;
	KNH_ASSERT(DP(cpr)->this_cid == prev_cid);	
}

/* ------------------------------------------------------------------------ */

void knh_StmtCLASS_cmpl(Ctx *ctx, Stmt *stmt, Compiler *cpr, NameSpace *ns, knh_type_t reqt, int level)
{
	knh_class_t prev_cid = DP(cpr)->this_cid;
	knh_class_t this_cid = knh_Token_toclass(ctx, StmtCLASS_class(stmt), CLASS_unknown, ns);
	KNH_ASSERT(this_cid != CLASS_unknown);
	DP(cpr)->this_cid = this_cid;
	{
		Stmt *instmt = StmtCLASS_instmt(stmt);
		KNH_ASSERT(IS_Stmt(instmt));
		while(IS_Stmt(instmt)) {
			if(DP(instmt)->stt == STT_METHOD) {
				DP(cpr)->line = DP(instmt)->line;
				knh_StmtMETHOD_cmpl(ctx, instmt, cpr, ns, TYPE_Method, 1);
			}
			else if(DP(instmt)->stt == STT_FORMAT) {
				DP(cpr)->line = DP(instmt)->line;
				knh_StmtFORMAT_cmpl(ctx, instmt, cpr, ns, TYPE_Method, 1);
			}
			else {
				//DEBUG_STMT(ctx, instmt);
			}
			instmt = DP(instmt)->next;
		}
	}
	DP(cpr)->this_cid = prev_cid;
	KNH_ASSERT(DP(cpr)->this_cid == prev_cid);	
}

/* ------------------------------------------------------------------------ */


#ifdef __cplusplus
}
#endif
