/*
 * 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.map;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;

import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.MapKeyColumn;
import javax.persistence.OneToMany;
import javax.persistence.Table;

import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.collection.internal.PersistentMap;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.junit.Test;

/**
 * Test various situations using a {@link PersistentMap}.
 *
 * @author Steve Ebersole
 * @author Brett Meyer
 * @author Gail Badner
 */
public class PersistentMapTest extends BaseCoreFunctionalTestCase {
	@Override
	public String[] getMappings() {
		return new String[] { "collection/map/Mappings.hbm.xml" };
	}
	
	@Override
	protected Class<?>[] getAnnotatedClasses() {
		return new Class<?>[] {
				User.class,
				UserData.class,
				MultilingualString.class,
				Address.class,
				Detail.class
		};
	}

	@Test
	@SuppressWarnings({ "unchecked" })
	public void testWriteMethodDirtying() {
		Parent parent = new Parent( "p1" );
		Child child = new Child( "c1" );
		parent.getChildren().put( child.getName(), child );
		child.setParent( parent );
		Child otherChild = new Child( "c2" );

		Session session = openSession();
		session.beginTransaction();
		session.save( parent );
		session.flush();
		// at this point, the map on parent has now been replaced with a PersistentMap...
		PersistentMap children = ( PersistentMap ) parent.getChildren();

		Object old = children.put( child.getName(), child );
		assertTrue( old == child );
		assertFalse( children.isDirty() );

		old = children.remove( otherChild.getName() );
		assertNull( old );
		assertFalse( children.isDirty() );

		HashMap otherMap = new HashMap();
		otherMap.put( child.getName(), child );
		children.putAll( otherMap );
		assertFalse( children.isDirty() );

		otherMap = new HashMap();
		otherMap.put( otherChild.getName(), otherChild );
		children.putAll( otherMap );
		assertTrue( children.isDirty() );

		children.clearDirty();
		session.delete( child );
		children.clear();
		assertTrue( children.isDirty() );
		session.flush();

		children.clear();
		assertFalse( children.isDirty() );

		session.delete( parent );
		session.getTransaction().commit();
		session.close();
	}

	@Test
	public void testPutAgainstUninitializedMap() {
		// prepare map owner...
		Session session = openSession();
		session.beginTransaction();
		Parent parent = new Parent( "p1" );
		session.save( parent );
		session.getTransaction().commit();
		session.close();

		// Now, reload the parent and test adding children
		session = openSession();
		session.beginTransaction();
		parent = ( Parent ) session.get( Parent.class, parent.getName() );
		parent.addChild( "c1" );
		parent.addChild( "c2" );
		session.getTransaction().commit();
		session.close();

		assertEquals( 2, parent.getChildren().size() );

		session = openSession();
		session.beginTransaction();
		session.delete( parent );
		session.getTransaction().commit();
		session.close();
	}

	@Test
    public void testRemoveAgainstUninitializedMap() {
        Parent parent = new Parent( "p1" );
        Child child = new Child( "c1" );
        parent.addChild( child );

        Session session = openSession();
        session.beginTransaction();
        session.save( parent );
        session.getTransaction().commit();
        session.close();

        // Now reload the parent and test removing the child
        session = openSession();
        session.beginTransaction();
        parent = ( Parent ) session.get( Parent.class, parent.getName() );
        Child child2 = ( Child ) parent.getChildren().remove( child.getName() );
		child2.setParent( null );
		assertNotNull( child2 );
		assertTrue( parent.getChildren().isEmpty() );
        session.getTransaction().commit();
        session.close();

		// Load the parent once again and make sure child is still gone
		//		then cleanup
        session = openSession();
        session.beginTransaction();
		parent = ( Parent ) session.get( Parent.class, parent.getName() );
		assertTrue( parent.getChildren().isEmpty() );
		session.delete( child2 );
		session.delete( parent );
        session.getTransaction().commit();
        session.close();
    }

	@Test
	@TestForIssue(jiraKey = "HHH-5732")
	public void testClearMap() {
		Session s = openSession();
		s.beginTransaction();
		
		User user = new User();
		UserData userData = new UserData();
		userData.user = user;
		user.userDatas.put( "foo", userData );
		s.persist( user );
		
		s.getTransaction().commit();
		s.clear();
		
		s.beginTransaction();
		
		user = s.get( User.class, 1 );
	    user.userDatas.clear();
	    s.update( user );
		Query q = s.createQuery( "DELETE FROM " + UserData.class.getName() + " d WHERE d.user = :user" );
		q.setParameter( "user", user );
		q.executeUpdate();
		
		s.getTransaction().commit();

		s.getTransaction().begin();

		assertEquals( s.get( User.class, user.id ).userDatas.size(), 0 );
		assertEquals( s.createQuery( "FROM " + UserData.class.getName() ).list().size(), 0 );
		s.createQuery( "delete " + User.class.getName() ).executeUpdate();

		s.getTransaction().commit();
		s.close();
	}

	@Test
	@TestForIssue( jiraKey = "HHH-5393")
	public void testMapKeyColumnInEmbeddableElement() {
		Session s = openSession();
		s.getTransaction().begin();
		MultilingualString m = new MultilingualString();
		LocalizedString localizedString = new LocalizedString();
		localizedString.setLanguage( "English" );
		localizedString.setText( "name" );
		m.getMap().put( localizedString.getLanguage(), localizedString );
		localizedString = new LocalizedString();
		localizedString.setLanguage( "English Pig Latin" );
		localizedString.setText( "amenay" );
		m.getMap().put( localizedString.getLanguage(), localizedString );
		s.persist( m );
		s.getTransaction().commit();
		s.close();

		s = openSession();
		s.beginTransaction();
		m = s.get( MultilingualString.class, m.getId());
		assertEquals( 2, m.getMap().size() );
		localizedString = m.getMap().get( "English" );
		assertEquals( "English", localizedString.getLanguage() );
		assertEquals( "name", localizedString.getText() );
		localizedString = m.getMap().get( "English Pig Latin" );
		assertEquals( "English Pig Latin", localizedString.getLanguage() );
		assertEquals( "amenay", localizedString.getText() );
		s.delete( m );
		s.getTransaction().commit();
		s.close();
	}

	@Test
	@TestForIssue( jiraKey = "HHH-11038" )
	public void testMapKeyColumnNonInsertableNonUpdatableBidirOneToMany() {
		Session s = openSession();
		s.getTransaction().begin();
		User user = new User();
		Address address = new Address();
		address.addressType = "email";
		address.addressText = "jane@doe.com";
		user.addresses.put( address.addressType, address );
		address.user = user;
		s.persist( user );
		s.getTransaction().commit();
		s.close();

		s = openSession();
		s.getTransaction().begin();
		user = s.get( User.class, user.id );
		user.addresses.clear();
		s.getTransaction().commit();
		s.close();

		s = openSession();
		s.getTransaction().begin();
		user = s.get( User.class, user.id );
		s.delete( user );
		s.createQuery( "delete from " + User.class.getName() ).executeUpdate();
		s.getTransaction().commit();
		s.close();
	}

	@Test
	@TestForIssue( jiraKey = "HHH-11038" )
	public void testMapKeyColumnNonInsertableNonUpdatableUnidirOneToMany() {
		Session s = openSession();
		s.getTransaction().begin();
		User user = new User();
		Detail detail = new Detail();
		detail.description = "desc";
		detail.detailType = "trivial";
		user.details.put( detail.detailType, detail );
		s.persist( user );
		s.getTransaction().commit();
		s.close();

		s = openSession();
		s.getTransaction().begin();
		user = s.get( User.class, user.id );
		user.details.clear();
		s.getTransaction().commit();
		s.close();

		s = openSession();
		s.getTransaction().begin();
		user = s.get( User.class, user.id );
		s.delete( user );
		s.createQuery( "delete from " + User.class.getName() ).executeUpdate();
		s.getTransaction().commit();
		s.close();
	}

	@Entity
	@Table(name = "MyUser")
	private static class User implements Serializable {
		@Id @GeneratedValue
		private Integer id;
		
		@OneToMany(fetch = FetchType.LAZY, mappedBy = "user", cascade = CascadeType.ALL)
		@MapKeyColumn(name = "name", nullable = true)
		private Map<String, UserData> userDatas = new HashMap<String, UserData>();

		@OneToMany(fetch = FetchType.LAZY, mappedBy = "user", cascade = CascadeType.ALL)
		@MapKeyColumn(name = "addressType", insertable = false, updatable = false)
		private Map<String, Address> addresses = new HashMap<String, Address>();

		@OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
		@MapKeyColumn(name = "detailType", insertable = false, updatable = false)
		@JoinColumn
		private Map<String, Detail> details = new HashMap<String, Detail>();
	}
	
	@Entity
	@Table(name = "UserData")
	private static class UserData {
		@Id @GeneratedValue
		private Integer id;
		
		@ManyToOne
		@JoinColumn(name = "userId")
		private User user;
	}

	@Entity
	@Table(name = "Address")
	private static class Address {
		@Id @GeneratedValue
		private Integer id;

		@ManyToOne
		@JoinColumn(name = "userId")
		private User user;

		@Column(nullable = false)
		private String addressType;

		@Column(nullable = false)
		private String addressText;
	}

	@Entity
	@Table(name="Detail")
	private static class Detail {
		@Id @GeneratedValue
		private Integer id;

		@Column(nullable = false)
		private String detailType;

		private String description;
	}
}
