/*******************************************************************************
 * Copyright (c) 2004, 2005 Actuate Corporation.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse private 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.core.script.bre;

import org.eclipse.birt.core.exception.BirtException;
import org.mozilla.javascript.ScriptableObject;

/**
 * 
 */
public class BirtMath extends ScriptableObject
{
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	public BirtMath()
	{
		this.defineProperty( "add", new Function_Add( ), 0 );
		this.defineProperty( "round", new Function_Round( ), 0 );
		this.defineProperty( "roundUp", new Function_RoundUp( ), 0 );
		this.defineProperty( "roundDown", new Function_RoundDown( ), 0 );
		this.defineProperty( "ceiling", new Function_Ceiling( ), 0 );
		this.defineProperty( "mod", new Function_Mod( ), 0 );
		this.defineProperty( "safeDivide", new Function_SafeDivide( ), 0 );
	}
	
	private class Function_Add extends Function_temp
	{
		/**
		 * 
		 */
		private static final long serialVersionUID = 1L;

		/**
		 * 
		 */
		Function_Add()
		{
			length = 2;
			isFixed = true;
		}
		
		protected Object getValue( final Object[] args ) throws BirtException
		{
			return new Double(add(toDoubleValue(args[0]),toDoubleValue(args[1])));
		}
	}	
	
	private static double add( double value1, double value2 )
	{
		return value1 + value2;
	}
	
	private class Function_Round extends Function_temp
	{
		/**
		 * 
		 */
		private static final long serialVersionUID = 1L;

		/**
		 * 
		 */
		Function_Round()
		{
			length = 2;
			isFixed = false;
		}
		
		protected Object getValue( Object[] args ) throws BirtException
		{
			if( args.length == 1)
				return new Double(round(toDoubleValue(args[0])));
			else
				return new Double(round(toDoubleValue(args[0]),(int)toDoubleValue(args[1])));
				
		}
	}
	
	/**
	 * Rounds a number to the integer.
	 * 
	 * @param value
	 * @return
	 */
	private static double round( double value )
	{
		return round( value, 0 );
	}
	
	/**
	 * Rounds a number to the specified number of digits. dec is an integer and
	 * can be negative.
	 * 
	 * @param value
	 * @param dec
	 * @return
	 */
	private static double round( double value, int dec )
	{
		double multiple = getMultiple( dec );
		return Math.round( value * multiple ) / multiple;
	}
	
	private class Function_RoundUp extends Function_temp
	{
		/**
		 * 
		 */
		private static final long serialVersionUID = 1L;

		Function_RoundUp()
		{
			length = 2;
			isFixed = false;
		}
		
		protected Object getValue( Object[] args ) throws BirtException
		{
			if( args.length == 1)
				return new Double(roundUp(toDoubleValue(args[0])));
			else
				return new Double(roundUp(toDoubleValue(args[0]),(int)toDoubleValue(args[1])));
				
		}
	}
	/**
	 * Rounds a number up, away from 0.
	 * 
	 * @param value
	 * @return
	 */
	private static double roundUp( double value )
	{
		return roundUp( value, 0 );
	}
	
	/**
	 * Rounds a number up, away from 0, to the specified number of digits.
	 * Default for dec is 0.
	 * 
	 * @param value
	 * @param dec
	 * @return
	 */
	private static double roundUp( double value, int dec )
	{
		double multiple = getMultiple( dec );
		return Math.round( Math.ceil( value * multiple) ) / multiple;
	}

	/**
	 * @param dec
	 * @return
	 */
	private static double getMultiple( int dec )
	{
		double multiple = 1;
		if ( dec >= 0 )
		{
			for ( int i = 0; i < dec; i++ )
			{
				multiple *= 10;
			}
		}
		else
		{
			double adjustment = 1;
			for( int i = dec; i < 0; i++ )
			{
				multiple *= 0.1;
				adjustment *= 10;
			}
			multiple = Math.round( multiple * adjustment ) / adjustment; 
		}
		return multiple;
	}
		
	private class Function_RoundDown extends Function_temp
	{
		/**
		 * 
		 */
		private static final long serialVersionUID = 1L;

		Function_RoundDown()
		{
			length = 2;
			isFixed = false;
		}
		
		protected Object getValue( Object[] args ) throws BirtException
		{
			if( args.length == 1)
				return new Double(roundDown(toDoubleValue(args[0])));
			else
				return new Double(roundDown(toDoubleValue(args[0]),(int)toDoubleValue(args[1])));
				
		}
	}
	/**
	 * Rounds a number down, away from 0, to the specified number of digits.
	 * 
	 * @param value
	 * @param dec
	 * @return
	 */
	private static double roundDown( double value, int dec )
	{
		double multiple = getMultiple( dec );
		return Math.round( Math.floor( value * multiple) ) / multiple;
	}
	
	/**
	 * Rounds a number down, away from 0.
	 * 
	 * @param value
	 * @return
	 */
	private static double roundDown( double value )
	{
		return roundDown( value, 0 );
	}
	
	private class Function_Ceiling extends Function_temp
	{
		/**
		 * 
		 */
		private static final long serialVersionUID = 1L;

		Function_Ceiling()
		{
			length = 2;
			isFixed = true;
		}
		
		protected Object getValue( Object[] args ) throws BirtException
		{
			return new Double(ceiling(toDoubleValue(args[0]),toDoubleValue(args[1])));
		}
	}	
	
	/**
	 * Rounds a number up, away from zero, to the nearest multiple of
	 * significance.
	 * 
	 * @param n
	 * @param significance
	 * @return
	 */
	private static double ceiling( double n, double significance )
	{
		if( significance == 0 || n == 0 )
			return 0;
		
		if( n * significance < 0 )
			throw new IllegalArgumentException("The given significance cannot be applied to the number");
		
		if( Math.abs( n ) < Math.abs( significance ) )
			return significance;
		
		double multiple = Math.ceil( n/significance );
		return multiple*significance;
	}
	
	private class Function_Mod extends Function_temp
	{
		/**
		 * 
		 */
		private static final long serialVersionUID = 1L;

		Function_Mod()
		{
			length = 2;
			isFixed = true;
		}
		
		protected Object getValue( Object[] args ) throws BirtException
		{
			return new Double(mod(toDoubleValue(args[0]),toDoubleValue(args[1])));
		}
	}
	
	/**
	 * Remainder after number is divided by divisor. The result has the same
	 * sign as divisor
	 * 
	 * @param n
	 * @param div
	 * @return
	 */
	private static double mod( double n, double div )
	{
		if( div == 0 )
			throw new IllegalArgumentException("The divisor cannot be 0"); 
		
		return n - div*Math.floor((n/div));
	}
	
	private class Function_SafeDivide extends Function_temp
	{
		private static final long serialVersionUID = 1L;

		Function_SafeDivide( )
		{
			length = 3;
			isFixed = true;
		}

		protected Object getValue( Object[] args ) throws BirtException
		{
			return new Double( safeDivide( toDoubleValue( args[0] ),
					toDoubleValue( args[1] ),
					toDoubleValue( args[2] ) ) );
		}
	}

	/**
	 * Returns a double whose value is (dividend / divisor) if divisor does NOT
	 * equal zero. ifZero will be returned otherwise
	 * 
	 * @param dividend
	 * @param divisor
	 * @param ifZero
	 * @return
	 */
	private static double safeDivide( double dividend, double divisor,
			double ifZero )
	{
		if ( divisor == 0 )
			return ifZero;

		return dividend / divisor;
	}
	
	private static double toDoubleValue( Object o )
	{
		if( o instanceof Number )
			return ((Number)o).doubleValue( );
		else 
			return Double.valueOf( o.toString( )).doubleValue( );
	}

	public String getClassName( )
	{
		return "BirtMath";
	}
} 
