/*******************************************************************************
 * 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.util.ArrayList;
import java.util.List;

import org.eclipse.birt.report.model.api.core.IStructure;
import org.eclipse.birt.report.model.metadata.ElementPropertyDefn;
import org.eclipse.birt.report.model.metadata.StructPropertyDefn;

/**
 * Represents the member reference which can cache the structure. This class can
 * be treated as the substitute for <code>MemberRef</code>.
 */

public class CachedMemberRef extends MemberRef
{

	/**
	 * Initial state.
	 */

	private final static int UNKNOWN = 1;

	/**
	 * Indicates the cached structure is from the local value.
	 */

	private final static int LOCAL = 2;

	/**
	 * Indicates the cached structure is from the inhertied value.
	 */

	private final static int INHERITED = 3;

	/**
	 * Indicates the cached structure is from the element referred value.
	 */

	private final static int REFERRED = 4;

	/**
	 * Cached structures.
	 */

	private final Object[] cachedObjects = new Object[2];

	/**
	 * Indicates whether this member reference cached structure.
	 */

	private boolean cached = false;

	/**
	 * Indicates where the cached structure is from.
	 */

	private int cachedFrom = UNKNOWN;

	public CachedMemberRef( MemberRef ref )
	{
		super( ref );

		cachedObjects[0] = ( (CachedMemberRef) ref ).cachedObjects[0];
		cachedObjects[1] = ( (CachedMemberRef) ref ).cachedObjects[1];
		cached = ( (CachedMemberRef) ref ).cached;
		cachedFrom = ( (CachedMemberRef) ref ).cachedFrom;
	}

	public CachedMemberRef( ElementPropertyDefn prop )
	{
		super( prop );
	}

	public CachedMemberRef( ElementPropertyDefn prop, int n )
	{
		super( prop, n );
	}

	public CachedMemberRef( ElementPropertyDefn prop, int n, String memberName )
	{
		super( prop, n, memberName );
	}

	public CachedMemberRef( ElementPropertyDefn prop, int n,
			StructPropertyDefn memberDef )
	{
		super( prop, n, memberDef );
	}

	public CachedMemberRef( ElementPropertyDefn prop, String memberName )
	{
		super( prop, memberName );
	}

	public CachedMemberRef( ElementPropertyDefn prop,
			StructPropertyDefn memberDef )
	{
		super( prop, memberDef );
	}

	public CachedMemberRef( MemberRef ref, int n )
	{
		super( ref, n );
		if ( ref instanceof CachedMemberRef )
		{
			cachedObjects[0] = ( (CachedMemberRef) ref ).cachedObjects[0];
			cachedObjects[1] = ( (CachedMemberRef) ref ).cachedObjects[1];
		}
	}

	public CachedMemberRef( MemberRef ref, int n, StructPropertyDefn memberDefn )
	{
		super( ref, n, memberDefn );
		if ( ref instanceof CachedMemberRef )
		{
			cachedObjects[0] = ( (CachedMemberRef) ref ).cachedObjects[0];
			cachedObjects[1] = ( (CachedMemberRef) ref ).cachedObjects[1];
		}
	}

	public CachedMemberRef( MemberRef ref, String memberName )
	{
		super( ref, memberName );
		if ( ref instanceof CachedMemberRef )
		{
			cachedObjects[0] = ( (CachedMemberRef) ref ).cachedObjects[0];
			cachedObjects[1] = ( (CachedMemberRef) ref ).cachedObjects[1];
			cached = ( (CachedMemberRef) ref ).cached;
			cachedFrom = ( (CachedMemberRef) ref ).cachedFrom;
		}
	}

	public CachedMemberRef( MemberRef ref, StructPropertyDefn memberDefn )
	{
		super( ref, memberDefn );
		if ( ref instanceof CachedMemberRef )
		{
			cachedObjects[0] = ( (CachedMemberRef) ref ).cachedObjects[0];
			cachedObjects[1] = ( (CachedMemberRef) ref ).cachedObjects[1];
			cached = ( (CachedMemberRef) ref ).cached;
			cachedFrom = ( (CachedMemberRef) ref ).cachedFrom;
		}
	}

	/**
	 * Caches the structure this member reference points in the given element.
	 * 
	 * @param module
	 *            module
	 * @param element
	 *            element containing the cached structure
	 * @return false only the structure is cached before and invalid, otherwise,
	 *         return true.
	 */

	public boolean checkOrCacheStructure( Module module, DesignElement element )
	{
		if ( cached )
			return checkCachedStructure( module, element );

		doCacheStructure( module, element );

		assert cachedFrom != UNKNOWN;

		return true;
	}

	/**
	 * Caches the structure this member reference points in the given element.
	 * 
	 * @param module
	 *            module
	 * @param element
	 *            element containing the cached structure
	 */

	private void doCacheStructure( Module module, DesignElement element )
	{
		Object localValue = element.getLocalProperty( module, propDefn );
		Object realValue = element.getProperty( module, propDefn );

		cachedFrom = localValue != null ? LOCAL : INHERITED;

		// if two values are not equal, this is for data binding reference.

		if ( localValue != realValue )
		{
			cachedFrom = REFERRED;
		}

		if ( propDefn.isListType( ) )
		{
			List list = (List) localValue;

			// retrieve the list value since this value can be inherited.

			if ( cachedFrom == INHERITED || cachedFrom == REFERRED )
				list = (List) realValue;

			if ( list == null )
			{
				cached = true;
				return;
			}

			if ( depth >= 1 )
			{
				cachedObjects[0] = super.getValue( list, 0 );
			}

			if ( depth == 1 )
			{
				cached = true;
				return;
			}

			// Get the entry within the list.

			list = super.getList( module, element );

			cachedObjects[1] = super.getValue( list, 1 );
		}
		else
		{
			Structure struct = (Structure) element.getProperty( module,
					propDefn );
			if ( struct == null )
			{
				cached = true;
				return;
			}

			if ( index[0] >= 0 )
			{
				// property.member.list[n]
				// property.member.list[n].member

				assert member[0].isList( );

				ArrayList list = (ArrayList) struct.getProperty( module,
						member[0] );

				cachedObjects[0] = super.getValue( list, 0 );
			}
		}

		cached = true;
	}

	/**
	 * Checks whether the cached structure is valid.
	 * 
	 * @param module
	 *            module
	 * @param element
	 *            module element
	 * @return true, if the cached structure is valid. Otherwise, return false
	 */

	private boolean checkCachedStructure( Module module, DesignElement element )
	{
		assert cached;

		Object localValue = element.getLocalProperty( module, propDefn );
		Object realValue = element.getProperty( module, propDefn );

		if ( ( cachedFrom == LOCAL && localValue == null )
				|| ( cachedFrom == INHERITED && localValue != null ) )
		{
			cachedObjects[0] = null;
			cachedObjects[1] = null;

			doCacheStructure( module, element );
			return true;
		}

		// Check the cached structure

		if ( propDefn.isListType( ) )
		{
			List list = (List) localValue;

			// retrieve the list value since this value can be inherited or
			// referred like data binding ref.

			if ( cachedFrom == INHERITED || cachedFrom == REFERRED )
				list = (List) realValue;

			if ( list == null )
			{
				if ( cachedObjects[0] != null )
					return false;

				return true;
			}

			if ( depth >= 1 )
			{
				if ( 0 > index[0] || index[0] >= list.size( )
						|| cachedObjects[0] != list.get( index[0] ) )
				{
					if ( cachedObjects[0] != null
							&& !list.contains( cachedObjects[0] ) )
						return false;
				}

				if ( depth == 1 )
					return true;
			}

			if ( member[0] != null && member[0].isList( ) )
			{
				assert cachedObjects[0] instanceof Structure;
				list = (List) ( (Structure) cachedObjects[0] ).getProperty(
						module, member[0] );
			}

			if ( depth == 2 )
			{
				if ( 0 > index[1] || index[1] >= list.size( )
						|| cachedObjects[1] != list.get( index[1] ) )
				{
					if ( cachedObjects[1] != null
							&& !list.contains( cachedObjects[1] ) )
						return false;
				}
			}
		}

		return true;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.birt.report.model.core.MemberRef#getList(org.eclipse.birt.report.model.core.Module,
	 *      org.eclipse.birt.report.model.core.DesignElement)
	 */

	public List getList( Module module, DesignElement element )
	{
		if ( !checkOrCacheStructure( module, element ) )
			return null;

		return super.getList( module, element );
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.birt.report.model.core.MemberRef#getLocalValue(org.eclipse.birt.report.model.core.Module,
	 *      org.eclipse.birt.report.model.core.DesignElement)
	 */

	public Object getLocalValue( Module module, DesignElement element )
	{
		if ( !checkOrCacheStructure( module, element ) )
			return null;

		return super.getLocalValue( module, element );
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.birt.report.model.core.MemberRef#getStructure(org.eclipse.birt.report.model.core.Module,
	 *      org.eclipse.birt.report.model.core.DesignElement)
	 */

	public Structure getStructure( Module module, DesignElement element )
	{
		 if ( !checkOrCacheStructure( module, element ) )
			return null;

		return super.getStructure( module, element );
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.birt.report.model.core.MemberRef#getValue(org.eclipse.birt.report.model.core.Module,
	 *      org.eclipse.birt.report.model.core.DesignElement)
	 */

	public Object getValue( Module module, DesignElement element )
	{
		if ( !checkOrCacheStructure( module, element ) )
			return null;

		if ( propDefn.isListType( ) )
		{
			// property
			// property.list[n]
			// property.list[n].member
			// property.list[n].member.list[n]
			// property.list[n].member.list[n].member
			// property.list[n].member.member

			switch ( refType )
			{
				case PROPERTY :
					return super.getList( module, element );
				case PROPERTY_LISTn :
					return getValue( null, 0 );
				case PROPERTY_LISTn_MEMBER_LISTn :
					return getValue( null, 1 );
				case PROPERTY_LISTn_MEMBER : // reference the list itself.
				{
					Structure struct = (Structure) getValue( null, 0 );
					if ( struct == null )
						return null;
					return struct.getProperty( module, member[0] );
				}
				case PROPERTY_LISTn_MEMBER_MEMBER :
				case PROPERTY_LISTn_MEMBER_LISTn_MEMBER :
				{
					Structure struct = (Structure) getValue( null, 1 );
					if ( struct == null )
						return null;
					return struct.getProperty( module, member[1] );
				}
				default :
				{
					assert false;
					return null;
				}
			}
		}

		return super.getValue( module, element );
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.birt.report.model.core.MemberRef#getValue(java.util.List,
	 *      int)
	 */

	protected Object getValue( List list, int level )
	{
		assert level == 0 || level == 1;

		return cachedObjects[level];
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	public boolean equals( Object obj )
	{
		if ( obj == this )
			return true;
		if ( !( obj instanceof CachedMemberRef ) )
			return false;

		if ( !super.equals( obj ) )
			return false;

		CachedMemberRef temp = (CachedMemberRef) obj;

		return ( temp.cached == this.cached
				&& temp.cachedFrom == this.cachedFrom && equalArray(
				temp.cachedObjects, this.cachedObjects ) );
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#hashCode()
	 */
	public int hashCode( )
	{
		int hash = super.hashCode( );

		hash = 7 * hash + ( cached == true ? 1 : 0 );
		hash = 7 * hash + cachedFrom;
		hash = addObjectArrayHashCode( hash, cachedObjects );

		return hash;
	}

	/**
	 * Updates the cached structure regardless of its previous state. This is
	 * used when the structure list is localized after the command.
	 * 
	 * @param module
	 *            the module
	 * @param element
	 *            the element
	 */

	public void cacheStructureInForce( Module module, DesignElement element )
	{
		cached = false;

		doCacheStructure( module, element );
		assert cachedFrom == LOCAL;
	}
}