/*******************************************************************************
 * Copyright (c) 2009 Luaj.org. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 ******************************************************************************/
package org.luaj.vm2;

import junit.framework.TestCase;

import org.luaj.vm2.TypeTest.MyData;
import org.luaj.vm2.libs.StringLib;
import org.luaj.vm2.libs.ThreeArgFunction;
import org.luaj.vm2.libs.TwoArgFunction;
import org.luaj.vm2.libs.ZeroArgFunction;

public class MetatableTest extends TestCase {

	private final String samplestring = "abcdef";
	private final Object sampleobject = new Object();
	private final MyData sampledata = new MyData();
	
	private final LuaValue    string        = LuaValue.valueOf(samplestring);
	private final LuaTable    table         = LuaValue.tableOf();
	private final LuaFunction function      = new ZeroArgFunction() { public LuaValue call() { return NONE;}};
	private final LuaThread   thread        = new LuaThread(new Globals(), function);
	private final LuaClosure  closure       = new LuaClosure(new Prototype(), new LuaTable());
	private final LuaUserdata userdata      = LuaValue.userdataOf(sampleobject);
	private final LuaUserdata userdatamt    = LuaValue.userdataOf(sampledata,table);
	
	protected void setUp() throws Exception {
		// needed for metatable ops to work on strings
		new StringLib();
	}

	protected void tearDown() throws Exception {
		super.tearDown();
		LuaBoolean.s_metatable = null;
		LuaFunction.s_metatable = null;
		LuaNil.s_metatable = null;
		LuaNumber.s_metatable = null;
//		LuaString.s_metatable = null;
		LuaThread.s_metatable = null;
	}

	public void testGetMetatable() {
		assertEquals( null, LuaValue.NIL.getmetatable() );
		assertEquals( null, LuaValue.TRUE.getmetatable() );
		assertEquals( null, LuaValue.ONE.getmetatable() );
//		assertEquals( null, string.getmetatable() );
		assertEquals( null, table.getmetatable() );
		assertEquals( null, function.getmetatable() );
		assertEquals( null, thread.getmetatable() );
		assertEquals( null, closure.getmetatable() );
		assertEquals( null, userdata.getmetatable() );
		assertEquals( table, userdatamt.getmetatable() );
	}

	public void testSetMetatable() {
		LuaValue mt = LuaValue.tableOf();
		assertEquals( null, table.getmetatable() );
		assertEquals( null, userdata.getmetatable() );
		assertEquals( table, userdatamt.getmetatable() );
		assertEquals( table, table.setmetatable(mt) );
		assertEquals( userdata, userdata.setmetatable(mt) );
		assertEquals( userdatamt, userdatamt.setmetatable(mt) );
		assertEquals( mt, table.getmetatable() );
		assertEquals( mt, userdata.getmetatable() );
		assertEquals( mt, userdatamt.getmetatable() );
		
		// these all get metatable behind-the-scenes
		assertEquals( null, LuaValue.NIL.getmetatable() );
		assertEquals( null, LuaValue.TRUE.getmetatable() );
		assertEquals( null, LuaValue.ONE.getmetatable() );
//		assertEquals( null, string.getmetatable() );
		assertEquals( null, function.getmetatable() );
		assertEquals( null, thread.getmetatable() );
		assertEquals( null, closure.getmetatable() );
		LuaNil.s_metatable = mt;
		assertEquals( mt, LuaValue.NIL.getmetatable() );
		assertEquals( null, LuaValue.TRUE.getmetatable() );
		assertEquals( null, LuaValue.ONE.getmetatable() );
//		assertEquals( null, string.getmetatable() );
		assertEquals( null, function.getmetatable() );
		assertEquals( null, thread.getmetatable() );
		assertEquals( null, closure.getmetatable() );
		LuaBoolean.s_metatable = mt;
		assertEquals( mt, LuaValue.TRUE.getmetatable() );
		assertEquals( null, LuaValue.ONE.getmetatable() );
//		assertEquals( null, string.getmetatable() );
		assertEquals( null, function.getmetatable() );
		assertEquals( null, thread.getmetatable() );
		assertEquals( null, closure.getmetatable() );
		LuaNumber.s_metatable = mt;
		assertEquals( mt, LuaValue.ONE.getmetatable() );
		assertEquals( mt, LuaValue.valueOf(1.25).getmetatable() );
//		assertEquals( null, string.getmetatable() );
		assertEquals( null, function.getmetatable() );
		assertEquals( null, thread.getmetatable() );
		assertEquals( null, closure.getmetatable() );
//		LuaString.s_metatable = mt;
//		assertEquals( mt, string.getmetatable() );
		assertEquals( null, function.getmetatable() );
		assertEquals( null, thread.getmetatable() );
		assertEquals( null, closure.getmetatable() );
		LuaFunction.s_metatable = mt;
		assertEquals( mt, function.getmetatable() );
		assertEquals( null, thread.getmetatable() );
		LuaThread.s_metatable = mt;
		assertEquals( mt, thread.getmetatable() );
	}
	
	public void testMetatableIndex() {
		assertEquals( table, table.setmetatable(null) );
		assertEquals( userdata, userdata.setmetatable(null) );
		assertEquals( userdatamt, userdatamt.setmetatable(null) );
		assertEquals( LuaValue.NIL, table.get(1) );
		assertEquals( LuaValue.NIL, userdata.get(1) );
		assertEquals( LuaValue.NIL, userdatamt.get(1) );
		
		// empty metatable
		LuaValue mt = LuaValue.tableOf();
		assertEquals( table, table.setmetatable(mt) );
		assertEquals( userdata, userdata.setmetatable(mt) );
		LuaBoolean.s_metatable = mt;
		LuaFunction.s_metatable = mt;
		LuaNil.s_metatable = mt;
		LuaNumber.s_metatable = mt;
//		LuaString.s_metatable = mt;
		LuaThread.s_metatable = mt;
		assertEquals( mt, table.getmetatable() );
		assertEquals( mt, userdata.getmetatable() );
		assertEquals( mt, LuaValue.NIL.getmetatable() );
		assertEquals( mt, LuaValue.TRUE.getmetatable() );
		assertEquals( mt, LuaValue.ONE.getmetatable() );
// 		assertEquals( StringLib.instance, string.getmetatable() );
		assertEquals( mt, function.getmetatable() );
		assertEquals( mt, thread.getmetatable() );
		
		// plain metatable
		LuaValue abc = LuaValue.valueOf("abc");
		mt.set( LuaValue.INDEX, LuaValue.listOf(new LuaValue[] { abc } ) );
		assertEquals( abc, table.get(1) );
		assertEquals( abc, userdata.get(1) );
		assertEquals( abc, LuaValue.NIL.get(1) );
		assertEquals( abc, LuaValue.TRUE.get(1) );
		assertEquals( abc, LuaValue.ONE.get(1) );
// 		assertEquals( abc, string.get(1) );
		assertEquals( abc, function.get(1) );
		assertEquals( abc, thread.get(1) );
		
		// plain metatable
		mt.set( LuaValue.INDEX, new TwoArgFunction() {
			public LuaValue call(LuaValue arg1, LuaValue arg2) {
				return LuaValue.valueOf( arg1.typename()+"["+arg2.tojstring()+"]=xyz" );
			}
			
		});
		assertEquals( "table[1]=xyz",    table.get(1).tojstring() );
		assertEquals( "userdata[1]=xyz", userdata.get(1).tojstring() );
		assertEquals( "nil[1]=xyz",      LuaValue.NIL.get(1).tojstring() );
		assertEquals( "boolean[1]=xyz",  LuaValue.TRUE.get(1).tojstring() );
		assertEquals( "number[1]=xyz",   LuaValue.ONE.get(1).tojstring() );
	//	assertEquals( "string[1]=xyz",   string.get(1).tojstring() );
		assertEquals( "function[1]=xyz", function.get(1).tojstring() );
		assertEquals( "thread[1]=xyz",   thread.get(1).tojstring() );
	}

	
	public void testMetatableNewIndex() {
		// empty metatable
		LuaValue mt = LuaValue.tableOf();
		assertEquals( table, table.setmetatable(mt) );
		assertEquals( userdata, userdata.setmetatable(mt) );
		LuaBoolean.s_metatable = mt;
		LuaFunction.s_metatable = mt;
		LuaNil.s_metatable = mt;
		LuaNumber.s_metatable = mt;
//		LuaString.s_metatable = mt;
		LuaThread.s_metatable = mt;
		
		// plain metatable
		final LuaValue fallback = LuaValue.tableOf();
		LuaValue abc = LuaValue.valueOf("abc");
		mt.set( LuaValue.NEWINDEX, fallback );
		table.set(2,abc);
		userdata.set(3,abc);
		LuaValue.NIL.set(4,abc);
		LuaValue.TRUE.set(5,abc);
		LuaValue.ONE.set(6,abc);
// 		string.set(7,abc);
		function.set(8,abc);
		thread.set(9,abc);
		assertEquals( abc, fallback.get(2) );
		assertEquals( abc, fallback.get(3) );
		assertEquals( abc, fallback.get(4) );
		assertEquals( abc, fallback.get(5) );
		assertEquals( abc, fallback.get(6) );
// 		assertEquals( abc, StringLib.instance.get(7) );
		assertEquals( abc, fallback.get(8) );
		assertEquals( abc, fallback.get(9) );
		
		// metatable with function call
		mt.set( LuaValue.NEWINDEX, new ThreeArgFunction() {
			public LuaValue call(LuaValue arg1, LuaValue arg2, LuaValue arg3) {
				fallback.rawset(arg2, LuaValue.valueOf( "via-func-"+arg3 ));
				return NONE;
			}
			
		});
		table.set(12,abc);
		userdata.set(13,abc);
		LuaValue.NIL.set(14,abc);
		LuaValue.TRUE.set(15,abc);
		LuaValue.ONE.set(16,abc);
// 		string.set(17,abc);
		function.set(18,abc);
		thread.set(19,abc);
		LuaValue via = LuaValue.valueOf( "via-func-abc" );
		assertEquals( via, fallback.get(12) );
		assertEquals( via, fallback.get(13) );
		assertEquals( via, fallback.get(14) );
		assertEquals( via, fallback.get(15) );
		assertEquals( via, fallback.get(16) );
//		assertEquals( via, StringLib.instance.get(17) );
		assertEquals( via, fallback.get(18) );
		assertEquals( via, fallback.get(19) );
	}


	private void checkTable( LuaValue t, 
			LuaValue aa, LuaValue bb, LuaValue cc, LuaValue dd, LuaValue ee, LuaValue ff, LuaValue gg,
			LuaValue ra, LuaValue rb, LuaValue rc, LuaValue rd, LuaValue re, LuaValue rf, LuaValue rg ) {
		assertEquals( aa, t.get("aa") );
		assertEquals( bb, t.get("bb") );
		assertEquals( cc, t.get("cc") );
		assertEquals( dd, t.get("dd") );
		assertEquals( ee, t.get("ee") );
		assertEquals( ff, t.get("ff") );
		assertEquals( gg, t.get("gg") );
		assertEquals( ra, t.rawget("aa") );
		assertEquals( rb, t.rawget("bb") );
		assertEquals( rc, t.rawget("cc") );
		assertEquals( rd, t.rawget("dd") );
		assertEquals( re, t.rawget("ee") );
		assertEquals( rf, t.rawget("ff") );
		assertEquals( rg, t.rawget("gg") );
	}

	private LuaValue makeTable( String key1, String val1, String key2, String val2 ) {
		return LuaValue.tableOf( new LuaValue[] {
				LuaValue.valueOf(key1), LuaValue.valueOf(val1),
				LuaValue.valueOf(key2), LuaValue.valueOf(val2),
		} );
	}
	
	public void testRawsetMetatableSet() {
		// set up tables
		LuaValue m = makeTable( "aa", "aaa", "bb", "bbb" );
		m.set(LuaValue.INDEX, m);
		m.set(LuaValue.NEWINDEX, m);
		LuaValue s = makeTable( "cc", "ccc", "dd", "ddd" ); 
		LuaValue t = makeTable( "cc", "ccc", "dd", "ddd" );
		t.setmetatable(m);
		LuaValue aaa = LuaValue.valueOf("aaa");
		LuaValue bbb = LuaValue.valueOf("bbb");
		LuaValue ccc = LuaValue.valueOf("ccc");
		LuaValue ddd = LuaValue.valueOf("ddd");
		LuaValue ppp = LuaValue.valueOf("ppp");
		LuaValue qqq = LuaValue.valueOf("qqq");
		LuaValue rrr = LuaValue.valueOf("rrr");
		LuaValue sss = LuaValue.valueOf("sss");
		LuaValue ttt = LuaValue.valueOf("ttt");
		LuaValue www = LuaValue.valueOf("www");
		LuaValue xxx = LuaValue.valueOf("xxx");
		LuaValue yyy = LuaValue.valueOf("yyy");
		LuaValue zzz = LuaValue.valueOf("zzz");
		LuaValue nil = LuaValue.NIL;
		
		// check initial values
		//             values via "bet()"           values via "rawget()"
		checkTable( s, nil,nil,ccc,ddd,nil,nil,nil, nil,nil,ccc,ddd,nil,nil,nil );
		checkTable( t, aaa,bbb,ccc,ddd,nil,nil,nil, nil,nil,ccc,ddd,nil,nil,nil );
		checkTable( m, aaa,bbb,nil,nil,nil,nil,nil, aaa,bbb,nil,nil,nil,nil,nil );

		// rawset()
		s.rawset("aa", www);
		checkTable( s, www,nil,ccc,ddd,nil,nil,nil, www,nil,ccc,ddd,nil,nil,nil );
		checkTable( t, aaa,bbb,ccc,ddd,nil,nil,nil, nil,nil,ccc,ddd,nil,nil,nil );
		checkTable( m, aaa,bbb,nil,nil,nil,nil,nil, aaa,bbb,nil,nil,nil,nil,nil );
		s.rawset("cc", xxx);
		checkTable( s, www,nil,xxx,ddd,nil,nil,nil, www,nil,xxx,ddd,nil,nil,nil );
		checkTable( t, aaa,bbb,ccc,ddd,nil,nil,nil, nil,nil,ccc,ddd,nil,nil,nil );
		checkTable( m, aaa,bbb,nil,nil,nil,nil,nil, aaa,bbb,nil,nil,nil,nil,nil );
		t.rawset("bb", yyy);
		checkTable( s, www,nil,xxx,ddd,nil,nil,nil, www,nil,xxx,ddd,nil,nil,nil );
		checkTable( t, aaa,yyy,ccc,ddd,nil,nil,nil, nil,yyy,ccc,ddd,nil,nil,nil );
		checkTable( m, aaa,bbb,nil,nil,nil,nil,nil, aaa,bbb,nil,nil,nil,nil,nil );
		t.rawset("dd", zzz);
		checkTable( s, www,nil,xxx,ddd,nil,nil,nil, www,nil,xxx,ddd,nil,nil,nil );
		checkTable( t, aaa,yyy,ccc,zzz,nil,nil,nil, nil,yyy,ccc,zzz,nil,nil,nil );
		checkTable( m, aaa,bbb,nil,nil,nil,nil,nil, aaa,bbb,nil,nil,nil,nil,nil );

		// set() invoking metatables
		s.set("ee", ppp);
		checkTable( s, www,nil,xxx,ddd,ppp,nil,nil, www,nil,xxx,ddd,ppp,nil,nil );
		checkTable( t, aaa,yyy,ccc,zzz,nil,nil,nil, nil,yyy,ccc,zzz,nil,nil,nil );
		checkTable( m, aaa,bbb,nil,nil,nil,nil,nil, aaa,bbb,nil,nil,nil,nil,nil );
		s.set("cc", qqq);
		checkTable( s, www,nil,qqq,ddd,ppp,nil,nil, www,nil,qqq,ddd,ppp,nil,nil );
		checkTable( t, aaa,yyy,ccc,zzz,nil,nil,nil, nil,yyy,ccc,zzz,nil,nil,nil );
		checkTable( m, aaa,bbb,nil,nil,nil,nil,nil, aaa,bbb,nil,nil,nil,nil,nil );
		t.set("ff", rrr);
		checkTable( s, www,nil,qqq,ddd,ppp,nil,nil, www,nil,qqq,ddd,ppp,nil,nil );
		checkTable( t, aaa,yyy,ccc,zzz,nil,rrr,nil, nil,yyy,ccc,zzz,nil,nil,nil );
		checkTable( m, aaa,bbb,nil,nil,nil,rrr,nil, aaa,bbb,nil,nil,nil,rrr,nil );
		t.set("dd", sss);
		checkTable( s, www,nil,qqq,ddd,ppp,nil,nil, www,nil,qqq,ddd,ppp,nil,nil );
		checkTable( t, aaa,yyy,ccc,sss,nil,rrr,nil, nil,yyy,ccc,sss,nil,nil,nil );
		checkTable( m, aaa,bbb,nil,nil,nil,rrr,nil, aaa,bbb,nil,nil,nil,rrr,nil );
		m.set("gg", ttt);
		checkTable( s, www,nil,qqq,ddd,ppp,nil,nil, www,nil,qqq,ddd,ppp,nil,nil );
		checkTable( t, aaa,yyy,ccc,sss,nil,rrr,ttt, nil,yyy,ccc,sss,nil,nil,nil );
		checkTable( m, aaa,bbb,nil,nil,nil,rrr,ttt, aaa,bbb,nil,nil,nil,rrr,ttt );
		
		// make s fall back to t
		s.setmetatable(LuaValue.tableOf(new LuaValue[] {LuaValue.INDEX,t,LuaValue.NEWINDEX,t}));
		checkTable( s, www,yyy,qqq,ddd,ppp,rrr,ttt, www,nil,qqq,ddd,ppp,nil,nil );
		checkTable( t, aaa,yyy,ccc,sss,nil,rrr,ttt, nil,yyy,ccc,sss,nil,nil,nil );
		checkTable( m, aaa,bbb,nil,nil,nil,rrr,ttt, aaa,bbb,nil,nil,nil,rrr,ttt );
		s.set("aa", www);
		checkTable( s, www,yyy,qqq,ddd,ppp,rrr,ttt, www,nil,qqq,ddd,ppp,nil,nil );
		checkTable( t, aaa,yyy,ccc,sss,nil,rrr,ttt, nil,yyy,ccc,sss,nil,nil,nil );
		checkTable( m, aaa,bbb,nil,nil,nil,rrr,ttt, aaa,bbb,nil,nil,nil,rrr,ttt );
		s.set("bb", zzz);
		checkTable( s, www,zzz,qqq,ddd,ppp,rrr,ttt, www,nil,qqq,ddd,ppp,nil,nil );
		checkTable( t, aaa,zzz,ccc,sss,nil,rrr,ttt, nil,zzz,ccc,sss,nil,nil,nil );
		checkTable( m, aaa,bbb,nil,nil,nil,rrr,ttt, aaa,bbb,nil,nil,nil,rrr,ttt );
		s.set("ee", xxx);
		checkTable( s, www,zzz,qqq,ddd,xxx,rrr,ttt, www,nil,qqq,ddd,xxx,nil,nil );
		checkTable( t, aaa,zzz,ccc,sss,nil,rrr,ttt, nil,zzz,ccc,sss,nil,nil,nil );
		checkTable( m, aaa,bbb,nil,nil,nil,rrr,ttt, aaa,bbb,nil,nil,nil,rrr,ttt );
		s.set("ff", yyy);
		checkTable( s, www,zzz,qqq,ddd,xxx,yyy,ttt, www,nil,qqq,ddd,xxx,nil,nil );
		checkTable( t, aaa,zzz,ccc,sss,nil,yyy,ttt, nil,zzz,ccc,sss,nil,nil,nil );
		checkTable( m, aaa,bbb,nil,nil,nil,yyy,ttt, aaa,bbb,nil,nil,nil,yyy,ttt );


	}
	
}
