package org.dynalang.mop.impl.test;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import junit.framework.TestCase;

import org.dynalang.mop.MetaobjectProtocol;
import org.dynalang.mop.BaseMetaobjectProtocol.Result;
import org.dynalang.mop.impl.StandardMetaobjectProtocolFactory;

public class TestStandardMetaobjectProtocolFactory extends TestCase {

    public void testDynaObject1() throws Exception {
	Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
	MetaobjectProtocol mop = StandardMetaobjectProtocolFactory.createStandardMetaobjectProtocol(true, false);
	
	DynaObject1 o1 = new DynaObject1();
	assertEquals(Result.notCallable, mop.call(o1, mop, (Map)null));
	assertEquals("called1", mop.call(o1, mop));
	assertEquals(Result.doesNotExist, mop.call(o1, "prop2", mop));
	assertEquals(Result.notCallable, mop.call(o1, "prop1", mop));
	
	assertEquals(Result.notDeleteable, mop.delete(o1, "prop1"));
	assertEquals(Result.doesNotExist, mop.delete(o1, "prop2"));
	
	assertEquals(Result.doesNotExist, mop.get(o1, 0L));
	assertEquals(Result.doesNotExist, mop.get(o1, "prop2"));
	assertEquals("value1", mop.get(o1, "prop1"));
	
	assertEquals(Boolean.FALSE, mop.has(o1, "prop2"));
	assertEquals(Boolean.TRUE, mop.has(o1, "prop1"));
	
	assertEquals(Result.notWritable, mop.put(o1, "prop1", "newValue", mop));
	assertEquals(Result.doesNotExist, mop.put(o1, "prop2", "newValue", mop));
	assertEquals(Result.doesNotExist, mop.put(o1, 0L, "newValue", mop));
	
	assertEquals("value1", mop.representAs(o1, String.class));
	assertEquals(Result.noRepresentation, mop.representAs(o1, Integer.class));
	assertSame(o1, mop.representAs(o1, DynaObject1.class));
	
	Iterator it = mop.propertyIds(o1);
	assertTrue(it.hasNext());
	assertEquals("prop1", it.next());
	assertFalse(it.hasNext());
	
	Iterator<Map.Entry> eit = mop.properties(o1);
	assertTrue(eit.hasNext());
	Map.Entry e = eit.next();
	assertEquals("prop1", e.getKey());
	assertEquals("value1", e.getValue());
	assertFalse(eit.hasNext());
    }
    
    public void testDynaObject2() throws Exception {
	Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
	MetaobjectProtocol mop = StandardMetaobjectProtocolFactory.createStandardMetaobjectProtocol(true, false);
	
	DynaObject2 o2 = new DynaObject2();
	assertEquals("called2-named", mop.call(o2, mop, (Map)null));
	assertEquals("called2-positional", mop.call(o2, mop));
	assertEquals(Result.doesNotExist, mop.call(o2, "prop2", mop));
	assertEquals(Result.doesNotExist, mop.call(o2, "prop1", mop));
	
	assertEquals(Result.doesNotExist, mop.delete(o2, "prop1"));
	assertEquals(Result.doesNotExist, mop.delete(o2, "prop2"));
	
	assertEquals(Result.ok, mop.put(o2, "prop2", "value2", mop));
	assertEquals("value2", mop.get(o2, "prop2"));
	assertEquals(Result.ok, mop.delete(o2, "prop2"));
	assertEquals(Result.doesNotExist, mop.get(o2, "prop2"));
	assertEquals(Boolean.FALSE, mop.has(o2, "prop2"));
	
	assertEquals(Result.ok, mop.put(o2, "prop2", "value3", mop));
	assertEquals("value3", mop.get(o2, "prop2"));
	assertEquals(Result.notCallable, mop.call(o2, "prop2", mop));

	assertEquals(Result.doesNotExist, mop.get(o2, 0L));
	assertEquals(Result.doesNotExist, mop.get(o2, "prop1"));
	
	assertEquals(Boolean.FALSE, mop.has(o2, "prop1"));
	assertEquals(Boolean.TRUE, mop.has(o2, "prop2"));
	
	assertEquals(Result.doesNotExist, mop.put(o2, 0L, "newValue", mop));
	
	assertEquals("value3", mop.representAs(o2, String.class));
	assertEquals(Result.noRepresentation, mop.representAs(o2, Integer.class));
	assertSame(o2, mop.representAs(o2, DynaObject2.class));
	
	// Fallback to POJO method
	assertEquals("tadatada", mop.call(o2, "callablePojoMethod", mop, "tada"));
	
	Object callablePojoMethod = mop.get(o2, "callablePojoMethod");
	assertFalse(callablePojoMethod instanceof Result);
	assertNotNull(callablePojoMethod);
	assertEquals("foofoo", mop.call(callablePojoMethod, mop, "foo"));
    }
    
    public void testList() throws Exception {
	Thread.currentThread().setContextClassLoader(
		getClass().getClassLoader());
	MetaobjectProtocol mop = 
	    StandardMetaobjectProtocolFactory.createStandardMetaobjectProtocol(
		    true, false);
	
	List<?> l = new ArrayList<Object>();
	assertEquals(Result.notCallable, mop.call(l, mop));
	assertEquals(Result.doesNotExist, mop.call(l, 0, mop));
	
	Iterator<Map.Entry> it = mop.properties(l);
	assertNotNull(it);
	assertFalse(it.hasNext());

	// Can't put into an empty list
	assertEquals(Result.doesNotExist, mop.put(l, 0, "0th", mop));
	
	// Can only add an element through the Java method
	assertEquals(Boolean.TRUE, mop.call(l, "add", mop, "0th"));
	assertEquals("0th", mop.get(l, 0));
	
	// Can't put out of bound
	assertEquals(Result.doesNotExist, mop.put(l, -1, "0th", mop));
	assertEquals(Result.doesNotExist, mop.put(l, 1, "0th", mop));

	assertEquals(Result.ok, mop.put(l, 0, "0th, 2nd time", mop));
	
	assertEquals(Boolean.TRUE, mop.call(l, "add", mop, "1st"));
	
	it = mop.properties(l);
	assertNotNull(it);
	assertTrue(it.hasNext());
	Map.Entry e = it.next();
	assertEquals(0, e.getKey());
	assertEquals("0th, 2nd time", e.getValue());
	assertTrue(it.hasNext());
	e = it.next();
	assertEquals(1, e.getKey());
	assertEquals("1st", e.getValue());
	assertFalse(it.hasNext());
	
	Iterator it2 = mop.propertyIds(l);
	assertNotNull(it2);
	assertTrue(it2.hasNext());
	assertEquals(0, it2.next());
	assertTrue(it2.hasNext());
	assertEquals(1, it2.next());
	assertFalse(it2.hasNext());
	
	assertEquals(Result.doesNotExist, mop.delete(l, -1));
	assertEquals(Result.notDeleteable, mop.delete(l, 0));
	assertEquals(Result.notDeleteable, mop.delete(l, 1));
	assertEquals(Result.doesNotExist, mop.delete(l, 2));

	assertEquals(Boolean.FALSE, mop.has(l, -1));
	assertEquals(Boolean.TRUE, mop.has(l, 0));
	assertEquals(Boolean.TRUE, mop.has(l, 1));
	assertEquals(Boolean.FALSE, mop.has(l, 2));
    }
    
    public void testMap() throws Exception {
	Thread.currentThread().setContextClassLoader(
		getClass().getClassLoader());
	
	MetaobjectProtocol mop = 
	    StandardMetaobjectProtocolFactory.createStandardMetaobjectProtocol(
		    true, false);
	
	Map m = new HashMap();
	assertEquals(Result.notCallable, mop.call(m, mop));
	assertEquals(Result.doesNotExist, mop.call(m, 0, mop));

	Iterator<Map.Entry> it = mop.properties(m);
	assertNotNull(it);
	assertFalse(it.hasNext());
	
	assertEquals(Result.doesNotExist, mop.delete(m, "foo"));
	assertEquals(Result.doesNotExist, mop.get(m, "foo"));
	assertEquals(Boolean.FALSE, mop.has(m, "foo"));
	
	assertEquals(Result.ok, mop.put(m, "foo", "bar", mop));
	assertEquals("bar", mop.get(m, "foo"));
	assertEquals(Boolean.TRUE, mop.has(m, "foo"));
	
	String s = "foobarbaz";
	Object hash = mop.call(s, "hashCode", mop);
	Object hashMethod = mop.get(s, "hashCode");
	assertEquals(hash, mop.call(hashMethod, mop));
	mop.put(m, "stringHashCode", hashMethod, mop);
	assertEquals(hash, mop.call(m, "stringHashCode", mop));

	assertEquals(Result.ok, mop.put(m, "baz", "bing", mop));
    }

    public void testCompositeProperties() throws Exception {
	Thread.currentThread().setContextClassLoader(
		getClass().getClassLoader());
	
	MetaobjectProtocol mop = 
	    StandardMetaobjectProtocolFactory.createStandardMetaobjectProtocol(
		    true, true);
	
	Map m = new HashMap();
	m.put("foo", "bar");
	m.put("baz", "bing");
	Iterator it = mop.propertyIds(m);
	Set s1 = new HashSet(Arrays.asList(new Object[] {"foo", "hashCode", 
		"entrySet", "isEmpty", "baz", "getClass", "get", "values", 
		"toString", "class", "put", "putAll", "clone", "remove", 
		"keySet", "containsValue", "notifyAll", "containsKey", "clear", 
		"equals", "size", "wait", "empty", "notify"}));
	Set s2 = new HashSet();
	while(it.hasNext()) {
	    s2.add(it.next());
	}
	assertEquals(s1, s2);
    }
}