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

import java.math.BigDecimal;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.eclipse.birt.core.format.DateFormatter;
import org.eclipse.birt.report.engine.api.ITOCTree;
import org.eclipse.birt.report.engine.api.TOCNode;

import com.ibm.icu.util.ULocale;

public class TOCTree implements ITOCTree
{
	private String format;

	private ULocale locale;

	TOCTreeNode root;
	
	TOCNode refactedRoot;
	
	/** tocId, TOCNode map */
	private HashMap tocMapByID;
	
	public TOCTree( )
	{
	}
	
	public TOCTree( TOCTreeNode root, String format, ULocale locale )
	{
		this.root = root;
		this.format = format;
		if ( "html".equalsIgnoreCase( this.format ) )
		{
			this.format = "viewer";
		}
		this.locale = locale;
	}

	public TOCNode findTOC( String tocNodeId )
	{
		initialize( );
		if ( refactedRoot == null )
		{
			return null;
		}
		if ( tocMapByID == null )
		{
			generateIndex( );
		}
		return (TOCNode) tocMapByID.get( tocNodeId );
	}

	public List findTOCByValue( Object tocValue )
	{
		if ( tocValue == null )
		{
			return null;
		}
		initialize( );
		if ( root == null )
		{
			return null;
		}
		List nodes = search( tocValue, locale );
		List result = new ArrayList( );
		for ( int i = 0; i < nodes.size( ); i++ )
		{
			TOCTreeNode treeNode = (TOCTreeNode) nodes.get( i );
			TOCNode node = refactNode( treeNode, format, locale );
			if ( node != null )
			{
				result.add( node );
			}
		}
		if ( result.size( ) == 0 )
		{
			return null;
		}
		return result;
	}

	private void initialize( )
	{
		initializeRoot( );
		initializeLocale( );
		ensureRefactedTreeAvailable( );
	}

	public TOCTreeNode getTOCRoot( )
	{
		initializeRoot( );
		return root;
	}

	public TOCNode getRoot( )
	{
		ensureRefactedTreeAvailable( );
		return refactedRoot;
	}

	private void ensureRefactedTreeAvailable( )
	{
		if ( refactedRoot == null )
		{
			refactedRoot = refactNode( root, format, locale );
		}
	}

	public String getFormat( )
	{
		return format;
	}

	public void setFormat( String format )
	{
		this.format = format;
		if ( "html".equalsIgnoreCase( this.format ) )
		{
			this.format = "viewer";
		}
	}

	public ULocale getLocale( )
	{
		return locale;
	}

	public void setLocale( ULocale locale )
	{
		this.locale = locale;
	}

	void setRoot( TOCTreeNode root )
	{
		this.root = root;
	}

	protected void initializeRoot( )
	{
		if ( root == null )
		{
			root = new TOCTreeNode( );
		}
	}

	private List search( Object tocValue, ULocale locale )
	{
		Set nodes = new HashSet( );
		List keys = parse( tocValue, locale );
		search( nodes, keys, root );
		return Collections.list( Collections.enumeration( nodes ) );
	}

	private void search( Set result, List keys, TOCTreeNode rootNode )
	{
		for ( int i = 0; i < keys.size( ); i++ )
		{
			if ( equals( keys.get( i ), rootNode.getTOCValue( ) ) )
			{
				result.add( rootNode );
			}
		}
		List children = rootNode.getChildren( );
		for ( int i = 0; i < children.size( ); i++ )
		{
			search( result, keys, (TOCTreeNode) children.get( i ) );
		}
	}

	private boolean equals( Object searchKey, Object key )
	{
		assert searchKey != null;
		if ( key == null )
		{
			return false;
		}
		if ( searchKey instanceof String )
		{
			return searchKey.equals( key.toString( ) );
		}
		if ( searchKey instanceof Number && key instanceof Number )
		{
			BigDecimal searchDecimal = toBigDecimal( (Number) searchKey );
			BigDecimal decima2 = toBigDecimal( (Number) key );
			return searchDecimal.compareTo( decima2 ) == 0;
		}
		return searchKey.equals( key );
	}

	private BigDecimal toBigDecimal( Number value )
	{
		BigDecimal decimal;
		if ( value instanceof BigDecimal )
		{
			decimal = (BigDecimal) value;
		}
		else
		{
			decimal = new BigDecimal( value.toString( ) );
		}
		decimal = decimal.setScale( 20 );
		return decimal;
	}

	private TOCNode refactNode( TOCTreeNode node, String format, ULocale locale )
	{
		if ( node == null || node.isHidden( format ) )
		{
			return null;
		}
		if ( isFirstChildOfDummyGroup( node, format ) )
		{
			TOCTreeNode parent = (TOCTreeNode) node.getParent( );
			return extractTOCNode( parent, format, locale );
		}
		else
		{
			return extractTOCNode( node, format, locale );
		}
	}

	private boolean isFirstChildOfDummyGroup( TOCTreeNode node, String format )
	{
		TOCTreeNode parent = (TOCTreeNode) node.getParent( );
		if ( parent != null && shouldRemove( parent ) )
		{
			List children = parent.getChildren( );
			for ( int i = 0; i < children.size( ); i++ )
			{
				TOCTreeNode tmpNode = (TOCTreeNode) children.get( i );
				if ( tmpNode == node )
				{
					return true;
				}
				if ( !tmpNode.isHidden( format ) )
				{
					return false;
				}
			}
		}
		return false;
	}

	private void generateIndex( )
	{
		tocMapByID = new HashMap( );
		generateRootTOCIndex( );
	}

	private TOCNode extractTOCNode( TOCTreeNode root, String format,
			ULocale locale )
	{
		if ( root == null )
		{
			return null;
		}
		if ( root.isHidden( format ) )
		{
			return null;
		}
		TOCNode result = TOCTreeNode.copyLocalizedNode( root, locale );
		List children = root.getChildren( );
		boolean start = true;
		for ( int i = 0; i < children.size( ); i++ )
		{
			TOCTreeNode child = (TOCTreeNode) children.get( i );
			TOCNode childResult = extractTOCNode( child, format, locale );
			if ( childResult != null )
			{
				if ( shouldRemove( root ) && start )
				{
					childResult.setParent( result.getParent( ) );
					result = childResult;
					start = false;
				}
				else
				{
					childResult.setParent( result );
					result.getChildren( ).add( childResult );
				}
			}
		}
		if ( shouldRemove( root ) && start )
		{
			return null;
		}
		return result;
	}

	private void generateRootTOCIndex( )
	{
		tocMapByID.put( "/", refactedRoot );
		generateTOCIndex( refactedRoot );
	}

	/**
	 * add the TOC node into the map for search.
	 * 
	 * @param node
	 *            TOC cache.
	 * @param map
	 *            map contains the (id, TOC) pair.
	 */
	private void generateTOCIndex( TOCNode node )
	{
		tocMapByID.put( node.getNodeID( ), node );
		Iterator iter = node.getChildren( ).iterator( );
		while ( iter.hasNext( ) )
		{
			TOCNode child = (TOCNode) iter.next( );
			generateTOCIndex( child );
		}
	}

	private boolean shouldRemove( TOCTreeNode node )
	{
		return node.isGroupRoot( ) && node.isBlank( );
	}

	private void initializeLocale( )
	{
		if ( locale == null )
		{
			locale = ULocale.getDefault( );
		}
	}

	private List parse( Object value, ULocale locale )
	{
		assert value != null;
		ArrayList result = new ArrayList( );
		result.add( value );
		if ( value instanceof String )
		{
			String stringValue = (String) value;
			Object parseValue = null;
			try
			{
				parseValue = new BigDecimal( stringValue );
			}
			catch ( NumberFormatException e )
			{
			}
			if ( parseValue != null )
			{
				result.add( parseValue );
				parseValue = null;
			}
			DateFormatter formatter = new DateFormatter( locale );
			try
			{
				parseValue = formatter.parse( stringValue );
			}
			catch ( ParseException e )
			{
			}
			if ( parseValue != null )
			{
				result.add( parseValue );
			}
		}
		else
		{
			result.add( value.toString( ) );
		}
		return result;
	}
}
