/*********************************************************************************
 * PROJECT: MiMic
 * --------------------------------------------------------------------------------
 *
 * This file is part of MiMic
 * Copyright (C)2011 Ryo Iizuka
 *
 * MiMic 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 3 of the　License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * For further information please contact.
 *	http://nyatla.jp/
 *	<airmail(at)ebony.plala.or.jp> or <nyatla(at)nyatla.jp>
 *
 *********************************************************************************/
#include "cRemoteMcuRequestParser.h"


#define ST_PARSE_PATH 1
#define ST_PARSE_QUERY_NAME 2
#define ST_PARSE_QUERY_VALUE 3		//Query読み出し中
#define ST_PARSE_QUERY_VALUE_BC 4	//BC読み出し中
#define ST_PARSE_QUERY_VALUE_DB 5


/**
 * IDテーブル
 */
struct TTextIdTbl{
	const char* n;
	NyLPC_TUInt8 id;
};
const struct TTextIdTbl TRemoteMcuRequest_content_id_table[]=
{
	{"/",TRemoteMcuRequest_CONTENT_ID_INDEX},
	{"/setup.api",TRemoteMcuRequest_CONTENT_ID_SETUP},
	{"/mvm.api",TRemoteMcuRequest_CONTENT_ID_MIMICBC},
	{"/status.api",TRemoteMcuRequest_CONTENT_ID_STATUS},
	{"/mimic.css",TRemoteMcuRequest_CONTENT_ID_CSS},
	{"/mimiclogo.png",TRemoteMcuRequest_CONTENT_ID_LOGO},
	{NULL,TRemoteMcuRequest_CONTENT_ID_UNKNOWN}
};
const struct TTextIdTbl TRemoteMcuRequest_qname_id_table[]=
{
	{"v",TRemoteMcuRequest_QNAME_ID_V},
	{"p",TRemoteMcuRequest_QNAME_ID_P},
	{"c",TRemoteMcuRequest_QNAME_ID_C},
	{"bc",TRemoteMcuRequest_QNAME_ID_BC},
	{NULL,TRemoteMcuRequest_CONTENT_ID_UNKNOWN}
};


static NyLPC_TBool updateQueryValue(TRemoteMcuRequest_CONTENT_ID i_content_id,TRemoteMcuRequest_QNAME_ID i_qid,const NyLPC_TcStr_t* i_qval,struct TRemoteMcuRequest* o_out);
static void initQueryValue(TRemoteMcuRequest_CONTENT_ID i_content_id,struct TRemoteMcuRequest* o_out);



static NyLPC_TBool message_handler(NyLPC_TcHttpBasicHeaderParser_t* i_inst,const NyLPC_TChar* i_name,NyLPC_TChar i_c,struct NyLPC_THttpBasicHeader* o_out)
{
	(void)i_inst;
	(void)i_name;
	(void)i_c;
	(void)o_out;
	return NyLPC_TBool_TRUE;
}


NyLPC_TUInt8 TTextIdTbl_getMatchId(const NyLPC_TcStr_t* i_str,const struct TTextIdTbl i_tbl[])
{
	int i;
	for(i=0;i_tbl[i].n!=NULL;i++){
		if(NyLPC_cStr_isEqual(i_str,i_tbl[i].n)){
			break;
		}
	}
	return i_tbl[i].id;
}



/**
 * RESTパーサ。
 * 構文エラーの場合は、FALSEを返します。
 * TRUEの場合は、返却したデータの構成をチェックしてください。
 */
static NyLPC_TBool urlHandler(NyLPC_TcHttpBasicHeaderParser_t* i_inst,NyLPC_TChar i_c,struct NyLPC_THttpBasicHeader* o_out)
{
	NyLPC_TUInt16 ol;
	TcRemoteMcuRequestParser_t* inst=(TcRemoteMcuRequestParser_t*)i_inst;
	struct TRemoteMcuRequest* out=(struct TRemoteMcuRequest*)o_out;
	switch(inst->_astate){
	case ST_PARSE_PATH:
		switch(i_c){
		case '\0':
		case '?':
			out->_content_id=TTextIdTbl_getMatchId(&(inst->_tstr),TRemoteMcuRequest_content_id_table);
			//未知のパス文字列なら、PATH部分を保存
			if(out->_content_id==TRemoteMcuRequest_CONTENT_ID_UNKNOWN){
				strcpy(out->content.unknown.path,NyLPC_cStr_str(&(inst->_tstr)));
			}
			initQueryValue(out->_content_id,out);//初期化
			NyLPC_cStr_clear(&(inst->_tstr));
			inst->_astate=ST_PARSE_QUERY_NAME;//クエリ名解析へ
			return NyLPC_TBool_TRUE;
		default:
			break;
		}
		break;
	case ST_PARSE_QUERY_NAME:
		switch(i_c){
		case '\0':
		case '&':
		case '=':
			//Query確定。
			inst->_content_qery_name_id=TTextIdTbl_getMatchId(&(inst->_tstr),TRemoteMcuRequest_qname_id_table);
			NyLPC_cStr_clear(&(inst->_tstr));
			if(i_c=='='){
				//クエリ値がある場合
				if(inst->_content_qery_name_id==TRemoteMcuRequest_QNAME_ID_BC && out->_content_id==TRemoteMcuRequest_CONTENT_ID_MIMICBC){
					inst->_astate=ST_PARSE_QUERY_VALUE_BC;//MIMICBCのBCパラメータの場合
					//binary出力バッファのセットアップ
					NyLPC_TUInt32ArrayPtr_setBuf(&(inst->bin_ptr),out->content.mvm.vm_instruction.bc_buf,TRemoteMcuRequest_SIZE_OF_IBUF);
				} else if(inst->_content_qery_name_id==TRemoteMcuRequest_QNAME_ID_P && out->_content_id==TRemoteMcuRequest_CONTENT_ID_SETUP){
					inst->_astate=ST_PARSE_QUERY_VALUE_DB;//MIMICBCのDBパラメータパーサを借用。
					NyLPC_TUInt32ArrayPtr_setBuf(&(inst->bin_ptr),out->content.setup.param_buf,TRemoteMcuRequest_SIZE_OF_SETUP_PARAM);
				}else{
					inst->_astate=ST_PARSE_QUERY_VALUE;//それ以外の場合
				}
			}else{
				if(!updateQueryValue(out->_content_id,inst->_content_qery_name_id,NULL,out)){
					//ERROR
					NyLPC_OnErrorGoto(ERROR);
				}
			}
			return NyLPC_TBool_TRUE;
		default:
			break;
		}
		break;
	case ST_PARSE_QUERY_VALUE:
		switch(i_c){
		case '&':
		case '\0':
			//クエリ値確定。状態を更新する。
			if(!updateQueryValue(out->_content_id,inst->_content_qery_name_id,&(inst->_tstr),out)){
				//ERROR
				NyLPC_OnErrorGoto(ERROR);
			}
			NyLPC_cStr_clear(&(inst->_tstr));
			inst->_astate=ST_PARSE_QUERY_NAME;//クエリ名解析へ
			return NyLPC_TBool_TRUE;
		default:
			break;
		}
		break;
	case ST_PARSE_QUERY_VALUE_DB:
		NyLPC_Assert(
			(TRemoteMcuRequest_CONTENT_ID_MIMICBC==out->_content_id)||
			(TRemoteMcuRequest_CONTENT_ID_SETUP==out->_content_id));
		switch(i_c){
		case '&':
		case '\0':
			//区切りのいいところで終わってる？
			if(NyLPC_cMiMicDbCompiler_hasFragment(&(inst->_dbcmp))){
				//ERROR
				NyLPC_OnErrorGoto(ERROR);
			}
			//データの長さを計算
			switch(out->_content_id){
			case TRemoteMcuRequest_CONTENT_ID_MIMICBC:
				out->content.mvm.vm_instruction.db_len=((NyLPC_TUInt8*)(inst->bin_ptr.ptr)-(NyLPC_TUInt8*)(out->content.mvm.vm_instruction.db_part))/sizeof(NyLPC_TUInt32);
				break;
			case TRemoteMcuRequest_CONTENT_ID_SETUP:
				out->content.setup.param_len=TRemoteMcuRequest_SIZE_OF_SETUP_PARAM-inst->bin_ptr.len;
				break;
			default:
				NyLPC_Abort();
			}
			//終端しているなら、次のクエリへ
			inst->_astate=ST_PARSE_QUERY_NAME;
			break;
		default:
			switch(NyLPC_cMiMicDbCompiler_compileFragment2(&(inst->_dbcmp),i_c,inst->bin_ptr.ptr))
			{
			case NyLPC_TcMiMicDbCompiler_RET_CONTINUE:
				break;
			case NyLPC_TcMiMicDbCompiler_RET_OK:
				//
				if(!NyLPC_TUInt32ArrayPtr_seek(&(inst->bin_ptr),1)){
					//ERROR
					NyLPC_OnErrorGoto(ERROR);
				}
				break;
			case NyLPC_TcMiMicDbCompiler_RET_ERROR:
			default:
				//ERROR
				NyLPC_OnErrorGoto(ERROR);
			}
		}
		return NyLPC_TBool_TRUE;
	case ST_PARSE_QUERY_VALUE_BC:
		//このケースはTRemoteMcuRequest_CONTENT_ID_MIMICBC==out->_content_idの時だけ使える。
		NyLPC_Assert(TRemoteMcuRequest_CONTENT_ID_MIMICBC==out->_content_id);
		switch(i_c){
		case '&':
		case '\0':
			//突然終わるのはダメです。
			//ERROR
			NyLPC_OnErrorGoto(ERROR);
		default:
			//コンパイル
			switch(NyLPC_cMiMicBcCompiler_compileFragment2(&(inst->_txtcmp),i_c,&(inst->bin_ptr),&ol))
			{
			case NyLPC_TcMiMicTxtCompiler_RET_OK:
				//命令確定。
				break;
			case NyLPC_TcMiMicTxtCompiler_RET_OK_END:
				//命令終端検出->モード切替
				out->content.mvm.vm_instruction.txt_len=TRemoteMcuRequest_SIZE_OF_IBUF-inst->bin_ptr.len+ol;
				out->content.mvm.vm_instruction.db_part=inst->bin_ptr.ptr;
				inst->_astate=ST_PARSE_QUERY_VALUE_DB;
				break;
			case NyLPC_TcMiMicTxtCompiler_RET_CONTINUE:
				//何もしない
				break;
			case NyLPC_TcMiMicTxtCompiler_RET_NG:
			default:
				//ERROR
				NyLPC_OnErrorGoto(ERROR);
			}
			break;
		}
		return NyLPC_TBool_TRUE;
	}
	//パースしているフラグメントの追記
	if(!NyLPC_cStr_put(&(inst->_tstr),i_c)){
		//ERROR
		NyLPC_OnErrorGoto(ERROR);
	}
	return NyLPC_TBool_TRUE;
ERROR:
	return NyLPC_TBool_FALSE;
}
/**
 * デフォルトハンドラ
 */
static const struct NyLPC_TcHttpBasicHeaderParser_Handler _handler=
{
	message_handler,
	urlHandler
};



void cRemoteMcuRequestParser_initialize(TcRemoteMcuRequestParser_t* i_inst)
{
	NyLPC_cHttpBasicHeaderParser_initialize(&(i_inst->super));
	i_inst->super._handler=&_handler;
	NyLPC_cStr_initialize(&(i_inst->_tstr),i_inst->_tstr_buf,16);
	NyLPC_cMiMicBcCompiler_initialize(&(i_inst->_txtcmp));
	NyLPC_cMiMicDbCompiler_initialize(&(i_inst->_dbcmp));
}
void cRemoteMcuRequestParser_finalize(TcRemoteMcuRequestParser_t* i_inst)
{
	(void)i_inst;
	NyLPC_cHttpBasicHeaderParser_finalize(&(i_inst->super));
	NyLPC_cStr_finalize(&(i_inst->_tstr));
	NyLPC_cMiMicBcCompiler_finalize(&(i_inst->_txtcmp));
	NyLPC_cMiMicDbCompiler_finalize(&(i_inst->_dbcmp));
}


NyLPC_TUInt16 cRemoteMcuRequestParser_parse(TcRemoteMcuRequestParser_t* i_inst,NyLPC_TcHttpStream_t* i_stream,struct TRemoteMcuRequest* o_out)
{
	TcRemoteMcuRequestParser_t* inst=(TcRemoteMcuRequestParser_t*)i_inst;
	//状態の初期化
	inst->_astate=ST_PARSE_PATH;

	//パースに失敗した。
	if(!NyLPC_cHttpBasicHeaderParser_parse(&(i_inst->super),i_stream,&(o_out->super)))
	{
		//エラーした。
		return 400;
	}
	NyLPC_cStr_clear(&(inst->_tstr));

	//HTTPリクエストではない。
	if(o_out->super.type!=NyLPC_THttpHeaderType_REQUEST){
		//エラーした。
		return 400;
	}
	//メソッドの制限
	if(o_out->super.startline.req.method!=NyLPC_THttpMethodType_GET){
		return 405;
	}
	//パースの整合性をテスト。
	switch(o_out->_content_id)
	{
	case TRemoteMcuRequest_CONTENT_ID_SETUP:
		//とりあえず7フラグメントあればOK
		switch(o_out->content.setup.cval){
		case TRemoteMcuRequest_QVAL_C_UPDATE:
			if(o_out->content.setup.param_len!=7){
				return 400;
			}
			break;
		default:
			break;
		}
		break;
	case TRemoteMcuRequest_CONTENT_ID_INDEX:
	case TRemoteMcuRequest_CONTENT_ID_UNKNOWN:
		//整合テスト無し
		break;
	case TRemoteMcuRequest_CONTENT_ID_MIMICBC:
		//バージョンは想定内？
		if(o_out->content.mvm.v==TRemoteMcuRequest_QVAL_VERSION_UNKNOWN){
			//エラー。バージョンミスマッチ
			return 406;
		}
		//TXT,BCの受信が出来ている?
		if(o_out->content.mvm.vm_instruction.db_part==NULL)
		{
			//ERROR
			return 400;
		}
		//命令長とストリーム長は計算済み
		break;
	default:
		NyLPC_Warning();
		break;
	}
	return 200;
}
static void initQueryValue(TRemoteMcuRequest_CONTENT_ID i_content_id,struct TRemoteMcuRequest* o_out)
{
	switch(i_content_id){
	case TRemoteMcuRequest_CONTENT_ID_SETUP:
		o_out->content.setup.param_len=0;
		o_out->content.setup.cval=TRemoteMcuRequest_QVAL_C_UNKNOWN;
		break;
	case TRemoteMcuRequest_CONTENT_ID_MIMICBC:
		o_out->content.mvm.v=TRemoteMcuRequest_QVAL_VERSION_UNKNOWN;
		o_out->content.mvm.o=TRemoteMcuRequest_QVAL_O_JSON;
		o_out->content.mvm.vm_instruction.db_part=NULL;
		o_out->content.mvm.vm_instruction.db_len=0;
		o_out->content.mvm.vm_instruction.txt_len=0;
		break;
	case TRemoteMcuRequest_CONTENT_ID_INDEX:
		break;
	case TRemoteMcuRequest_CONTENT_ID_UNKNOWN:
		//やることなす
		break;
	default:
		//ｳｪｰｲ
		break;
	}
	return;

}

static NyLPC_TBool updateQueryValue(TRemoteMcuRequest_CONTENT_ID i_content_id,TRemoteMcuRequest_QNAME_ID i_qid,const NyLPC_TcStr_t* i_qval,struct TRemoteMcuRequest* o_out)
{
	switch(i_content_id){
	case TRemoteMcuRequest_CONTENT_ID_SETUP:
		switch(i_qid){
		case TRemoteMcuRequest_QNAME_ID_C:
			if(i_qval!=NULL){
				if(NyLPC_cStr_isEqual(i_qval,"get")){
					o_out->content.setup.cval=TRemoteMcuRequest_QVAL_C_GET;
				}else if(NyLPC_cStr_isEqual(i_qval,"update")){
					o_out->content.setup.cval=TRemoteMcuRequest_QVAL_C_UPDATE;
				}
			}
			break;
		default:
			//知らない子は無視
			break;
		}

		//やることなす
		break;
	case TRemoteMcuRequest_CONTENT_ID_MIMICBC:
		//vパラメータとbcパラメータ。
		switch(i_qid){
		case TRemoteMcuRequest_QNAME_ID_V:
			if(i_qval!=NULL){
				if(NyLPC_cStr_isEqual(i_qval,"1")){
					o_out->content.mvm.v=TRemoteMcuRequest_QVAL_VERSION_1;
				}
			}
			break;
		case TRemoteMcuRequest_QNAME_ID_O:
			if(i_qval!=NULL){
				if(NyLPC_cStr_isEqual(i_qval,"j")){
					o_out->content.mvm.o=TRemoteMcuRequest_QVAL_O_JSON;
				}else if(NyLPC_cStr_isEqual(i_qval,"x")){
					o_out->content.mvm.o=TRemoteMcuRequest_QVAL_O_XML;
				}
			}
			break;
		default:
			//知らない子は無視
			break;
		}
		break;
	case TRemoteMcuRequest_CONTENT_ID_INDEX:
		//やることなす
		break;
	case TRemoteMcuRequest_CONTENT_ID_UNKNOWN:
		//やることなす
		break;
	case TRemoteMcuRequest_CONTENT_ID_STATUS:
		break;
	default:
		//ｳｪｰｲ
		return NyLPC_TBool_FALSE;
	}
	return NyLPC_TBool_TRUE;
}
#define TEST
#ifndef TEST

NyLPC_TBool test(char* test_patt,struct TRemoteMcuRequest* req)
{
	NyLPC_TcHttpStream_t st;
	NyLPC_TcTcpSocket_t ts;
	TcRemoteMcuRequestParser_t inst;
	//TCPのオープン
	NyLPC_cTcpSocket_initialized(&ts,test_patt,strlen(test_patt));
	NyLPC_cHttpStream_initialize(&st,&ts);

	//各CGIの呼び出しをチェックする。

	cRemoteMcuRequestParser_initialize(&inst);
	if(cRemoteMcuRequestParser_parse(&inst,&st,req)!=200){
		return NyLPC_TBool_FALSE;
	}else{
		return NyLPC_TBool_TRUE;
	}
//	cRemoteMcuRequestParser_finalize(&inst);
}



#define PFX "GET "
#define SFX " HTTP/1.1\r\n\r\n"
void main(void)
{
	struct TRemoteMcuRequest req;
/*	puts("TEST /");
	if(	test(PFX"/"SFX,&req)&&
		(req._content_id==TRemoteMcuRequest_CONTENT_ID_INDEX)
		)
	{
		puts("OK");
	}else{
		puts("NG");
	}

*/
	puts("TEST /setup.api");
	if(	test(PFX"/setup.api?p=0001020300010203000102030001020300010203000102030001020300010203000102030001020300010203000102030001020300010203000102030001"SFX,&req)&&
		(req._content_id==TRemoteMcuRequest_CONTENT_ID_SETUP)
		)
	{
		puts("OK");
	}else{
		puts("NG");
	}

/*
	puts("TEST /mvm.api?v=1&bc=ZADA0500000003ZA.E00000032");
	if(	test(PFX"/mvm.api?v=1&bc=ZADA0500000003.E00000032"SFX,&req)&&
		(req._content_id==TRemoteMcuRequest_CONTENT_ID_MIMICBC)
		)
	{
		puts("OK");
	}else{
		puts("NG");
	}
*/
/*
	puts("TEST /unknown");
	if(	test(PFX"/unknown"SFX,&req)&&
		(req._content_id==TRemoteMcuRequest_CONTENT_ID_UNKNOWN)
		)
	{
		puts("OK");
	}else{
		puts("NG");
	}
*/
	return;

}
#endif
