/*******************************************************************************
 * 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 org.eclipse.birt.report.model.api.GroupHandle;
import org.eclipse.birt.report.model.api.ListingHandle;
import org.eclipse.birt.report.model.api.core.INameManager;
import org.eclipse.birt.report.model.api.extension.IMessages;
import org.eclipse.birt.report.model.api.extension.IReportItemFactory;
import org.eclipse.birt.report.model.api.metadata.MetaDataConstants;
import org.eclipse.birt.report.model.api.util.StringUtil;
import org.eclipse.birt.report.model.api.validators.GroupNameValidator;
import org.eclipse.birt.report.model.elements.ExtendedItem;
import org.eclipse.birt.report.model.elements.GroupElement;
import org.eclipse.birt.report.model.elements.ReportDesign;
import org.eclipse.birt.report.model.i18n.MessageConstants;
import org.eclipse.birt.report.model.i18n.ModelMessages;
import org.eclipse.birt.report.model.i18n.ThreadResources;
import org.eclipse.birt.report.model.metadata.ElementDefn;
import org.eclipse.birt.report.model.metadata.ExtensionElementDefn;
import org.eclipse.birt.report.model.metadata.PeerExtensionElementDefn;

/**
 * This class is a utility class to do some managements about the element names
 * so that a newly created element can have a unique name.
 */

public class NameManager implements INameManager
{

	/**
	 * The prefix for the default group name.
	 */

	private static final String GROUP_NAME_PREFIX = "group_"; //$NON-NLS-1$

	/**
	 * The array of name spaces. The meanings of each name space is defined in
	 * {@link org.eclipse.birt.report.model.api.metadata.MetaDataConstants MetaDataConstants}.
	 */

	private NameSpace nameSpaces[] = new NameSpace[Module.NAME_SPACE_COUNT];

	/**
	 * The module to which some name managements are done.
	 */

	private Module root = null;

	/**
	 * Constructs the name manager with the module to which all name managements
	 * are done.
	 * 
	 * @param module
	 *            the module to which all name managements are done
	 * 
	 */

	public NameManager( Module module )
	{
		// initialize the name space

		initNameSpaces( );

		assert module != null;
		root = module;
	}

	/**
	 * Initializes the namespace cached in the name manager.
	 * 
	 */

	private void initNameSpaces( )
	{
		for ( int i = 0; i < Module.NAME_SPACE_COUNT; i++ )
		{
			nameSpaces[i] = new NameSpace( );
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.birt.report.model.api.core.INameManager#flush()
	 */

	public void clear( )
	{
		initNameSpaces( );
	}

	/**
	 * 
	 * Returns unique name of element.
	 * <tr>
	 * <td>if element is null or elements have a blank name</td>
	 * <td>return null</td>
	 * </tr>
	 * <tr>
	 * <td>if element is group element</td>
	 * <td>return name of group element</td>
	 * </tr>
	 * <tr>
	 * <td>if element is already has a unique name</td>
	 * <td>return it</td>
	 * </tr>
	 * <tr>
	 * <td>if element has no name in library</td>
	 * <td>return newly created unique name</td>
	 * </tr>
	 * 
	 * @param element
	 *            the design element.
	 * @return unique name of element.
	 * 
	 */

	public String getUniqueName( DesignElement element )
	{
		if ( element == null )
			return null;

		if ( element instanceof GroupElement )
		{
			String groupName = getUniqueGroupName( (GroupElement) element );
			return groupName;
		}

		ElementDefn eDefn = (ElementDefn) element.getDefn( );
		String name = StringUtil.trimString( element.getName( ) );

		// Some elements can have a blank name.

		if ( eDefn.getNameOption( ) == MetaDataConstants.NO_NAME )
			return null;

		if ( eDefn.getNameOption( ) == MetaDataConstants.OPTIONAL_NAME
				&& name == null && root instanceof ReportDesign )
			return null;

		// If the element already has a unique name, return it.

		NameSpace nameSpace = getNameSpace( eDefn.getNameSpaceID( ) );
		NameSpace moduleNameSpace = root.getNameSpace( eDefn.getNameSpaceID( ) );
		if ( name != null && !nameSpace.contains( name )
				&& !moduleNameSpace.contains( name ) )
			return name;

		// If the element has no name, create it as "New<new name>" where
		// "<new name>" is the new element display name for the element. Both
		// "New" and the new element display name are localized to the user's
		// locale.

		if ( name == null )
		{
			// When creating a new report element which requires a name, the
			// default name will be "New" followed by the element name, such as
			// "New Label"; also, if "NewLabel" already exists, then a number
			// will be appended, such as "NewLabel1", etc.

			if ( element instanceof ExtendedItem )
			{
				ExtensionElementDefn extDefn = ( (ExtendedItem) element )
						.getExtDefn( );

				PeerExtensionElementDefn peerDefn = (PeerExtensionElementDefn) extDefn;
				IReportItemFactory peerFactory = peerDefn
						.getReportItemFactory( );

				assert peerFactory != null;

				String extensionDefaultName = null;
				IMessages msgs = peerFactory.getMessages( );
				if ( msgs != null )
					extensionDefaultName = msgs
							.getMessage( (String) extDefn.getDisplayNameKey( ),
									ThreadResources.getLocale( ) );

				if ( StringUtil.isBlank( extensionDefaultName ) )
					extensionDefaultName = peerDefn.getName( );

				name = ModelMessages
						.getMessage( MessageConstants.NAME_PREFIX_NEW_MESSAGE );

				name = name + extensionDefaultName;

			}
			else
			{
				name = ModelMessages.getMessage( "New." //$NON-NLS-1$
						+ element.getDefn( ).getName( ) );
				name = name.trim( );
			}
		}

		// Add a numeric suffix that makes the name unique.

		int index = 0;
		String baseName = name;
		while ( nameSpace.contains( name ) || moduleNameSpace.contains( name ) )
		{
			name = baseName + ++index;
		}

		return name;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.birt.report.model.api.core.INameManager#makeUniqueName(org.eclipse.birt.report.model.core.DesignElement)
	 */

	public void makeUniqueName( DesignElement element )
	{
		String name = getUniqueName( element );

		if ( name == null )
			return;

		// if element is groupelement , set unique group name.

		if ( element instanceof GroupElement )
		{
			setUniqueGroupName( (GroupElement) element, name );
			return;
		}

		// if element already has the unique name.

		if ( name.equals( element.getName( ) ) )
			return;

		// set the unique name and add the element to the name manager

		ElementDefn eDefn = (ElementDefn) element.getDefn( );
		NameSpace nameSpace = getNameSpace( eDefn.getNameSpaceID( ) );
		element.setName( name.trim( ) );
		nameSpace.insert( element );

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.birt.report.model.api.core.INameManager#getHostModule()
	 */

	public Module getHostModule( )
	{
		return root;
	}

	/**
	 * Gets the cached namespace with the given id.
	 * 
	 * @param id
	 *            the namespace id to get
	 * @return the cached namespace with the given id
	 */

	NameSpace getNameSpace( int id )
	{
		assert id >= 0 && id < Module.NAME_SPACE_COUNT;
		return nameSpaces[id];
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.birt.report.model.api.core.INameManager#dropElement(org.eclipse.birt.report.model.core.DesignElement)
	 */

	public void dropElement( DesignElement element )
	{
		if ( element == null )
			return;

		ElementDefn defn = (ElementDefn) element.getDefn( );
		int id = defn.getNameSpaceID( );
		NameSpace ns = getNameSpace( id );
		if ( ns.getElement( element.getName( ) ) == element )
			ns.remove( element );
	}

	/**
	 * Returns a unique name for the group element. The name is unique in the
	 * scope of the table.
	 * 
	 * @param element
	 *            the group element.
	 * @return unique name of group element.
	 * 
	 */

	private String getUniqueGroupName( GroupElement group )
	{
		if ( group == null || group.getContainer( ) == null )
			return null;

		ListingHandle listing = (ListingHandle) group.getContainer( )
				.getHandle( root );

		String groupName = (String) group.getLocalProperty( root,
				GroupElement.GROUP_NAME_PROP );

		if ( StringUtil.isBlank( groupName ) )
		{
			GroupElement virtualGroup = (GroupElement) group.getVirtualParent( );

			while ( virtualGroup != null )
			{
				groupName = (String) virtualGroup.getLocalProperty(
						virtualGroup.getRoot( ), GroupElement.GROUP_NAME_PROP );
				if ( !StringUtil.isBlank( groupName ) )
				{
					break;
				}

				virtualGroup = (GroupElement) virtualGroup.getVirtualParent( );
			}
		}

		String namePrefix = groupName;
		int level = group.getGroupLevel( );

		if ( StringUtil.isBlank( namePrefix ) )
		{
			namePrefix = ModelMessages.getMessage( "New." //$NON-NLS-1$ 
					+ group.getDefn( ).getName( ) );
			namePrefix = namePrefix.trim( );

			if ( StringUtil.isBlank( namePrefix ) )
				namePrefix = GROUP_NAME_PREFIX;

			groupName = namePrefix + level;
		}

		while ( true )
		{
			if ( GroupNameValidator.getInstance( ).validateForRenamingGroup(
					listing, (GroupHandle) group.getHandle( root ), groupName )
					.size( ) == 0 )
			{
				break;
			}

			groupName = namePrefix + level;
			level++;

		}

		return groupName;

	}

	/**
	 * Creates a unique name for the group element. The name is unique in the
	 * scope of the table.
	 * 
	 * @param element
	 *            the group element.
	 * @param groupName
	 *            name of group element.
	 * 
	 */

	private void setUniqueGroupName( GroupElement group, String groupName )
	{
		assert groupName != null;

		String localGroupName = (String) group.getLocalProperty( root,
				GroupElement.GROUP_NAME_PROP );
		if ( groupName.equals( localGroupName ) )
			return;

		group.setProperty( GroupElement.GROUP_NAME_PROP, groupName );
	}

}
