/*
 * 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.cfg;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

import org.hibernate.AnnotationException;
import org.hibernate.AssertionFailure;
import org.hibernate.MappingException;
import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.mapping.Column;
import org.hibernate.mapping.Component;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property;
import org.hibernate.mapping.Selectable;
import org.hibernate.mapping.SimpleValue;

import org.jboss.logging.Logger;

/**
 * @author Emmanuel Bernard
 */
public class CopyIdentifierComponentSecondPass implements SecondPass {
	private static final Logger log = Logger.getLogger( CopyIdentifierComponentSecondPass.class );

	private final String referencedEntityName;
	private final Component component;
	private final MetadataBuildingContext buildingContext;
	private final Ejb3JoinColumn[] joinColumns;

	public CopyIdentifierComponentSecondPass(
			Component comp,
			String referencedEntityName,
			Ejb3JoinColumn[] joinColumns,
			MetadataBuildingContext buildingContext) {
		this.component = comp;
		this.referencedEntityName = referencedEntityName;
		this.buildingContext = buildingContext;
		this.joinColumns = joinColumns;
	}

	@SuppressWarnings({ "unchecked" })
	public void doSecondPass(Map persistentClasses) throws MappingException {
		PersistentClass referencedPersistentClass = (PersistentClass) persistentClasses.get( referencedEntityName );
		// TODO better error names
		if ( referencedPersistentClass == null ) {
			throw new AnnotationException( "Unknown entity name: " + referencedEntityName );
		}
		if ( ! ( referencedPersistentClass.getIdentifier() instanceof Component ) ) {
			throw new AssertionFailure(
					"Unexpected identifier type on the referenced entity when mapping a @MapsId: "
							+ referencedEntityName
			);
		}
		Component referencedComponent = (Component) referencedPersistentClass.getIdentifier();
		Iterator<Property> properties = referencedComponent.getPropertyIterator();


		//prepare column name structure
		boolean isExplicitReference = true;
		Map<String, Ejb3JoinColumn> columnByReferencedName = new HashMap<String, Ejb3JoinColumn>(joinColumns.length);
		for (Ejb3JoinColumn joinColumn : joinColumns) {
			final String referencedColumnName = joinColumn.getReferencedColumn();
			if ( referencedColumnName == null || BinderHelper.isEmptyAnnotationValue( referencedColumnName ) ) {
				break;
			}
			//JPA 2 requires referencedColumnNames to be case insensitive
			columnByReferencedName.put( referencedColumnName.toLowerCase(Locale.ROOT), joinColumn );
		}
		//try default column orientation
		AtomicInteger index = new AtomicInteger( 0 );
		if ( columnByReferencedName.isEmpty() ) {
			isExplicitReference = false;
			for (Ejb3JoinColumn joinColumn : joinColumns) {
				columnByReferencedName.put( "" + index.get(), joinColumn );
				index.getAndIncrement();
			}
			index.set( 0 );
		}

		while ( properties.hasNext() ) {
			Property referencedProperty = properties.next();
			if ( referencedProperty.isComposite() ) {
				Property property = createComponentProperty( referencedPersistentClass, isExplicitReference, columnByReferencedName, index, referencedProperty );
				component.addProperty( property );
			}
			else {
				Property property = createSimpleProperty( referencedPersistentClass, isExplicitReference, columnByReferencedName, index, referencedProperty );
				component.addProperty( property );
			}
		}
	}

	private Property createComponentProperty(
			PersistentClass referencedPersistentClass,
			boolean isExplicitReference,
			Map<String, Ejb3JoinColumn> columnByReferencedName,
			AtomicInteger index,
			Property referencedProperty ) {
		Property property = new Property();
		property.setName( referencedProperty.getName() );
		//FIXME set optional?
		//property.setOptional( property.isOptional() );
		property.setPersistentClass( component.getOwner() );
		property.setPropertyAccessorName( referencedProperty.getPropertyAccessorName() );
		Component value = new Component( buildingContext.getMetadataCollector(), component.getOwner() );

		property.setValue( value );
		final Component referencedValue = (Component) referencedProperty.getValue();
		value.setTypeName( referencedValue.getTypeName() );
		value.setTypeParameters( referencedValue.getTypeParameters() );
		value.setComponentClassName( referencedValue.getComponentClassName() );


		Iterator<Property> propertyIterator = referencedValue.getPropertyIterator();
		while(propertyIterator.hasNext())
		{
			Property referencedComponentProperty = propertyIterator.next();

			if ( referencedComponentProperty.isComposite() ) {
				Property componentProperty = createComponentProperty( referencedValue.getOwner(), isExplicitReference, columnByReferencedName, index, referencedComponentProperty );
				value.addProperty( componentProperty );
			}
			else {
				Property componentProperty = createSimpleProperty( referencedValue.getOwner(), isExplicitReference, columnByReferencedName, index, referencedComponentProperty );
				value.addProperty( componentProperty );
			}
		}

		return property;
	}


	private Property createSimpleProperty(
			PersistentClass referencedPersistentClass,
			boolean isExplicitReference,
			Map<String, Ejb3JoinColumn> columnByReferencedName,
			AtomicInteger index,
			Property referencedProperty ) {
		Property property = new Property();
		property.setName( referencedProperty.getName() );
		//FIXME set optional?
		//property.setOptional( property.isOptional() );
		property.setPersistentClass( component.getOwner() );
		property.setPropertyAccessorName( referencedProperty.getPropertyAccessorName() );
		SimpleValue value = new SimpleValue( buildingContext.getMetadataCollector(), component.getTable() );
		property.setValue( value );
		final SimpleValue referencedValue = (SimpleValue) referencedProperty.getValue();
		value.setTypeName( referencedValue.getTypeName() );
		value.setTypeParameters( referencedValue.getTypeParameters() );
		final Iterator<Selectable> columns = referencedValue.getColumnIterator();

		if ( joinColumns[0].isNameDeferred() ) {
			joinColumns[0].copyReferencedStructureAndCreateDefaultJoinColumns(
				referencedPersistentClass,
				columns,
				value);
		}
		else {
			//FIXME take care of Formula
			while ( columns.hasNext() ) {
				final Selectable selectable = columns.next();
				if ( ! Column.class.isInstance( selectable ) ) {
					log.debug( "Encountered formula definition; skipping" );
					continue;
				}
				final Column column = (Column) selectable;
				final Ejb3JoinColumn joinColumn;
				String logicalColumnName = null;
				if ( isExplicitReference ) {
					final String columnName = column.getName();
					logicalColumnName = buildingContext.getMetadataCollector().getLogicalColumnName(
							referencedPersistentClass.getTable(),
							columnName
					);
					//JPA 2 requires referencedColumnNames to be case insensitive
					joinColumn = columnByReferencedName.get( logicalColumnName.toLowerCase(Locale.ROOT ) );
				}
				else {
					joinColumn = columnByReferencedName.get( "" + index.get() );
					index.getAndIncrement();
				}
				if ( joinColumn == null && ! joinColumns[0].isNameDeferred() ) {
					throw new AnnotationException(
							isExplicitReference ?
									"Unable to find column reference in the @MapsId mapping: " + logicalColumnName :
									"Implicit column reference in the @MapsId mapping fails, try to use explicit referenceColumnNames: " + referencedEntityName
					);
				}
				final String columnName = joinColumn == null || joinColumn.isNameDeferred() ? "tata_" + column.getName() : joinColumn
						.getName();
				value.addColumn( new Column( columnName ) );
				if ( joinColumn != null ) {
					joinColumn.linkWithValue( value );
				}
				column.setValue( value );
			}
		}
		return property;
	}

	public boolean dependentUpon( CopyIdentifierComponentSecondPass other ) {
		return this.referencedEntityName.equals( other.component.getOwner().getEntityName() );
	}
}
