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

	/**
	 * Cached structures.
	 */

	private final Structure[] structures = new Structure[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 );

		structures[0] = ( (CachedMemberRef) ref ).structures[0];
		structures[1] = ( (CachedMemberRef) ref ).structures[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 )
		{
			structures[0] = ( (CachedMemberRef) ref ).structures[0];
			structures[1] = ( (CachedMemberRef) ref ).structures[1];
		}
	}

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

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

	public CachedMemberRef( MemberRef ref, StructPropertyDefn memberDefn )
	{
		super( ref, memberDefn );
		if ( ref instanceof CachedMemberRef )
		{
			structures[0] = ( (CachedMemberRef) ref ).structures[0];
			structures[1] = ( (CachedMemberRef) ref ).structures[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 )
	{
		cached = true;

		Object localValue = element.getLocalProperty( module, propDefn );
		cachedFrom = localValue != null ? LOCAL : INHERITED;

		if ( propDefn.isList( ) )
		{
			List list = super.getList( module, element );
			if ( list == null )
				return;

			// Get the entry within the list.

			if ( depth == 1 )
			{
				structures[0] = super.getStructure( list, 0 );
			}
			else if ( depth == 2 )
			{
				assert localValue instanceof List;

				structures[0] = super.getStructure( (List) localValue, 0 );
				structures[1] = super.getStructure( list, 1 );
			}
		}
		else
		{
			Structure struct = (Structure) element.getProperty( module,
					propDefn );
			if ( struct == null )
				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] );

				structures[0] = super.getStructure( list, 0 );
			}
		}
	}

	/**
	 * 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 );

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

			doCacheStructure( module, element );
			return true;
		}

		// Check the cached structure

		if ( propDefn.isList( ) )
		{
			List list = super.getList( module, element );
			if ( list == null )
			{
				if ( structures[0] != null )
					return false;

				return true;
			}

			if ( depth == 1 )
			{
				if ( 0 > index[0] || index[0] >= list.size( )
						|| structures[0] != list.get( index[0] ) )
				{
					if ( structures[0] != null
							&& !list.contains( structures[0] ) )
						return false;
				}
			}
			else if ( depth == 2 )
			{
				if ( 0 > index[1] || index[1] >= list.size( )
						|| structures[1] != list.get( index[1] ) )
				{
					if ( structures[1] != null
							&& !list.contains( structures[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;

		return super.getValue( module, element );
	}

	/*
	 * Returns the cached structure.
	 * 
	 * @see org.eclipse.birt.report.model.core.MemberRef#getStructure(java.util.List,
	 *      int)
	 */

	Structure getStructure( List list, int level )
	{
		return structures[level];
	}
}