/**
 * 
 */
package org.activejpa.entity;

import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNull;

import java.io.Serializable;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.activejpa.entity.testng.ActiveJpaAgentObjectFactory;
import org.activejpa.entity.testng.BaseModelTest;
import org.activejpa.jpa.JPA;
import org.testng.IObjectFactory;
import org.testng.ITestContext;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.ObjectFactory;
import org.testng.annotations.Test;

/**
 * @author ganeshs
 *
 */
public class EntityCollectionTest extends BaseModelTest {
	
	private DummyModel model;
	
	private DummyModel child1;
	
	private DummyModel child2;
	
	private DummyModel child3;
	
	/**
	 * HACK. `mvn test` will be run before the package is created. javaagent can be loaded only from a jar. Since the
	 * jar is not yet created, it will throw agent not found exception. This is a hack to get rid of that exception
	 */
	@ObjectFactory
	public IObjectFactory getObjectFactory(ITestContext context) throws Exception {
		return new ActiveJpaAgentObjectFactory();
	}

	@BeforeClass
	public void beforeClass() {
		JPA.instance.addPersistenceUnit("test");
	}
	
	public void setup() throws Exception {
		super.setup();
		model = createModel("testColumn1", "testColumn2");
		child1 = createModel("testChildColumn0", "testChildColumn2");
		child2 = createModel("testChildColumn1", "testChildColumn3");
		child3 = createModel("testChildColumn1", "testChildColumn3");
		model.addChild(child1);
		model.addChild(child2);
		model.addChild(child3);
		model.persist();
	}
	
	@Test
	public void shouldGetAll() {
		EntityCollection<DummyModel> collection = model.collection("children");
		List<DummyModel> children = collection.all();
		assertEquals(children.size(), 3);
	}
	
	@Test
	public void shouldGetCount() {
		assertEquals(model.collection("children").count(new Filter()), 3);
	}
	
	@Test
	public void shouldFindById() {
		assertEquals(model.collection("children").findById(child2.getId()), child2);
	}
	
	@Test
	public void shouldSearchAndReturnFirst() {
		assertEquals(model.collection("children").first("children.column1", "testChildColumn1"), child2);
	}
	
	@Test
	public void shouldSearchAndReturnNullIfNoElementExistOnFirst() {
		assertNull(model.collection("children").first("children.column1", "unknown"));
	}
	
	@Test
	public void shouldSearchAndReturnTheOnlyMatch() {
		assertEquals(model.collection("children").one("children.column1", "testChildColumn0", "children.column2", "testChildColumn2"), child1);
	}
	
	@Test
	public void shouldSearchByKeyValue() {
		assertEquals(model.collection("children").where("children.column1", "testChildColumn0"), Arrays.asList(child1));
	}
	
	@Test
	public void shouldSearchByMultipleKeyValues() {
		assertEquals(model.collection("children").where("children.column1", "testChildColumn0", "children.column2", "testChildColumn2"), Arrays.asList(child1));
	}
	
	@Test
	public void shouldSearchUsingFilter() {
		assertEquals(model.collection("children").where(new Filter(new Condition("children.column1", "testChildColumn0"), new Condition("children.column2", "testChildColumn2"))), Arrays.asList(child1));
	}
	
	@Test
	public void shouldAddItemToCollectionUsingAddMethod() {
		ParentWithAddMethod parent = new ParentWithAddMethod();
		EntityCollection<DummyModel> collection = new EntityCollection<DummyModel>(parent, "models", DummyModel.class);
		collection.add(model);
		assertEquals(parent.models.size(), 1);
	}
	
	@Test
	public void shouldAddItemToCollectionUsingGetter() {
		ParentWithGetter parent = new ParentWithGetter();
		EntityCollection<DummyModel> collection = new EntityCollection<DummyModel>(parent, "models", DummyModel.class);
		collection.add(model);
		assertEquals(parent.models.size(), 1);
	}
	
	@Test
	public void shouldAddItemToCollectionUsingField() {
		ParentWithField parent = new ParentWithField();
		EntityCollection<DummyModel> collection = new EntityCollection<DummyModel>(parent, "models", DummyModel.class);
		collection.add(model);
		assertEquals(parent.models.size(), 1);
	}
	
	@Test
	public void shouldRemoveItemFromCollectionUsingRemoveMethod() {
		ParentWithAddMethod parent = new ParentWithAddMethod();
		EntityCollection<DummyModel> collection = new EntityCollection<DummyModel>(parent, "models", DummyModel.class);
		collection.add(model);
		assertEquals(parent.models.size(), 1);
		collection.remove(model);
		assertEquals(parent.models.size(), 0);
	}
	
	@Test
	public void shouldRemoveItemFromCollectionUsingGetter() {
		ParentWithGetter parent = new ParentWithGetter();
		EntityCollection<DummyModel> collection = new EntityCollection<DummyModel>(parent, "models", DummyModel.class);
		collection.add(model);
		assertEquals(parent.models.size(), 1);
		collection.remove(model);
		assertEquals(parent.models.size(), 0);
	}
	
	@Test
	public void shouldRemoveItemFromCollectionUsingField() {
		ParentWithField parent = new ParentWithField();
		EntityCollection<DummyModel> collection = new EntityCollection<DummyModel>(parent, "models", DummyModel.class);
		collection.add(model);
		assertEquals(parent.models.size(), 1);
		collection.remove(model);
		assertEquals(parent.models.size(), 0);
	}
	
	private DummyModel createModel(String column1, String column2) {
		DummyModel model = new DummyModel();
		model.setColumn1(column1);
		model.setColumn2(column2);
		model.persist();
		return model;
	}
	
	public static class ParentWithAddMethod extends Model {
		
		private Set<DummyModel> models = new HashSet<DummyModel>();
		
		public void addModel(DummyModel model) {
			models.add(model);
		}
		
		public void removeModel(DummyModel model) {
			models.remove(model);
		}

		@Override
		public Serializable getId() {
			return null;
		}
	}
	
	public static class ParentWithGetter extends Model {
		
		private Set<DummyModel> models = new HashSet<DummyModel>();
		
		@Override
		public Serializable getId() {
			return null;
		}

		/**
		 * @return the models
		 */
		public Set<DummyModel> getModels() {
			return models;
		}

		/**
		 * @param models the models to set
		 */
		public void setModels(Set<DummyModel> models) {
			this.models = models;
		}
	}
	
	public static class ParentWithField extends Model {
		
		private Set<DummyModel> models = new HashSet<DummyModel>();
		
		@Override
		public Serializable getId() {
			return null;
		}
	}
}
