/*******************************************************************************
 * Copyright (c) 2004 Actuate Corporation.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *  Actuate Corporation  - initial API and implementation
 *******************************************************************************/

package org.eclipse.birt.report.model.core;

import java.io.IOException;
import java.util.List;

import org.eclipse.birt.report.model.api.DataSetHandle;
import org.eclipse.birt.report.model.api.DesignElementHandle;
import org.eclipse.birt.report.model.api.DesignEngine;
import org.eclipse.birt.report.model.api.DesignFileException;
import org.eclipse.birt.report.model.api.ErrorDetail;
import org.eclipse.birt.report.model.api.GraphicMasterPageHandle;
import org.eclipse.birt.report.model.api.GridHandle;
import org.eclipse.birt.report.model.api.ImageHandle;
import org.eclipse.birt.report.model.api.LabelHandle;
import org.eclipse.birt.report.model.api.ReportDesignHandle;
import org.eclipse.birt.report.model.api.SessionHandle;
import org.eclipse.birt.report.model.api.SharedStyleHandle;
import org.eclipse.birt.report.model.api.StyleHandle;
import org.eclipse.birt.report.model.api.TableHandle;
import org.eclipse.birt.report.model.api.activity.NotificationEvent;
import org.eclipse.birt.report.model.api.command.ContentEvent;
import org.eclipse.birt.report.model.api.command.ContentException;
import org.eclipse.birt.report.model.api.command.NameException;
import org.eclipse.birt.report.model.api.core.Listener;
import org.eclipse.birt.report.model.api.elements.ReportDesignConstants;
import org.eclipse.birt.report.model.api.elements.SemanticError;
import org.eclipse.birt.report.model.elements.DataSet;
import org.eclipse.birt.report.model.elements.DataSource;
import org.eclipse.birt.report.model.elements.GraphicMasterPage;
import org.eclipse.birt.report.model.elements.GridItem;
import org.eclipse.birt.report.model.elements.ImageItem;
import org.eclipse.birt.report.model.elements.Label;
import org.eclipse.birt.report.model.elements.Library;
import org.eclipse.birt.report.model.elements.ReportDesign;
import org.eclipse.birt.report.model.elements.SimpleDataSet;
import org.eclipse.birt.report.model.elements.Style;
import org.eclipse.birt.report.model.elements.TableItem;
import org.eclipse.birt.report.model.metadata.ElementRefValue;
import org.eclipse.birt.report.model.metadata.MetaDataDictionary;
import org.eclipse.birt.report.model.util.BaseTestCase;

import com.ibm.icu.util.ULocale;

/**
 * Test the clone feature of
 * <code>{@link org.eclipse.birt.report.model.core.DesignElement}</code> Clone
 * the original design element, and use the <code>rename</code> method, which
 * is provided by the
 * <code>{@link org.eclipse.birt.report.model.api.ReportDesignHandle#rename(DesignElementHandle)}</code>
 * to check whether is there any duplicate name existed. Then add the cloned
 * element into the design tree again. Use the golden file to check the cloned
 * result.
 * 
 */

public class DesignElementCloneTest extends BaseTestCase
{

	/*
	 * (non-Javadoc)
	 * 
	 * @see junit.framework.TestCase#setUp()
	 */
	protected void setUp( ) throws Exception
	{
		super.setUp( );

	}

	/**
	 * Tests the clone method of
	 * <code>{@link org.eclipse.birt.report.model.core.DesignElement}</code>.
	 * 
	 * @throws Exception
	 *             If the test fails.
	 */

	public void testCloneLabel( ) throws Exception
	{
		openDesign( "DesignElementTest_1.xml" ); //$NON-NLS-1$
		Label label = (Label) design.findElement( "label1" ); //$NON-NLS-1$
		assertNotNull( label );

		DesignElement element = (DesignElement) label.clone( );
		assertNotNull( element );
	}

	/**
	 * Tests cloning the grid element and renaming it. Add the new grid into the
	 * design tree.
	 * 
	 * @throws Exception
	 *             if any exception
	 */

	public void testCloneGrid( ) throws Exception
	{
		openDesign( "DesignElementTest_3.xml" ); //$NON-NLS-1$

		GridHandle gridHandle = (GridHandle) designHandle
				.findElement( "hexingjie" ); //$NON-NLS-1$
		assertNotNull( gridHandle );
		assertEquals( "hexingjie", gridHandle.getName( ) ); //$NON-NLS-1$

		GridItem grid = (GridItem) gridHandle.copy( );
		assertNotNull( grid );
		assertEquals( "hexingjie", grid.getName( ) ); //$NON-NLS-1$
		ContainerSlot rowSlot = grid.getSlot( GridItem.ROW_SLOT );
		assertEquals( grid, rowSlot.getContent( 0 ).getContainer( ) );

		designHandle.rename( grid.getHandle( design ) );
		designHandle.getBody( ).paste( grid.getHandle( design ) );

		saveAs( "DesignElementCloneTest_gridClone.xml" ); //$NON-NLS-1$

		assertTrue( compareTextFile(
				"DeisgnElementCloneTest_gridClone_golden.xml", "DesignElementCloneTest_gridClone.xml" ) ); //$NON-NLS-1$ //$NON-NLS-2$

	}

	/**
	 * Tests the style reference in new element.
	 * 
	 * @throws Exception
	 *             if any exception.
	 */

	public void testCloneImage( ) throws Exception
	{

		openDesign( "DesignElementCloneTest_ImageClone.xml" ); //$NON-NLS-1$

		ImageHandle imageHandle = (ImageHandle) designHandle
				.findElement( "Image1" ); //$NON-NLS-1$
		assertNotNull( imageHandle );
		assertEquals( "Image1", imageHandle.getName( ) ); //$NON-NLS-1$

		ImageItem image = (ImageItem) imageHandle.copy( );
		assertNotNull( image );

		designHandle.rename( image.getHandle( design ) );
		designHandle.findMasterPage( "My Page" ).getSlot( //$NON-NLS-1$
				GraphicMasterPage.CONTENT_SLOT )
				.add( image.getHandle( design ) );

		imageHandle = (ImageHandle) designHandle.findElement( "Image3" ); //$NON-NLS-1$
		assertNotNull( imageHandle );
		assertEquals( "Image3", imageHandle.getName( ) ); //$NON-NLS-1$

		image = (ImageItem) imageHandle.copy( );
		assertNotNull( image );

		designHandle.rename( image.getHandle( design ) );
		designHandle.findMasterPage( "My Page" ).getSlot( //$NON-NLS-1$
				GraphicMasterPage.CONTENT_SLOT ).paste(
				image.getHandle( design ) );

		saveAs( "DesignElementCloneTest_ImageClone_out.xml" ); //$NON-NLS-1$
		assertTrue( compareTextFile(
				"DesignElementCloneTest_ImageClone_golden.xml", "DesignElementCloneTest_ImageClone_out.xml" ) ); //$NON-NLS-1$//$NON-NLS-2$

	}

	/**
	 * Tests the extend relationship in element.
	 * <ul>
	 * <li>Extends is not cloned
	 * <li>Derived is not cloned
	 * </ul>
	 * 
	 * @throws Exception
	 *             if any exception.
	 */

	public void testCloneExtendedLabel( ) throws Exception
	{

		openDesign( "DesignElementCloneTest.xml" ); //$NON-NLS-1$

		// The derived list is not cloned

		LabelHandle labelHandle = (LabelHandle) designHandle
				.findElement( "Base Label" ); //$NON-NLS-1$
		assertNotNull( labelHandle );
		assertEquals( 1, labelHandle.getElement( ).getDerived( ).size( ) );

		Label label = (Label) labelHandle.copy( );
		assertNotNull( label );
		assertEquals( 0, label.getDerived( ).size( ) );
		design.makeUniqueName( label );
		designHandle.getComponents( ).paste( label.getHandle( design ) );

		// Add again.

		labelHandle = (LabelHandle) designHandle.findElement( label.getName( ) );
		label = (Label) labelHandle.copy( );
		design.makeUniqueName( label );
		designHandle.getComponents( ).paste( label.getHandle( design ) );

		// The extends is not cloned.

		labelHandle = (LabelHandle) designHandle.findElement( "Body Label" ); //$NON-NLS-1$
		assertEquals( "Base Label", labelHandle.getExtends( ).getName( ) ); //$NON-NLS-1$
		label = (Label) labelHandle.copy( );
		assertNull( label.getExtendsElement( ) );
		design.makeUniqueName( label );
		designHandle.getBody( ).paste( label.getHandle( design ) );

		saveAs( "DesignElementCloneTest_ExtendedClone_out.xml" ); //$NON-NLS-1$
		assertTrue( compareTextFile(
				"DesignElementCloneTest_ExtendedClone_golden.xml", //$NON-NLS-1$
				"DesignElementCloneTest_ExtendedClone_out.xml" ) ); //$NON-NLS-1$
	}

	/**
	 * Tests the listeners in new element.
	 * <ul>
	 * <li>Listener is not cloned.
	 * <li>The container is not cloned
	 * </ul>
	 * 
	 * @throws Exception
	 *             if any exception
	 */

	public void testTheReferencesAfterClone( ) throws Exception
	{

		openDesign( "DesignElementCloneTest.xml" ); //$NON-NLS-1$

		GraphicMasterPageHandle pageHandle = (GraphicMasterPageHandle) designHandle
				.findMasterPage( "My Page" ); //$NON-NLS-1$
		assertNotNull( pageHandle );

		pageHandle.addListener( new MyListener( "listener1" ) ); //$NON-NLS-1$
		pageHandle.addListener( new MyListener( "listener2" ) ); //$NON-NLS-1$

		assertEquals( 2, pageHandle.getElement( ).listeners.size( ) );
		assertEquals( design, pageHandle.getContainer( ) );

		GraphicMasterPage masterPage = (GraphicMasterPage) pageHandle.copy( );

		// the listeners should be set to null after clone.

		assertNull( masterPage.listeners );

		// the container should not be kept after clone

		assertNull( masterPage.getContainer( ) );

		DataSetHandle dataset = designHandle.findDataSet( "firstDataSet" ); //$NON-NLS-1$
		SimpleDataSet newDataSet = (SimpleDataSet) dataset.copy( );

		dataset = (DataSetHandle) newDataSet.getHandle( design );

		SessionHandle sessionHandle = DesignEngine.newSession( ULocale.ENGLISH );
		ReportDesignHandle newDesignHandle = sessionHandle.createDesign( );

		newDesignHandle.getDataSets( ).paste( dataset );

		designHandle.checkReport( );
		List list = designHandle.getErrorList( );
		assertEquals( 3, list.size( ) );
		assertEquals( SemanticError.DESIGN_EXCEPTION_INVALID_ELEMENT_REF,
				( (ErrorDetail) list.get( 0 ) ).getErrorCode( ) );
		assertEquals( SemanticError.DESIGN_EXCEPTION_INVALID_STRUCTURE_REF,
				( (ErrorDetail) list.get( 1 ) ).getErrorCode( ) );
		assertEquals( SemanticError.DESIGN_EXCEPTION_INVALID_STRUCTURE_REF,
				( (ErrorDetail) list.get( 2 ) ).getErrorCode( ) );
		assertEquals( 1, designHandle.getWarningList( ).size( ) );

	}

	/**
	 * Tests the style in new element.
	 * <ul>
	 * <li>Style is not cloned
	 * <li>Cached element definition is not cloned
	 * </ul>
	 * 
	 * @throws Exception
	 *             if any exception.
	 */

	public void testStyleAfterClone( ) throws Exception
	{

		openDesign( "DesignElementCloneTest.xml" ); //$NON-NLS-1$

		// Copy style and check clients

		SharedStyleHandle styleHandle = designHandle.findStyle( "My Style" ); //$NON-NLS-1$
		assertNotNull( styleHandle );
		assertNotNull( styleHandle.getElement( ).cachedDefn );

		List client = ( (Style) styleHandle.getElement( ) ).getClientList( );
		assertEquals( 3, client.size( ) );

		Style style = (Style) styleHandle.copy( );
		assertNotNull( style );
//		assertNull( style.cachedDefn );

		assertEquals( 0, style.getClientList( ).size( ) );

		// Copy styled element and check style

		LabelHandle labelHandle = (LabelHandle) designHandle
				.findElement( "Base Label" ); //$NON-NLS-1$
		assertNotNull( labelHandle );
		assertNotNull( labelHandle.getStyle( ) );

		Label page = (Label) labelHandle.copy( );
		designHandle.rename( page.getHandle( design ) );
		labelHandle = (LabelHandle) page.getHandle( design );

		designHandle.getBody( ).paste( labelHandle );

		// resolved style reference

		assertEquals( styleHandle, labelHandle.getStyle( ) );
		assertEquals( "My Style", page.getStyleName( ) ); //$NON-NLS-1$
	}

	/**
	 * Tests cloning table and grid. The new element has new rows and cells.
	 * 
	 * @throws Exception
	 *             if any exception.
	 */

	public void testCloneTableAndGrid( ) throws Exception
	{
		openDesign( "DesignElementCloneTest_Container.xml" ); //$NON-NLS-1$

		// Clone a table

		TableHandle tableHandle = (TableHandle) designHandle
				.findElement( "my table1" ); //$NON-NLS-1$
		assertEquals( 1, tableHandle.getDetail( ).getCount( ) );
		TableItem newTable = (TableItem) tableHandle.copy( );
		newTable.setName( "new table 1" ); //$NON-NLS-1$
		designHandle.getSlot( ReportDesign.BODY_SLOT ).paste(
				newTable.getHandle( design ) );

		// Drop the old table and check the new one.

		tableHandle.dropAndClear( );

		TableHandle newTableHandle = (TableHandle) newTable.getHandle( design );
		assertEquals( newTableHandle, newTableHandle.getDetail( ).get( 0 )
				.getContainer( ) );
		assertEquals( 1, newTableHandle.getDetail( ).getCount( ) );

		// Clone a grid

		GridHandle grid = (GridHandle) designHandle.findElement( "my grid1" ); //$NON-NLS-1$		
		GridItem newGrid = (GridItem) grid.copy( );
		newGrid.setName( "new grid 1" ); //$NON-NLS-1$

		designHandle.rename( newGrid.handle( design ) );

		designHandle.getSlot( ReportDesign.BODY_SLOT ).paste(
				newGrid.getHandle( design ) );

		// Drop the old grid and check the new one

		grid.dropAndClear( );

		GridHandle newGridHandle = (GridHandle) newGrid.getHandle( design );
		assertEquals( 2, newGridHandle.getRows( ).getCount( ) );
		
		// make sure there is no NPE to copy empty tables.
		
		tableHandle = designHandle.getElementFactory( ).newTableItem( "table3" ); //$NON-NLS-1$
		tableHandle.copy( );

	}

	/**
	 * Test cases:
	 * 
	 * Test element id for the clone case.
	 * 
	 * @throws DesignFileException
	 * @throws ContentException
	 * @throws NameException
	 * @throws IOException
	 */

	public void testElementId( ) throws DesignFileException, ContentException,
			NameException, IOException
	{
		MetaDataDictionary.getInstance( ).enableElementID( );
		openDesign( "DesignElementCloneTest.xml" ); //$NON-NLS-1$

		StyleHandle myStyle = (StyleHandle) designHandle.getStyles( ).get( 0 );

		Style newStyle = (Style) myStyle.copy( );
		assertEquals( myStyle.getID( ), newStyle.getID( ) );
		assertEquals( 2, newStyle.getID( ) );

		designHandle.rename( newStyle.getHandle( design ) );
		designHandle.getStyles( ).paste( newStyle );

		assertFalse( newStyle.getID( ) == myStyle.getID( ) );

	}

	/**
	 * Tests the clone method of the module elements, such as report design and
	 * library.
	 * 
	 * @throws Exception
	 */

	public void testDesignWithIncludeLibrary( ) throws Exception
	{
		openDesign( "DesignElementCloneTest_Module.xml" ); //$NON-NLS-1$
		assertNotNull( designHandle );
		assertNotNull( design );

		ReportDesign clonedDesign = (ReportDesign) design.clone( );
		assertNotNull( clonedDesign );

		assertNotSame( design, clonedDesign );

		// tests some simple properties

		assertNotSame( design.getActivityStack( ), clonedDesign
				.getActivityStack( ) );
		assertNotNull( clonedDesign.getActivityStack( ) );
		assertEquals( 2, clonedDesign.getAllExceptions( ).size( ) );
		assertNull( clonedDesign.getFatalException( ) );

		// test "theme" property

		ElementRefValue theme = (ElementRefValue) clonedDesign.getProperty(
				clonedDesign, ReportDesign.THEME_PROP );
		assertNotNull( theme );
		assertEquals( "theme1", theme.getName( ) ); //$NON-NLS-1$
		assertNull( theme.getElement( ) );

		// tests id-map

		assertEquals( clonedDesign, clonedDesign.getElementByID( 1 ) );
		testID( design, clonedDesign, 2,
				ReportDesignConstants.GRAPHIC_MASTER_PAGE_ELEMENT );
		testID( design, clonedDesign, 3, ReportDesignConstants.FREE_FORM_ITEM );
		testID( design, clonedDesign, 4, ReportDesignConstants.LABEL_ITEM );
		assertEquals( 5, design.getNextID( ), clonedDesign.getNextID( ) );

		// tests namespace

		testNameSpace( design, clonedDesign, ReportDesign.PAGE_NAME_SPACE,
				"page", ReportDesignConstants.GRAPHIC_MASTER_PAGE_ELEMENT ); //$NON-NLS-1$
		testNameSpace( design, clonedDesign, ReportDesign.ELEMENT_NAME_SPACE,
				"freeForm", ReportDesignConstants.FREE_FORM_ITEM ); //$NON-NLS-1$
		testNameSpace( design, clonedDesign, ReportDesign.ELEMENT_NAME_SPACE,
				"label", ReportDesignConstants.LABEL_ITEM ); //$NON-NLS-1$

		// tests included libraries

		List libraries = clonedDesign.getAllLibraries( );
		assertEquals( 1, design.getAllLibraries( ).size( ), libraries.size( ) );
		assertNotSame( design.getAllLibraries( ).get( 0 ), clonedDesign
				.getAllLibraries( ).get( 0 ) );
		assertEquals( clonedDesign, ( (Library) clonedDesign.getAllLibraries( )
				.get( 0 ) ).getHost( ) );

	}

	/**
	 * Tests the style property get be retrieved properly from factorty property
	 * handle after the design is copied.
	 * 
	 * @throws DesignFileException
	 */
	public void testElementResolveAfterClone( ) throws DesignFileException
	{

		openDesign( "TestElementResolveAfterClone.xml" ); //$NON-NLS-1$

		TableHandle table = (TableHandle) designHandle.findElement( "myTable" ); //$NON-NLS-1$

		assertEquals( "red", table.getProperty( Style.COLOR_PROP ) ); //$NON-NLS-1$
		assertEquals(
				"red", table.getFactoryPropertyHandle( Style.COLOR_PROP ).getValue( ) ); //$NON-NLS-1$

		ReportDesign design = (ReportDesign) designHandle.copy( );
		ReportDesignHandle myDesignHandle = (ReportDesignHandle) design
				.getHandle( design );
		table = (TableHandle) designHandle.findElement( "myTable" ); //$NON-NLS-1$
		assertEquals( "red", table.getProperty( Style.COLOR_PROP ) ); //$NON-NLS-1$
		assertEquals(
				"red", table.getFactoryPropertyHandle( Style.COLOR_PROP ).getValue( ) ); //$NON-NLS-1$
	}

	/**
	 * Tests the id-map of the cloned module.
	 * 
	 * @param module
	 *            the original module
	 * @param clonedModule
	 *            the cloned module
	 * @param id
	 *            the element id to test
	 * @param elementName
	 *            the element type name
	 */

	private void testID( Module module, Module clonedModule, long id,
			String elementName )
	{
		assert module != null;
		assert clonedModule != null;

		assertNotNull( clonedModule.getElementByID( id ) );
		assertNotSame( module.getElementByID( id ), clonedModule
				.getElementByID( id ) );
		assertEquals( elementName,
				module.getElementByID( id ).getElementName( ), clonedModule
						.getElementByID( id ).getElementName( ) );
	}

	/**
	 * Tests the namespace of the cloned module.
	 * 
	 * @param module
	 *            the original module
	 * @param clonedModule
	 *            the cloned module
	 * @param id
	 *            the namespace id
	 * @param name
	 *            the element name to check
	 * @param elementName
	 *            the element type name of the element
	 */

	private void testNameSpace( Module module, Module clonedModule, int id,
			String name, String elementName )
	{
		assert module != null;
		assert clonedModule != null;

		NameSpace ns = module.getNameSpace( id );
		NameSpace clonedNs = clonedModule.getNameSpace( id );
		assertNotSame( ns, clonedNs );

		assertNotNull( ns.getElement( name ) );
		assertNotNull( clonedNs.getElement( name ) );
		assertNotSame( ns.getElement( name ), clonedNs.getElement( name ) );
		assertEquals( elementName, ns.getElement( name ).getElementName( ),
				clonedNs.getElement( name ).getElementName( ) );
	}

	class MyListener implements Listener
	{

		String name = null;

		ContentEvent event = null;

		MyListener( String name )
		{
			this.name = name;
		}

		public void elementChanged( DesignElementHandle focus,
				NotificationEvent ev )
		{
			return;

		}
	}

	/**
	 * test if the display name is exist or not after cloning
	 * 
	 * @throws Exception
	 */
	public void testDisplayNameAfterClone( ) throws Exception
	{
		String name = "DesignElementCloneTest_DisplayName.xml"; //$NON-NLS-1$
		openDesign( name );
		DataSet ds = (DataSet) design.findDataSet( "Data Set" ); //$NON-NLS-1$
		DataSet copyDs = (DataSet) ds.clone( );
		assertNull( copyDs.getProperty( design.getRoot( ),
				DesignElement.DISPLAY_NAME_PROP ) );
		assertNull( copyDs.getProperty( design.getRoot( ),
				DesignElement.DISPLAY_NAME_ID_PROP ) );

		DataSource source = (DataSource) design.findDataSource( "Data Source" ); //$NON-NLS-1$
		DataSource copySource = (DataSource) source.clone( );
		assertNull( copySource.getProperty( design.getRoot( ),
				DesignElement.DISPLAY_NAME_PROP ) );
		assertNull( copySource.getProperty( design.getRoot( ),
				DesignElement.DISPLAY_NAME_ID_PROP ) );

	}

}