/****************************************************************************
 * 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


/* ------------------------------------------------------------------------ */
/* [name] */

void knh_Compiler_initDecl(Ctx *ctx, Compiler *cpr)
{
	DP(cpr)->vars_size = 0;
	DP(cpr)->vars_offset = 0;
	knh_int_t i;
	for(i = 0; i < KONOHA_LOCALVAR_SIZE; i++) {
		DP(cpr)->vars[i].flag  = 0;
		DP(cpr)->vars[i].type  = TYPE_Any;
		DP(cpr)->vars[i].fn    = FIELDN_NONAME;
		KNH_SETv(ctx, DP(cpr)->vars[i].value, KNH_NULL);
	}
}

/* ------------------------------------------------------------------------ */
/* [this] */

void knh_Compiler_initThisVariable(Ctx *ctx, Compiler *cpr, knh_class_t this_cid)
{
	KNH_ASSERT(DP(cpr)->vars_size == 0);
	DP(cpr)->vars[0].flag = 0;
	DP(cpr)->vars[0].type = CLASS_TONNTYPE(this_cid);
	DP(cpr)->vars[0].fn   = FIELDN_this;
	KNH_SETv(ctx, DP(cpr)->vars[0].value, KNH_NULL);
	DP(cpr)->vars_size = 1;
}

/* ======================================================================== */
/* [METHOD] */

static void KNH_ASM_PUSH_INITLOCALs(Ctx *ctx, Compiler *cpr)
{
	Method *mtd = DP(cpr)->method;
	KNH_ASSERT(IS_Method(mtd));
	{
		int i = 1;
		for(;i < knh_Method_psize(mtd) + 1; i++) {
			if(IS_NOTNULL(DP(cpr)->vars[i].value)) {
				KNH_ASM_MOVSFP_IFNUL(ctx, cpr, i, DP(cpr)->vars[i].value);
			}
		}
		if(DP(cpr)->vars[i].fn == FIELDN_vargs) {
			KNH_ASM_VARGS(ctx, cpr, i); i++;
		}
		for(; i < DP(cpr)->vars_size; i++) {
			if(IS_NOTNULL(DP(cpr)->vars[i].value) || TYPE_ISNULLABLE(DP(cpr)->vars[i].type)) {
				KNH_ASM_PUSH__OBJ(ctx, cpr, DP(cpr)->vars[i].value);
			}
			else {
				KNH_ASM_PUSH__DEF(ctx, cpr, DP(cpr)->vars[i].type);
			}
		}
		//KNH_ASM_PUSH__OBJ(ctx, cpr, KNH_NULL);
		KNH_ASM_SHIFT(ctx, cpr, 1);
	}
}

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

void knh_Stmt_run(Ctx *ctx, Stmt *stmt, Compiler *cpr, NameSpace *ns)
{
	Script *scr = knh_Compiler_getScript(ctx, cpr);
	Method *mtd = knh_Class_getMethod(ctx, knh_Object_cid(scr), METHODN_lambda);
	knh_stmt_t stt_expr = SP(stmt)->stt;

	DP(cpr)->this_cid = knh_Object_cid(scr);
	knh_Compiler_initDecl(ctx, cpr);
	knh_Compiler_initThisVariable(ctx, cpr, DP(cpr)->this_cid);
	KNH_SETv(ctx, DP(cpr)->method, mtd);

	knh_Compiler_startCompilation(ctx, cpr, stmt);
	knh_Stmt_name(ctx, stmt, cpr, ns, 2);
	KNH_ASM_PUSH_INITLOCALs(ctx, cpr);

	if(knh_stmt_isExpr(SP(stmt)->stt)) {
		knh_Stmt_cmpl(ctx, stmt, cpr, ns, CLASS_Any, 0);
	}
	else {
		knh_Stmt_cmpl(ctx, stmt, cpr, ns, CLASS_Any, 2);
	}

	if(!knh_Compiler_isStopped(cpr)) {
		KNH_ASM_RET__ebp(ctx, cpr, 0);
	}

	knh_Compiler_endCompilation(ctx, cpr);

	if(!knh_Method_isAbstract(mtd)) {
		double stime = 0.0;
		KNH_LOPEN(ctx, 0);
		KNH_LPUSH(ctx, new_ExceptionHandler(ctx));      /* 0 */
		KNH_LPUSH(ctx, KNH_NULL);                       /* 1 Exception e */

		KNH_ASSERT(IS_ExceptionHandler(KNH_LOCALn(0)));
		VM_TRY_BEGIN(ctx, NPC, L_CATCH_EXCEPTION, KNH_LOCALn(0), KNH_LOCALn(1));
		if(knh_StmtMETA_istime(stmt)) {
			stime = knh_gettime();
		}
		KNH_LPUSH(ctx, mtd);                            /* 2 mtd, return ebp[-1] */
		KNH_LPUSH(ctx, scr);                            /* 3 this ebp[0] */
		KNH_SCALL(ctx, 1);
		if(knh_stmt_isExpr(stt_expr) && KNH_LOCALn(2) != KNH_VOID) {
			if(stt_expr == STT_MT && IS_bString(KNH_LOCALn(2))) {
				knh_println(ctx, KNH_STDOUT, knh_String_tobytes((String*)KNH_LOCALn(2)));
			}
			else {
				knh_format(ctx, KNH_STDOUT, METHODN__k, KNH_LOCALn(2), KNH_NULL);
				knh_println(ctx, KNH_STDOUT, STEXT(""));
			}
		}
//		DBG2_(else {
//			fprintf(stdout, "ebp[stack+%d] = ", (int)(((Context*)ctx)->ebp - ((Context*)ctx)->stack));
//			knh_format(ctx, KNH_STDOUT, METHODN__k, KNH_LOCALn(2), KNH_NULL);
//			knh_println(ctx, KNH_STDOUT, STEXT(""));
//		})
		if(stime != 0.0) {
			double etime = knh_gettime();
			if(etime - stime > 0.01) {
				char buf[80];
				knh_snprintf(buf, sizeof(buf), " - %s: %10.3f[s]", knh_message_text(KMSG_ETIME), etime - stime);
				knh_println(ctx, KNH_STDOUT, B(buf));
			}
		}
		VM_TRY_END(ctx, KNH_LOCALn(0));
		KNH_LCLOSE(ctx);

		return ;

		L_CATCH_EXCEPTION:;
		VM_TRY_END(ctx, KNH_LOCALn(0));
		KNH_ASSERT(IS_Exception(KNH_LOCALn(1)));
		knh_format(ctx, KNH_STDOUT, METHODN__dump, KNH_LOCALn(1), KNH_NULL);
		KNH_LCLOSE(ctx);
		return;
	}
}

/* ------------------------------------------------------------------------ */
/* [METHOD] */


static
knh_methodn_t knh_Compiler_getMethodName(Ctx *ctx, Compiler *cpr, Token *tk, int level)
{
	KNH_ASSERT(IS_Token(tk));
	if(SP(tk)->tt == TT_ASIS) {
		if(level == 1) {
			return METHODN_new;
		}
		else {
			DEBUG3("?? level = %d", level);
			return METHODN_lambda;
		}
	}
	else {
		knh_bytes_t name = knh_Token_tobytes(tk);
		knh_index_t idx = knh_bytes_rindex(name, '.');
		if(idx != -1) {
			name = knh_bytes_last(name, idx);
		}
		return knh_Token_tomethodn(ctx, tk);
	}
}

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

static
knh_class_t knh_Compiler_getMethodClass(Ctx *ctx, Compiler *cpr,  Token *tk, NameSpace *ns, int level)
{
	KNH_ASSERT(IS_Token(tk));
	if(SP(tk)->tt == TT_ASIS) {
		if(level == 0) {
			Script *scr = knh_Compiler_getScript(ctx, cpr);
			return knh_Object_cid(scr);
		}
		else {
			DEBUG3("level=%d, cid=%s", level, CLASSN(DP(cpr)->this_cid));
			return DP(cpr)->this_cid;
		}
	}
	{
		knh_bytes_t name = knh_Token_tobytes(tk);
		knh_class_t cid = CLASS_unknown;
		if(SP(tk)->tt == TT_CMETHODN) {
			knh_index_t idx = knh_bytes_rindex(name, '.');
			KNH_ASSERT(idx != 1);
			name = knh_bytes_first(name, idx);
		}
		cid = knh_NameSpace_getClass(ctx, ns, name);
		if(cid == CLASS_unknown) {
			knh_Compiler_perror(ctx, cpr, KMSG_UCLASSN, (char*)name.buf);
			cid = DP(cpr)->this_cid;
			knh_Token_perrata(ctx, tk, CLASSN(cid));
		}
		else if(level > 0 && cid != DP(cpr)->this_cid) {
			knh_Compiler_perror(ctx, cpr, KMSG_DIFFCLASSN, (char*)name.buf);
			cid = DP(cpr)->this_cid;
			knh_Token_perrata(ctx, tk, CLASSN(cid));
		}
		return cid;
	}
}

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

static
void knh_Compiler_initMethodParams(Ctx *ctx, Compiler *cpr, Stmt *pstmt, NameSpace *ns, knh_class_t cid, int level)
{
	/* ['TYPEN', 'METHODFN', 'decl', '?{'] */
	knh_Compiler_initDecl(ctx, cpr);
	knh_Compiler_initThisVariable(ctx, cpr, cid);
	KNH_ASSERT(IS_Stmt(pstmt));
	while(IS_Stmt(pstmt)) {
		//DBG2_DUMP(ctx, pstmt, KNH_STDOUT, "param");
		if(SP(pstmt)->stt == STT_DECL) {
			knh_StmtDECL_name(ctx, pstmt, cpr, ns, -level);
		}
		pstmt = DP(pstmt)->next;
	}
}


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

static
MethodField *knh_tMethodField_find(Ctx *ctx, Compiler *cpr, knh_type_t rztype, int psize)
{
	int i, n = 0, size = knh_tMethodField_size;
	L_CONTINUE:;
	while(n < size) {
		MethodField *mf = knh_tMethodField(n);
		if(psize != DP(mf)->size) {
			n++;
			continue;
		}
		if(DP(mf)->params[0].type != rztype) {
			n++;
			continue;
		}
		for(i = 1; i < psize; i++) {
			knh_type_t vtype = DP(cpr)->vars[i].type;
			if(IS_NOTNULL(DP(cpr)->vars[i].value)) {
				vtype = TYPE_UNMASK_NN(vtype);
			}
			if(DP(mf)->params[i].type != vtype) {
				n++;
				goto L_CONTINUE;
			}
		}
//		DEBUG3("Found defined method field");
		return mf;
	}
	return NULL;
}

/* ------------------------------------------------------------------------ */
/* [METHOD] */

#ifndef StmtMACRO
#define StmtMETHOD_rtype(stmt)          DP(stmt)->tokens[0]
#define StmtMETHOD_class(stmt)          DP(stmt)->tokens[1]
#define StmtMETHOD_method(stmt)         DP(stmt)->tokens[2]
#define StmtMETHOD_params(stmt)         DP(stmt)->stmts[3]
#define StmtMETHOD_instmt(stmt)         DP(stmt)->stmts[4]
#endif

void knh_StmtMETHOD_name(Ctx *ctx, Stmt *stmt, Compiler *cpr, NameSpace *ns, int level)
{
	knh_flag_t flag   = knh_Stmt_metaflag__method(ctx, stmt);
	knh_type_t rztype = knh_Token_totype(ctx, StmtMETHOD_rtype(stmt), CLASS_Any, ns);
	knh_class_t mtd_cid = knh_Compiler_getMethodClass(ctx, cpr, StmtMETHOD_class(stmt), ns, level);
	knh_methodn_t mn = knh_Compiler_getMethodName(ctx, cpr, StmtMETHOD_method(stmt), level);
	Method *mtd = knh_Class_getMethod(ctx, mtd_cid, mn);

	knh_Compiler_initMethodParams(ctx, cpr, StmtMETHOD_params(stmt), ns, mtd_cid, 1);

	if(knh_methodn_isNew(mn) && mtd_cid != TYPE_UNMASK_NN(rztype)) {
		knh_Compiler_perror(ctx, cpr, KMSG_DIFFNEWTYPE, NULL);
		knh_Token_perrata(ctx, StmtMETHOD_rtype(stmt), CLASSN(mtd_cid));
		rztype = mtd_cid;
	}

	if(IS_NULL(mtd)) {
		int i, psize = DP(cpr)->vars_size;
		//DEBUG3("PSIZE=%d", psize);
		MethodField *mf = knh_tMethodField_find(ctx, cpr, rztype, psize);
		if(mf == NULL) {
			mf = new_MethodField(ctx, flag, psize);
			DP(mf)->params[0].type = rztype;
			DP(mf)->params[0].fn   = FIELDN_return;
			for(i = 1; i < psize; i++) {
				if(IS_NOTNULL(DP(cpr)->vars[i].value)) {
					DP(mf)->params[i].type = TYPE_UNMASK_NN(DP(cpr)->vars[i].type);
				}
				else {
					DP(mf)->params[i].type = DP(cpr)->vars[i].type;
				}
				DP(mf)->params[i].fn = DP(cpr)->vars[i].fn;
			}
			DEBUG3("Add new MethodField into tMethodField");
			knh_tMethodField_add(ctx, mf);
		}
		mtd = new_Method(ctx, flag, mtd_cid, mn, NULL);
		KNH_SETv(ctx, DP(mtd)->mf, mf);
		knh_Class_addMethod(ctx, mtd_cid, mtd);
	}
	else {
		int i, psize = DP(cpr)->vars_size;
		//DEBUG3("PSIZE=%d", psize);

		MethodField *mf = DP(mtd)->mf;
		if(DP(mtd)->cid != mtd_cid) {
			if(knh_Method_isFinal(mtd)) {
				char bufcm[CLASSNAME_BUFSIZ];
				knh_format_cmethodn(bufcm, sizeof(bufcm), DP(mtd)->cid, mn);
				knh_Compiler_perror(ctx, cpr, KMSG_EOVERRIDE, bufcm);
				knh_Stmt_done(ctx, stmt);
				return;
			}
			mtd = new_Method(ctx, flag, mtd_cid, mn, NULL);
			KNH_SETv(ctx, DP(mtd)->mf, mf);
			knh_Class_addMethod(ctx, mtd_cid, mtd);
		}
		if(!knh_StmtMETA_isOverride(stmt) && !knh_Method_isAbstract(mtd) && DP(mtd)->mn != METHODN_main) {
			char bufcm[CLASSNAME_BUFSIZ];
			knh_format_cmethodn(bufcm, sizeof(bufcm), mtd_cid, mn);
			knh_Compiler_perror(ctx, cpr, KMSG_AOVERRIDE, bufcm);
			knh_Stmt_done(ctx, stmt);
			return;
		}
		if(DP(mf)->params[0].type != rztype) {
			knh_wbuf_t wb = knh_Context_wbuf(ctx);
			knh_Method__k(ctx, mtd, wb.w, KNH_NULL);
			knh_Compiler_perror(ctx, cpr, KMSG_DIFFRTYPE, knh_wbuf_top(wb));
			knh_wbuf_clear(wb);
			knh_Stmt_done(ctx, stmt);
			return;
		}
		if(psize != DP(mf)->size) {
			knh_wbuf_t wb = knh_Context_wbuf(ctx);
			knh_Method__k(ctx, mtd, wb.w, KNH_NULL);
			knh_Compiler_perror(ctx, cpr, KMSG_DIFFPSIZE, knh_wbuf_top(wb));
			knh_wbuf_clear(wb);
			knh_Stmt_done(ctx, stmt);
			return;
		}
		for(i = 1; i < psize; i++) {
			knh_type_t ptype = DP(mf)->params[i].type;
			knh_type_t vtype = DP(cpr)->vars[i].type;
			if(IS_NOTNULL(DP(cpr)->vars[i].value)) {
				vtype = TYPE_UNMASK_NN(vtype);
			}
			if(ptype != vtype) {
				knh_fieldn_t fn = DP(cpr)->vars[i].fn;
				char buf[CLASSNAME_BUFSIZ * 2];
				knh_snprintf(buf, sizeof(buf), "(#%d) %s%s %s >> %s%s %s", i, TYPEQN(vtype), FIELDN(fn), TYPEQN(ptype), FIELDN(fn));
				knh_Compiler_perror(ctx, cpr, KMSG_DIFFPTYPE, buf);
				knh_Stmt_done(ctx, stmt);
				return;
			}
		}
//		for(i = 1; i < psize; i++) {
//			knh_type_t pfn = DP(mf)->params[i].fn;
//			knh_type_t vfn = DP(cpr)->vars[i].fn;
//			if(pfn != vfn) {
//				char buf[CLASSNAME_BUFSIZ];
//				knh_snprintf(buf, sizeof(buf), "(#%d) %s", i, FIELDN(pfn));
//				knh_Compiler_perror(ctx, cpr, KMSG_DIFFPNAME, buf);
//			}
//		}
	}
	{
		Stmt *instmt = StmtMETHOD_instmt(stmt);
		if(SP(instmt)->stt == STT_DONE && level != -1) {
			DEBUG3("@Abstract");
			knh_Stmt_done(ctx, stmt);
		}
		else {
			knh_Token_setCONST(ctx, StmtMETHOD_method(stmt), UP(mtd));
		}
	}
}

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

static
knh_bool_t knh_Compiler_checkLastReturn(Ctx *ctx, Compiler *cpr, Stmt *stmt, Method *mtd)
{
	Stmt *last_stmt = knh_Stmt_tail(stmt);
	knh_stmt_t stt = SP(last_stmt)->stt;
	if(stt == STT_RETURN || stt == STT_THROW || stt == STT_ERR) {
		return 1;
	}
	else if(stt == STT_IF) {
		if(knh_Compiler_checkLastReturn(ctx, cpr, StmtIF_truecase(last_stmt), NULL) &&
				knh_Compiler_checkLastReturn(ctx, cpr, StmtIF_falsecase(last_stmt), NULL)) {
			return 1;
		}
	}
	if(mtd == NULL) return 0;
	if(knh_Method_isNew(mtd) || knh_Method_rtype(mtd) == TYPE_void) {
		Stmt *rstmt = new_Stmt(ctx, 0, STT_RETURN);
		knh_Stmt_tail_append(ctx, last_stmt, rstmt);
	}
	else {
		Stmt *estmt = new_Stmt(ctx, 0, STT_THROW);
		knh_Stmt_add(ctx, estmt, TM(new_TokenCONST(ctx, FL(last_stmt), KNH_NULL)));
		knh_perror(ctx, SP(last_stmt)->fileid, SP(last_stmt)->line, KMSG_NORETURN, NULL);
		knh_Stmt_tail_append(ctx, last_stmt, estmt);
	}
	return 1;
}

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

void knh_StmtMETHOD_cmpl(Ctx *ctx, Stmt *stmt, Compiler *cpr, NameSpace *ns, knh_type_t reqt, int level)
{
	knh_class_t prev_cid = DP(cpr)->this_cid;
	Method *mtd = (Method*)DP(StmtMETHOD_method(stmt))->data;
	KNH_ASSERT(IS_Method(mtd));
	KNH_ASSERT(IS_Stmt(StmtMETHOD_instmt(stmt)));

	KNH_SETv(ctx, DP(cpr)->method, mtd);
	DP(cpr)->this_cid = DP(mtd)->cid;

	knh_Compiler_startCompilation(ctx, cpr, StmtMETHOD_instmt(stmt));
	knh_Compiler_initMethodParams(ctx, cpr, StmtMETHOD_params(stmt), ns, DP(mtd)->cid, 2);

	knh_Stmt_names(ctx, StmtMETHOD_instmt(stmt), cpr, ns, 2);
	KNH_ASM_PUSH_INITLOCALs(ctx, cpr);

	knh_Compiler_checkLastReturn(ctx, cpr, StmtMETHOD_instmt(stmt), mtd);

	knh_Stmt_cmpls(ctx, StmtMETHOD_instmt(stmt), cpr, ns, 2);

	knh_Compiler_endCompilation(ctx, cpr);
	DP(cpr)->this_cid = prev_cid;
}

/* ======================================================================== */
/* [RETURN] */

void knh_StmtRETURN_cmpl(Ctx *ctx, Stmt *stmt, Compiler *cpr, NameSpace *ns, knh_type_t reqt, int level)
{
	knh_type_t rtype = knh_pmztype_totype(ctx, knh_Method_rtype(DP(cpr)->method), DP(cpr)->this_cid);
	if(rtype == TYPE_void) {
		if(DP(stmt)->size > 0) {
			knh_Compiler_perror(ctx, cpr, KMSG_IGRETURN, NULL);
		}
		KNH_ASM_RET__OBJ(ctx, cpr, KNH_VOID);
	}
	else if(knh_Method_isNew(DP(cpr)->method)) {
		if(DP(stmt)->size > 0) {
			knh_Compiler_perror(ctx, cpr, KMSG_IGRETURN, NULL);
		}
		KNH_ASM_RET__sfp(ctx, cpr, 0);  /* return sfp[0] this */
	}
	else if(DP(stmt)->size == 0) {
		knh_Compiler_perror(ctx, cpr, KMSG_NORETURNVALUE, NULL);
		if(TYPE_ISNULLABLE(rtype)) {
			KNH_ASM_RET__OBJ(ctx, cpr, KNH_NULL);
		}
		else {
			KNH_ASM_THROW__OBJ(ctx, cpr, KNH_NULL);
		}
	}
	else {
		KNH_ASSERT(DP(stmt)->size > 0);
		knh_Stmt_terms_cmpl(ctx, stmt, 0, cpr, ns, rtype, 0 /* MC_FIRST*/);
		KNH_ASM_RET(ctx, cpr, DP(stmt)->terms[0]);
	}
}

/* ------------------------------------------------------------------------ */
/* [FORMAT] */

void knh_StmtFORMAT_name(Ctx *ctx, Stmt *stmt, Compiler *cpr, NameSpace *ns, int level)
{
	TODO();
}

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

void knh_StmtFORMAT_cmpl(Ctx *ctx, Stmt *stmt, Compiler *cpr, NameSpace *ns, knh_type_t reqt, int level)
{
	TODO();
}

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

#ifdef __cplusplus
}
#endif
