/*
 *  ADP (Another Data Processor) www.adp.la
 *  Copyright (C) 2010 Katsuhisa Ohfuji <katsuhisa@ohfuji.name>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation.
 *
 *  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 General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 *  MA 02110-1301, USA.
 */

#ifndef ADP_BUILTIN_LIST_H
#define ADP_BUILTIN_LIST_H

struct ExecContext_Item : public ExecContextRoot {
	ExecContext_Item(ExecContext *p_, void *f_, const PPredicate *pred_, VLocal *l) : ExecContextRoot(p_, f_, pred_, l) {}
	
	bool item_list(const PList *l, PException &excp, int idx = 1 ) { 
		const PObject *k = get<PString>(idx);
		if ( !k ) k = get<PInteger>(idx);
		if ( !k ) return RERR_argment_type( excp, 1);
		
		while ( l ) {
			const PList *item = dynamic_cast<const PList *>(l->lval());
			if ( item ) {
				const PObject *lk = item->lval();
				if ( lk->isc() && k->unify( *lk, this) ) {
					return unify<PObject>( idx+1, item->rval(), excp);
				}
			}
			l = dynamic_cast<const PList*>(l->rval());
		}
		return false;
	}

	bool item_array(const PArray *a, PException &excp, int idx = 1) { 
		const PString *s = get<PString>(idx);
		if ( s ) {
			int kidx = a->find_key( s->value );
			if ( kidx >= 0 ) {
				return unify<PObject>( idx + 1, (*a)[kidx], excp);
			} else {
				return false;
			}
		} else {
			const PInteger *k = get<PInteger>(idx);
			if ( k ) {
				if ( static_cast<size_t>(k->value) >= a->size() ) return RERR_range( excp, k->value, a->size());
				return unify<PObject>( idx + 1, (*a)[static_cast<size_t>(k->value)], excp);
			} else {
				return RERR_argment_type( excp, idx);
			}
		}
	}

	virtual bool first(PException &excp) { 
		if ( pred->size() != 3 ) return RERR_argment_number(excp, 3);
		const PList *l = get<PList>(0);
		if ( l ) return item_list( l, excp);
		
		const PArray *a = get<PArray>(0);
		if ( a ) return item_array( a, excp);
		
		if ( get<PNil>(0) != 0 ) return false;
		
		return RERR_argment_type( excp, 0);
	}
};

struct ExecContext_ItemKey : public ExecContextRoot {
	ExecContext_ItemKey(ExecContext *p_, void *f_, const PPredicate *pred_, VLocal *l) : ExecContextRoot(p_, f_, pred_, l) {}

	bool item_list(const PList *l, PException &excp, int idx = 1 ) { 
		string key;
		const PObject *s = get<PObject>(idx);
		if ( !s->cnv_string(key) ) { return RERR_argment_type( excp, idx); }

		while ( l ) {
			const PList *item = dynamic_cast<const PList *>(l->lval());
			if ( item ) {
				const PObject *lk = item->lval();
				string key2;
				if ( lk->isc() && lk->cnv_string(key2) && key2 == key ) {
					return unify<PObject>( idx+1, item->rval(), excp);
				}
			}
			l = dynamic_cast<const PList*>(l->rval());
		}
		return false;
	}
		
	bool item_array(const PArray *a, PException &excp, int idx = 1) { 
		string key;
		const PObject *s = get<PObject>(idx);
		if ( !s->cnv_string(key) ) { return RERR_argment_type( excp, idx); }
		int kidx = a->find_key( key );
		if ( kidx >= 0 ) {
			return unify<PObject>( idx + 1, (*a)[kidx], excp);
		} else {
			return false;
		}
	}

	virtual bool first(PException &excp) { 
		if ( pred->size() != 3 ) return RERR_argment_number(excp, 3);
		const PList *l = get<PList>(0);
		if ( l ) return item_list( l, excp);
		
		const PArray *a = get<PArray>(0);
		if ( a ) return item_array( a, excp);
		
		if ( get<PNil>(0) != 0 ) return false;
		
		return RERR_argment_type( excp, 0);
	}
};


struct ExecContext_Global : public ExecContext_Item {
	ExecContext_Global(ExecContext *p_, void *f_, const PPredicate *pred_, VLocal *l) : ExecContext_Item(p_, f_, pred_, l) {}
	virtual bool first(PException &excp) { 
		if ( pred->size() != 2 ) return RERR_argment_number(excp, 2);
		if ( global != 0 ) {
			return item_array( global, excp, 0);
		} else {
			return false;
			
		}
	}
};

struct ExecContext_SetGlobal : public ExecContextRoot {
	ExecContext_SetGlobal(ExecContext *p_, void *f_, const PPredicate *pred_, VLocal *l) : ExecContextRoot(p_, f_, pred_, l) {}

	virtual bool first(PException &excp) { 
		if ( pred->size() != 2 ) return RERR_argment_number(excp, 2);
		const PObject *item = get<PObject>(0);
		const PString *key = get<PString>(1);
		if ( !item ) return RERR_argment_type( excp, 0);
		if ( !key ) return RERR_argment_type( excp, 1);

		if ( !setGlobalItem( item, key->value, global) ) RERR_File_Operation(excp);
		sessionwriteflg = true;
		return true;
	}
};

struct ExecContext_Session : public ExecContext_Item {
	ExecContext_Session(ExecContext *p_, void *f_, const PPredicate *pred_, VLocal *l) : ExecContext_Item(p_, f_, pred_, l) {}
	virtual bool first(PException &excp) { 
		if ( pred->size() != 2 ) return RERR_argment_number(excp, 2);
		if ( session != 0 ) {
			return item_array( session, excp, 0);
		} else {
			//return unify<PObject>( 1, &pnil, excp);
			return false;
		}
	}
};

struct ExecContext_SetSession : public ExecContextRoot {
	ExecContext_SetSession(ExecContext *p_, void *f_, const PPredicate *pred_, VLocal *l) : ExecContextRoot(p_, f_, pred_, l) {}

	virtual bool first(PException &excp) { 
		if ( pred->size() != 2 ) return RERR_argment_number(excp, 2);
		const PObject *item = get<PObject>(0);
		const PString *key = get<PString>(1);
		if ( !item ) return RERR_argment_type( excp, 0);
		if ( !key ) return RERR_argment_type( excp, 1);

		if ( !setSessionItem(item, key->value, envmap, session, sessionid, sessionpath) ) RERR_File_Operation(excp);
		sessionwriteflg = true;
		return true;
	}
};

struct ExecContext_SetSessionCookie : public ExecContextRoot {
	ExecContext_SetSessionCookie(ExecContext *p_, void *f_, const PPredicate *pred_, VLocal *l) : ExecContextRoot(p_, f_, pred_, l) {}

	virtual bool first(PException &excp) { 
		if ( pred->size() != 4 ) return RERR_argment_number(excp, 4);
		const char *domain = cnv_charp(0);
		const char *path = cnv_charp(1);
		const char *expires = cnv_charp(2);
		const char *secure = cnv_charp(3);
	
		if ( domain )  session_cookie_domain = domain;
		if ( path ) session_cookie_path = path;
		if ( expires ) session_cookie_expires = expires;
		if ( secure ) session_cookie_secure = secure;

		return true;
	}
};


struct ExecContext_Each : public ExecContextBase {
	vector<const PObject*>	current;
	size_t					siz;
	size_t					idx;

	ExecContext_Each(ExecContext *p_, void *f_, const PPredicate *pred_, VLocal *l) : ExecContextBase(p_, f_, pred_, l), current(), siz(0), idx(0) {}

	virtual bool first(PException &excp) { 
		current.clear();
		idx = 0;
		siz = pred->size();
		if ( siz % 2 ) return RERR_argment_number(excp, 2);
		siz /= 2;
		for ( size_t i = 0; i < siz; i++ ) {
			const PObject *list = get<PObject>(i);
			if ( !list ) return RERR_argment_type(excp, i);
			if ( !list->iscollection() ) return RERR_argment_type(excp, i);
			current.push_back(list);
			const PVeriable *v = dynamic_cast<const PVeriable*>((*pred)[siz + i]);
			if ( !v ) return RERR_result_type(excp);
		}
		return backtrack(excp);
	}
	virtual bool backtrack(PException &excp) { 
		bool flg = false;
		for ( size_t i = 0; i < siz; i++ ) {
			if ( current[i] ) {
				const PObject *ret = &pnil;
				const PList *list = 0;
				const PArray *array;
				if ( (list = dynamic_cast<const PList*>(current[i])) != 0 ) {
					ret = list->lval();
					current[i] = list->rval();
					if ( ret != &pnil || current[i] != &pnil) flg = true;
				} else if ( (array = dynamic_cast<const PArray*>(current[i])) != 0 ) {
					if ( idx < array->size() ) {
						ret = (*array)[idx];
						flg = true;
					}
				}
				if ( !unify<PObject>( siz + i, ret, excp) ) flg = false;
			} else {
				unify<PObject>( siz + i, &pnil, excp);
			}
		}
		idx++;
		return flg;
	}

};

struct ExecContext_Size : public ExecContextRoot {
	ExecContext_Size(ExecContext *p_, void *f_, const PPredicate *pred_, VLocal *l) : ExecContextRoot(p_, f_, pred_, l) {}
	
	bool size_list(const PList *l, PException &excp ) { 
		size_t	cnt = 0;
		while ( l ) {
			++cnt;
			l = dynamic_cast<const PList*>(l->rval());
		}
		const PInteger  *result = pmm.newPInteger(pobjs, cnt);
		return unify<PInteger>( 1, result, excp);
	}

	bool size_array(const PArray *a, PException &excp) { 
		const PInteger  *result = pmm.newPInteger(pobjs, a->size());
		return unify<PInteger>( 1, result, excp);
	}

	virtual bool first(PException &excp) { 
		if ( pred->size() != 2 ) return RERR_argment_number(excp, 2);
		const PList *l = get<PList>(0);
		if ( l ) return size_list( l, excp);
		
		const PArray *a = get<PArray>(0);
		if ( a ) return size_array( a, excp);

		const PString *s = get<PString>(0);
		if ( s ) {
			const PInteger  *result = pmm.newPInteger(pobjs, s->value.size());
			return unify<PInteger>( 1, result, excp);
		}
		
		return RERR_argment_type( excp, 0);
	}
};


struct ExecContext_Keys : public ExecContextRoot {
	ExecContext_Keys(ExecContext *p_, void *f_, const PPredicate *pred_, VLocal *l) : ExecContextRoot(p_, f_, pred_, l) {}

	virtual bool first(PException &excp) { 
		if ( pred->size() != 2 ) return RERR_argment_number(excp, 2);
		const PArray *src = get<PArray>(0);
		if ( !src ) return RERR_argment_type( excp, 0);

		PArray *dst = pmm.newPArray(pobjs, src->hamap.size(), &pnil, true);	// 結果配列はmapのサイズで初期化される（パフォーマンスの為）

		size_t	i = 0;
		for ( PHashArrayMap::const_iterator ki = src->hamap.begin(); ki != src->hamap.end(); ki++, i++ ) {
			const PString  *key = pmm.newPString(pobjs,ki->first);
			dst->value[i] = key;
		}

		return unify<PArray>( 1, dst, excp);
	}

};

struct ExecContext_Remap : public ExecContextRoot {
	ExecContext_Remap(ExecContext *p_, void *f_, const PPredicate *pred_, VLocal *l) : ExecContextRoot(p_, f_, pred_, l) {}
	
	virtual bool first(PException &excp) { 
		if ( pred->size() != 3 ) return RERR_argment_number(excp, 3);
		const PArray *src = get<PArray>(0);
		if ( !src ) return RERR_argment_type( excp, 0);
		
		const PArray *map = get<PArray>(1);
		if ( !map ) return RERR_argment_type( excp, 1);

		PArray *dst = pmm.newPArray(pobjs, map->size(), &pnil, true);	// 結果配列はmapのサイズで初期化される（パフォーマンスの為）
		if ( map->hamap.size() == 0 ) {
			return RERR_argment_type( excp, 1);	// mapのサイズを超える変換はNG
		} else {
			if ( src->hamap.size() == 0 ) return RERR_argment_type( excp, 0);
			// hashmapの置き換え（リマップ）	{ keyword => newkeyword, ...}
			size_t i = 0;
			for ( PHashArrayMap::const_iterator ki = map->hamap.begin(); ki != map->hamap.end(); ki++, i++ ) {
				PHashArrayMap::const_iterator hai = src->hamap.find(ki->first);
				string newkey;
				if ( !map->value[ki->second]->cnv_string(newkey) ) RERR_argment_type( excp, 1); // マッピングが文字列のハッシュになっていない
				if ( hai != src->hamap.end() ) {
					dst->value[i] = src->value[hai->second]->clone(pobjs);
					dst->hamap[newkey] = i;
				} else {
					// ↓ SRCからハッシュキーが見つからない場合はnilとする場合
					// dst->hamap[newkey] = i;
					return RERR_no_key_err( excp, ki->first); // エラーとする
				}
			}
			
		}

		return unify<PArray>( 2, dst, excp);
	}
};


struct ExecContext_ReArray : public ExecContextRoot {
	ExecContext_ReArray(ExecContext *p_, void *f_, const PPredicate *pred_, VLocal *l) : ExecContextRoot(p_, f_, pred_, l) {}
	
	virtual bool first(PException &excp) { 
		if ( pred->size() != 3 ) return RERR_argment_number(excp, 3);
		const PArray *src = get<PArray>(0);
		if ( !src ) return RERR_argment_type( excp, 0);
		
		const PArray *map = get<PArray>(1);
		if ( !map ) return RERR_argment_type( excp, 1);

		PArray *dst = pmm.newPArray(pobjs, map->size(), &pnil, true);	// 結果配列はmapのサイズで初期化される（パフォーマンスの為）
		// 配列の置き換え（リマップ）	{ 3, 1, 2, 0 }
		for ( size_t i = 0; i < map->size(); i++ ) {
			const PINTEGER *v = map->value[i]->c_integer();
			if ( !v ) return RERR_argment_type( excp, 1); // 数値のみ有効
			if ( (size_t)*v >= dst->size() ) return RERR_argment_type( excp, 1);	// mapのサイズを超える変換はNG
			dst->value[static_cast<size_t>(*v)] = src->value[i]->clone(pobjs);
		}

		return unify<PArray>( 2, dst, excp);
	}
};

struct ExecContext_Mkmap : public ExecContextRoot {
	ExecContext_Mkmap(ExecContext *p_, void *f_, const PPredicate *pred_, VLocal *l) : ExecContextRoot(p_, f_, pred_, l) {}
	
	virtual bool first(PException &excp) { 
		if ( pred->size() != 3 ) return RERR_argment_number(excp, 3);
		const PArray *src = get<PArray>(0);
		if ( !src ) return RERR_argment_type( excp, 0);
		
		const PArray *map = get<PArray>(1);
		if ( !map ) return RERR_argment_type( excp, 1);

		PArray *dst = pmm.newPArray(pobjs, map->size(), &pnil, true);	    // 結果配列はmapのサイズで初期化される（パフォーマンスの為）
		
		for ( size_t i = 0; i < map->size(); i++ ) {
			if ( i >= src->size() ) return RERR_argment_type( excp, 1); // 長さがたりない
			dst->value[i] = src->value[i]->clone(pobjs);
			string newkey;
			if ( !map->value[i]->cnv_string(newkey) ) return RERR_argment_type( excp, 1); // 文字列の配列ではない。
			dst->hamap[newkey] = i;
		}

		return unify<PArray>( 2, dst, excp);
	}
};


struct ExecContext_Slmap : public ExecContextRoot {
	ExecContext_Slmap(ExecContext *p_, void *f_, const PPredicate *pred_, VLocal *l) : ExecContextRoot(p_, f_, pred_, l) {}
	
	virtual bool first(PException &excp) { 
		if ( pred->size() != 3 ) return RERR_argment_number(excp, 3);
		const PArray *src = get<PArray>(0);
		if ( !src ) return RERR_argment_type( excp, 0);
		
		const PArray *map = get<PArray>(1);
		if ( !map ) return RERR_argment_type( excp, 1);

		PArray *dst = pmm.newPArray(pobjs, map->size(), &pnil, true);	// 結果配列はmapのサイズで初期化される（パフォーマンスの為）
		if ( map->hamap.size() == 0 ) {
			for ( size_t i = 0; i < map->size(); i++ ) {
				const PString *v= dynamic_cast<const PString*>(map->value[i]);
				if ( !v ) return RERR_argment_type( excp, 1); // 文字のみ有効
				PHashArrayMap::const_iterator hai = src->hamap.find(v->value);
				if ( hai != src->hamap.end() ) {
					dst->value[i] = src->value[hai->second]->clone(pobjs);
					dst->hamap[v->value] = i;
				} else {
					return RERR_no_key_err( excp, v->value); // エラーとする
				}				
			}
		} else {
			return RERR_argment_type( excp, 1); 
		}

		return unify<PArray>( 2, dst, excp);
	}
};


struct ExecContext_Sort : public ExecContextRoot {
	ExecContext_Sort(ExecContext *p_, void *f_, const PPredicate *pred_, VLocal *l) : ExecContextRoot(p_, f_, pred_, l) {}
	
	virtual bool first(PException &excp) { 
		if ( pred->size() < 2 || 3 < pred->size() ) return RERR_argment_number(excp, 3);
		const PArray *src = get<PArray>(0);
		if ( !src ) return RERR_argment_type( excp, 0);
		int idx = pred->size() - 1;
		PArray *dst = src->clone(pobjs);
		dst->hamap.clear();	// Keymap is broken with sorting. 

		if ( dst->size() > 1 ) {
			if ( typeid(*(*dst)[0]) == typeid(const PString) ) {
				if ( pred->size() == 3 ) return RERR_argment_number(excp, 2);
				sort( dst->value.begin(), dst->value.end(), ComparePObject<PString>() );
			} else if ( typeid(*(*dst)[0]) == typeid(const PInteger) ) {
				if ( pred->size() == 3 ) return RERR_argment_number(excp, 2);
				sort( dst->value.begin(), dst->value.end(), ComparePObject<PInteger>() );
			} else if ( typeid(*(*dst)[0]) == typeid(const PArray) ) {
				// ArrayのArrayのソート
				if ( pred->size() == 2 ) return RERR_argment_number(excp, 3);
				const PArray *key = get<PArray>(1);
				if ( key->size() < 1 ) RERR_argment_type( excp, 1);
				// ソートキーのタイプチェック
				if ( typeid(*(*key)[0]) == typeid(const PString) ) {
					vector<string> keys;
					for ( size_t i = 0; i < key->size(); i++ ) {
						if ( (*key)[i]->c_str() == 0 ) return RERR_argment_type( excp, 1);
						keys.push_back( (*key)[i]->c_str() );
					}
					ComparePArrayKey<string, string> cmp(keys);
					sort( dst->value.begin(), dst->value.end(), cmp );
				} else if ( typeid(*(*key)[0]) == typeid(const PInteger) ) {
					vector<PINTEGER> keys;
					for ( size_t i = 0; i < key->size(); i++ ) {
						if ( (*key)[i]->c_integer() == 0 ) return RERR_argment_type( excp, 1);
						keys.push_back( *(*key)[i]->c_integer() );
					}
					ComparePArrayKey<PINTEGER, size_t> cmp(keys);
					sort( dst->value.begin(), dst->value.end(), cmp );
				} else {
					return RERR_argment_type( excp, 1);
				}
			} else {
				return RERR_argment_type( excp, 0);
			}
		}
		return unify<PArray>( idx, dst, excp);
	}
};

struct ExecContext_RSort : public ExecContextRoot {
	ExecContext_RSort(ExecContext *p_, void *f_, const PPredicate *pred_, VLocal *l) : ExecContextRoot(p_, f_, pred_, l) {}
	
	virtual bool first(PException &excp) { 
		if ( pred->size() < 2 || 3 < pred->size() ) return RERR_argment_number(excp, 3);
		const PArray *src = get<PArray>(0);
		if ( !src ) return RERR_argment_type( excp, 0);
		int idx = pred->size() - 1;
		PArray *dst = src->clone(pobjs);
		dst->hamap.clear();	// Keymap is broken with sorting. 

		if ( dst->size() > 1 ) {
			if ( typeid(*(*dst)[0]) == typeid(const PString) ) {
				sort( dst->value.begin(), dst->value.end(), ComparePObjectReverse<PString>() );
			} else if ( typeid(*(*dst)[0]) == typeid(const PInteger) ) {
				sort( dst->value.begin(), dst->value.end(), ComparePObjectReverse<PInteger>() );
			} else if ( typeid(*(*dst)[0]) == typeid(const PArray) ) {
				// ArrayのArrayのソート
				if ( pred->size() == 2 ) return RERR_argment_number(excp, 3);
				const PArray *key = get<PArray>(1);
				if ( key->size() < 1 ) RERR_argment_type( excp, 1);
				// ソートキーのタイプチェック
				if ( typeid(*(*key)[0]) == typeid(const PString) ) {
					vector<string> keys;
					for ( size_t i = 0; i < key->size(); i++ ) {
						if ( (*key)[i]->c_str() == 0 ) return RERR_argment_type( excp, 1);
						keys.push_back( (*key)[i]->c_str() );
					}
					ComparePArrayKeyReverse<string, string> cmp(keys);
					sort( dst->value.begin(), dst->value.end(), cmp );
				} else if ( typeid(*(*key)[0]) == typeid(const PInteger) ) {
					vector<PINTEGER> keys;
					for ( size_t i = 0; i < key->size(); i++ ) {
						if ( (*key)[i]->c_integer() == 0 ) return RERR_argment_type( excp, 1);
						keys.push_back( *(*key)[i]->c_integer() );
					}
					ComparePArrayKeyReverse<PINTEGER, size_t> cmp(keys);
					sort( dst->value.begin(), dst->value.end(), cmp );
				} else {
					return RERR_argment_type( excp, 1);
				}
			} else {
				return RERR_argment_type( excp, 0);
			}
		}
		return unify<PArray>( idx, dst, excp);
	}
};

#endif
