package org.hibernate.jpa.test.criteria;

import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Collections;

import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.ManyToMany;
import javax.persistence.Query;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;

import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;


public class InheritedCollectionTestV3 extends BaseEntityManagerFunctionalTestCase {

	EntityManager em;
	
	Role customerRole;
	Customer c1;
	Customer c2;
	
	Role supporterRole;
	
	Supporter s1;
	Supporter s2;
	
	@Before
	public void before() {
		em = createEntityManager(Collections.emptyMap());
		
		customerRole = createRole("customerLevel1");
		c1 = createCustomer("c1", customerRole);
		c2 = createCustomer("c2", customerRole);
		
		supporterRole = createRole("supporterLevel1");
		s1 = createSupporter("s1", supporterRole);
		s2 = createSupporter("s2", supporterRole);
	}
	
	@After
	public void after() {
		if(em != null && em.isOpen()) {
			em.close();
		}
	}
	
	@Override
	protected Class<?>[] getAnnotatedClasses() {
		return new Class<?>[] {Role.class, UserBase.class, Customer.class, Supporter.class};
	}
	
	@Test
	public void test1() {
		
		System.out.println("created role:" + customerRole.id);
		
		List<Customer> resultList = findCustomerByRole(customerRole);
		System.out.println("****resultList=" + resultList);
		
		Assert.assertEquals(2, resultList.size());
		Assert.assertTrue(resultList.contains(c1));
		Assert.assertTrue(resultList.contains(c2));
	}
	
	@Test
	public void test2() {
	
		System.out.println("created role:" + supporterRole.id);
		
		List<UserBase> resultList = findAllUsers();
		System.out.println("****resultList=" + resultList);
		
		Assert.assertEquals(4, resultList.size());
		Assert.assertTrue(resultList.contains(s1));
		Assert.assertTrue(resultList.contains(s2));
		Assert.assertTrue(resultList.contains(c1));
		Assert.assertTrue(resultList.contains(c2));
	}
	
	public List<UserBase> findAllUsers() {
		EntityManager em = createEntityManager(Collections.emptyMap());
		Query query = em.createQuery("SELECT u from UserBase u");
		List<UserBase> list = query.getResultList();
		return list;
	}
	
	public List<Customer> findCustomerByRole(Role role) {
		EntityManager em = createEntityManager(Collections.emptyMap());
		CriteriaBuilder cb = em.getCriteriaBuilder();
		CriteriaQuery<Customer> cq = cb.createQuery(Customer.class);
		
		// search customer with role
		System.out.println(">>>findCustomerByRole");
		Root<Customer> root = cq.from( Customer.class );
		cq.select(root)
		.where( cb.isMember(role, root.<Set<Role>>get( "roles" )));
		System.out.println("<<<");
		
		TypedQuery<Customer> query = em.createQuery( cq );
		return query.getResultList();
	}
	
	public Customer createCustomer(String name, Role role) {
		EntityManager em = createEntityManager(Collections.emptyMap());
		EntityTransaction tx = em.getTransaction();
		tx.begin();
		
		Customer c = new Customer();
		c.name = name;
		c.companyName = name + ".companyName";
		
		c.roles.add(role);
		
		c = em.merge(c);
		tx.commit();
		return c;
	}
	
	public Supporter createSupporter(String name, Role role) {
		EntityManager em = createEntityManager(Collections.emptyMap());
		EntityTransaction tx = em.getTransaction();
		tx.begin();
		
		Supporter s = new Supporter();
		s.name = name;
		s.productName = name + ".productName";
		
		s.roles.add(role);
		
		s = em.merge(s);
		tx.commit();
		return s;
	}
	
	private Role createRole(String name) {
		EntityManager em = createEntityManager(Collections.emptyMap());
		EntityTransaction tx = em.getTransaction();
		tx.begin();
		
		Role r = new Role();
		r.name = name;
		
		r =  em.merge(r);
		tx.commit();
		return r;
	}
	
	@Entity(name="Role")
	public static class Role {
		@Id
		@GeneratedValue
		Integer id;
		
		String name;
		
		protected Role() {
		}
		
		protected Role(String name) {
			this.name = name;
		}
	}

	@Entity(name="UserBase")
	@Inheritance(strategy=InheritanceType.JOINED)
	public static class UserBase {
		@Id
		@GeneratedValue
		Integer id;
		
		String name;
				
		@ManyToMany
		Set<Role> roles = new HashSet<Role>();

		@Override
		public int hashCode() {
			final int prime = 31;
			int result = 1;
			result = prime * result + ((id == null) ? 0 : id.hashCode());
			result = prime * result + ((name == null) ? 0 : name.hashCode());
			return result;
		}

		@Override
		public boolean equals(Object obj) {
			if (this == obj)
				return true;
			if (obj == null)
				return false;
			if (getClass() != obj.getClass())
				return false;
			UserBase other = (UserBase) obj;
			if (id == null) {
				if (other.id != null)
					return false;
			} else if (!id.equals(other.id))
				return false;
			if (name == null) {
				if (other.name != null)
					return false;
			} else if (!name.equals(other.name))
				return false;
			return true;
		}

	}
	
	@Entity(name="Customer")
	public static class Customer extends UserBase{
		String companyName;
	}
	
	@Entity(name="Supporter")
	public static class Supporter extends UserBase {
		String productName;
	}
}
