/*
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * License: GNU Lesser General Public License (LGPL), version 2.1 or later.
 * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
 */

package org.hibernate.test.collection.delayedOperation;

import java.util.HashSet;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import org.hibernate.Hibernate;
import org.hibernate.Session;
import org.hibernate.annotations.LazyCollection;
import org.hibernate.annotations.LazyCollectionOption;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;

/**
 * Tests delayed operations that are queued for a PersistentSet. The Set must be
 * extra lazy to queue the operations.
 * @author Gail Badner
 */
public class SetDelayedOperationTest extends BaseCoreFunctionalTestCase {
	private Long parentId;
	private Long childId1;
	private Long childId2;

	@Override
	protected Class[] getAnnotatedClasses() {
		return new Class[] {
				Parent.class,
				Child.class
		};
	}

	@Before
	public void setup() {
		// start by cleaning up in case a test fails
		if ( parentId != null ) {
			cleanup();
		}

		Parent parent = new Parent();
		Child child1 = new Child( "Sherman" );
		Child child2 = new Child( "Yogi" );
		parent.addChild( child1 );
		parent.addChild( child2 );

		Session s = openSession();
		s.getTransaction().begin();
		s.persist( parent );
		s.getTransaction().commit();
		s.close();

		parentId = parent.getId();
		childId1 = child1.getId();
		childId2 = child2.getId();
	}

	@After
	public void cleanup() {
		Session s = openSession();
		s.getTransaction().begin();
		Parent parent = s.get( Parent.class, parentId );
		parent.getChildren().clear();
		s.delete( parent );
		s.getTransaction().commit();
		s.close();

		parentId = null;
	}

	@Test
	@TestForIssue( jiraKey = "HHH-5855")
	public void testSimpleAddDetached() {
		// Create 2 detached Child objects.
		Session s = openSession();
		s.getTransaction().begin();
		Child c1 = new Child( "Darwin" );
		s.persist( c1 );
		Child c2 = new Child( "Comet" );
		s.persist( c2 );
		s.getTransaction().commit();
		s.close();

		// Now Child c is detached.

		s = openSession();
		s.getTransaction().begin();
		Parent p = s.get( Parent.class, parentId );
		assertFalse( Hibernate.isInitialized( p.getChildren() ) );
		// add detached Child c
		p.addChild( c1 );
		// collection should still be uninitialized
		assertFalse( Hibernate.isInitialized( p.getChildren() ) );
		s.getTransaction().commit();
		s.close();

		// Add a detached Child and commit
		s = openSession();
		s.getTransaction().begin();
		p = s.get( Parent.class, parentId );
		assertFalse( Hibernate.isInitialized( p.getChildren() ) );
		assertEquals( 3, p.getChildren().size() );
		s.getTransaction().commit();
		s.close();

		// Add another detached Child, merge, and commit
		s = openSession();
		s.getTransaction().begin();
		p = s.get( Parent.class, parentId );
		assertFalse( Hibernate.isInitialized( p.getChildren() ) );
		p.addChild( c2 );
		assertFalse( Hibernate.isInitialized( p.getChildren() ) );
		s.merge( p );
		s.getTransaction().commit();
		s.close();

		s = openSession();
		s.getTransaction().begin();
		p = s.get( Parent.class, parentId );
		assertFalse( Hibernate.isInitialized( p.getChildren() ) );
		assertEquals( 4, p.getChildren().size() );
		s.getTransaction().commit();
		s.close();
	}

	@Test
	@TestForIssue( jiraKey = "HHH-5855")
	public void testSimpleAddTransient() {
		// Add a transient Child and commit.
		Session s = openSession();
		s.getTransaction().begin();
		Parent p = s.get( Parent.class, parentId );
		assertFalse( Hibernate.isInitialized( p.getChildren() ) );
		// add transient Child
		p.addChild( new Child( "Darwin" ) );
		// collection should still be uninitialized
		assertFalse( Hibernate.isInitialized( p.getChildren() ) );
		s.getTransaction().commit();
		s.close();

		s = openSession();
		s.getTransaction().begin();
		p = s.get( Parent.class, parentId );
		assertFalse( Hibernate.isInitialized( p.getChildren() ) );
		assertEquals( 3, p.getChildren().size() );
		s.getTransaction().commit();
		s.close();

		// Add another transient Child and commit again.
		s = openSession();
		s.getTransaction().begin();
		p = s.get( Parent.class, parentId );
		assertFalse( Hibernate.isInitialized( p.getChildren() ) );
		// add transient Child
		p.addChild( new Child( "Comet" ) );
		// collection should still be uninitialized
		assertFalse( Hibernate.isInitialized( p.getChildren() ) );
		s.merge( p );
		s.getTransaction().commit();
		s.close();

		s = openSession();
		s.getTransaction().begin();
		p = s.get( Parent.class, parentId );
		assertFalse( Hibernate.isInitialized( p.getChildren() ) );
		assertEquals( 4, p.getChildren().size() );
		s.getTransaction().commit();
		s.close();
	}

	@Test
	@TestForIssue( jiraKey = "HHH-5855")
	public void testSimpleAddManaged() {
		// Add 2 Child entities
		Session s = openSession();
		s.getTransaction().begin();
		Child c1 = new Child( "Darwin" );
		s.persist( c1 );
		Child c2 = new Child( "Comet" );
		s.persist( c2 );
		s.getTransaction().commit();
		s.close();

		// Add a managed Child and commit
		s = openSession();
		s.getTransaction().begin();
		Parent p = s.get( Parent.class, parentId );
		assertFalse( Hibernate.isInitialized( p.getChildren() ) );
		// get the first Child so it is managed; add to collection
		p.addChild( s.get( Child.class, c1.getId() ) );
		// collection should still be uninitialized
		assertFalse( Hibernate.isInitialized( p.getChildren() ) );
		s.getTransaction().commit();
		s.close();

		s = openSession();
		s.getTransaction().begin();
		p = s.get( Parent.class, parentId );
		assertFalse( Hibernate.isInitialized( p.getChildren() ) );
		assertEquals( 3, p.getChildren().size() );
		s.getTransaction().commit();
		s.close();

		// Add the other managed Child, merge and commit.
		s = openSession();
		s.getTransaction().begin();
		p = s.get( Parent.class, parentId );
		assertFalse( Hibernate.isInitialized( p.getChildren() ) );
		// get the second Child so it is managed; add to collection
		p.addChild( s.get( Child.class, c2.getId() ) );
		// collection should still be uninitialized
		assertFalse( Hibernate.isInitialized( p.getChildren() ) );
		s.merge( p );
		s.getTransaction().commit();
		s.close();

		s = openSession();
		s.getTransaction().begin();
		p = s.get( Parent.class, parentId );
		assertFalse( Hibernate.isInitialized( p.getChildren() ) );
		assertEquals( 4, p.getChildren().size() );
		s.getTransaction().commit();
		s.close();
	}

	@Test
	@TestForIssue( jiraKey = "HHH-5855")
	public void testSimpleRemoveDetached() {
		// Get the 2 Child entities and detach.
		Session s = openSession();
		s.getTransaction().begin();
		Child c1 = s.get( Child.class, childId1 );
		Child c2 = s.get( Child.class, childId2 );
		s.getTransaction().commit();
		s.close();

		// Remove a detached entity element and commit
		s = openSession();
		s.getTransaction().begin();
		Parent p = s.get( Parent.class, parentId );
		assertFalse( Hibernate.isInitialized( p.getChildren() ) );
		// remove a detached element and commit
		p.removeChild( c1 );
		assertFalse( Hibernate.isInitialized( p.getChildren() ) );
		s.merge( p );
		s.getTransaction().commit();
		s.close();

		// Remove a detached entity element, merge, and commit
		s = openSession();
		s.getTransaction().begin();
		p = s.get( Parent.class, parentId );
		assertFalse( Hibernate.isInitialized( p.getChildren() ) );
		assertEquals( 1, p.getChildren().size() );
		s.getTransaction().commit();
		s.close();

		// Remove a detached entity element, merge, and commit
		s = openSession();
		s.getTransaction().begin();
		p = s.get( Parent.class, parentId );
		assertFalse( Hibernate.isInitialized( p.getChildren() ) );
		// remove a detached element and commit
		p.removeChild( c2 );
		assertFalse( Hibernate.isInitialized( p.getChildren() ) );
		p = (Parent) s.merge( p );
		Hibernate.initialize( p );
		s.getTransaction().commit();
		s.close();

		// Remove a detached entity element, merge, and commit
		s = openSession();
		s.getTransaction().begin();
		p = s.get( Parent.class, parentId );
		assertFalse( Hibernate.isInitialized( p.getChildren() ) );
		assertEquals( 0, p.getChildren().size() );
		s.getTransaction().commit();
		s.close();
	}

	@Test
	@TestForIssue( jiraKey = "HHH-5855")
	public void testSimpleRemoveManaged() {
		// Remove a managed entity element and commit
		Session s = openSession();
		s.getTransaction().begin();
		Parent p = s.get( Parent.class, parentId );
		assertFalse( Hibernate.isInitialized( p.getChildren() ) );
		// get c1 so it is managed, then remove and commit
		p.removeChild( s.get( Child.class, childId1 ) );
		assertFalse( Hibernate.isInitialized( p.getChildren() ) );
		s.getTransaction().commit();
		s.close();

		s = openSession();
		s.getTransaction().begin();
		p = s.get( Parent.class, parentId );
		assertFalse( Hibernate.isInitialized( p.getChildren() ) );
		assertEquals( 1, p.getChildren().size() );
		s.getTransaction().commit();
		s.close();

		s = openSession();
		s.getTransaction().begin();
		p = s.get( Parent.class, parentId );
		assertFalse( Hibernate.isInitialized( p.getChildren() ) );
		// get c1 so it is managed, then remove, merge and commit
		p.removeChild( s.get( Child.class, childId2 ) );
		assertFalse( Hibernate.isInitialized( p.getChildren() ) );
		s.merge( p );
		s.getTransaction().commit();
		s.close();

		s = openSession();
		s.getTransaction().begin();
		p = s.get( Parent.class, parentId );
		assertFalse( Hibernate.isInitialized( p.getChildren() ) );
		assertEquals( 0, p.getChildren().size() );
		s.getTransaction().commit();
		s.close();
	}

	@Entity(name = "Parent")
	public static class Parent {

		@Id
		@GeneratedValue(strategy = GenerationType.AUTO)
		private Long id;

		@OneToMany(cascade = CascadeType.ALL, mappedBy = "parent", orphanRemoval = true)
		@LazyCollection( value = LazyCollectionOption.EXTRA)
		private Set<Child> children = new HashSet<Child>();

		public Parent() {
		}

		public Long getId() {
			return id;
		}

		public void setId(Long id) {
			this.id = id;
		}

		public Set<Child> getChildren() {
			return children;
		}

		public void addChild(Child child) {
			children.add(child);
			child.setParent(this);
		}

		public void removeChild(Child child) {
			children.remove(child);
			child.setParent(null);
		}
	}

	@Entity(name = "Child")
	public static class Child {

		@Id
		@GeneratedValue(strategy = GenerationType.AUTO)
		private Long id;

		@Column(nullable = false)
		private String name;

		@ManyToOne
		private Parent parent;

		public Child() {
		}

		public Child(String name) {
			this.name = name;
		}

		public Long getId() {
			return id;
		}

		public String getName() {
			return name;
		}

		public void setName(String name) {
			this.name = name;
		}

		public Parent getParent() {
			return parent;
		}

		public void setParent(Parent parent) {
			this.parent = parent;
		}

		@Override
		public String toString() {
			return "Child{" +
					"id=" + id +
					", name='" + name + '\'' +
					'}';
		}

		@Override
		public boolean equals(Object o) {
			if ( this == o ) {
				return true;
			}
			if ( o == null || getClass() != o.getClass() ) {
				return false;
			}

			Child child = (Child) o;

			return name.equals( child.name );

		}

		@Override
		public int hashCode() {
			return name.hashCode();
		}
	}

}
