// Aqsis
// Copyright (c) 1997 - 2001, Paul C. Gregory
//
// Contact: pgregory@aqsis.com
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

/**
 * Copyright (C) 2006-2007  NTT DATA CORPORATION
 * 
 * Version: 1.0.0 2007/04/01
 *  
 */
package net.cellcomputing.himawari.library;

import java.lang.reflect.Modifier;

import net.cellcomputing.himawari.accessory.primitive.p_String;
import net.cellcomputing.himawari.accessory.primitive.p_float;
import net.cellcomputing.himawari.accessory.primitive.p_int;
import net.cellcomputing.himawari.library.types.CqColor;
import net.cellcomputing.himawari.library.types.CqMatrix;
import net.cellcomputing.himawari.library.types.CqVector3D;
import net.cellcomputing.himawari.library.types.CqVector4D;
import net.cellcomputing.himawari.util.HimawariLogger;

/**
 * @brief Template class to handle Bezier forward differencing<br>
 * ベジェの違いを検出するテンプレートのクラス
 * @author Paul C. Gregory (pgregory@aqsis.com)
 * 
 * @author NTT DATA Corporation
 */
public strictfp class CqForwardDiffBezier<T> {
	
	
	// Precalculated forward-difference matrix (applied to control points to
	// find actual forward difference increments).
	private float M00, M01, M02, M03, M10, M11, M12, M13, M20, M21, M22, M23;
	
	// Forward difference vector increments (and start point f)
	Class<? extends T> cls; //Generics T
	T f, df, ddf, dddf;
	
	/**
	 * コンストラクタ
	 * 
	 * @param dt
	 * @param cls
	 */
	public CqForwardDiffBezier( float dt, Class<? extends T> cls  )
	{
		f = newInstance( cls );
		df = newInstance( cls );
		ddf = newInstance( cls );
		dddf = newInstance( cls );
		InitPreCalcMatrix( dt );
	}
	
	/**
	 * Initializes the PRECALCULATED matrix (M). Needs to be reset any time the
	 * the parametric "step-size" changes (same as Uincr in direct eval version).
	 * 
	 * @param dt
	 */
	public void InitPreCalcMatrix( float dt )
	{
		// store repeated calculations
		float dt2 = dt * dt, dt3 = dt2 * dt, dt3_3 = 3 * dt3, dt3_6 = 6 * dt3,
		dt3_18 = 18 * dt3, dt2_3 = 3 * dt2, dt2_6 = 6 * dt2, dt_3 = 3 * dt;
		M00 = -dt3_6; M01 = dt3_18; M02 = -dt3_18; M03 = dt3_6;
		M10 = dt2_6 - dt3_6; M11 = dt3_18 - 2 * dt2_6; M12 = dt2_6 - dt3_18; M13 = dt3_6;
		M20 = dt2_3 - dt_3 - dt3; M21 = dt3_3 - dt2_6 + dt_3; M22 = dt2_3 - dt3_3; M23 = dt3;
	};
	
	/**
	 * Applies PRECALCULATED matrix to the four bezier control points to calculate
	 *  the FORWARD-DIFFERENCE increments matrix (FD).
	 * 
	 * @param A
	 * @param B
	 * @param C
	 * @param D
	 */
	public void CalcForwardDiff( T A, T B, T C, T D )
	{
		if( A instanceof CqVector3D ) {
			((CqVector3D)f).assignment( ((CqVector3D)A) );	// STORE THE START VALUE
			((CqVector3D)df).assignment( ((CqVector3D)A).mul( M20 ).add( ((CqVector3D)B).mul( M21 ).add( ((CqVector3D)C).mul( M22 ).add( ((CqVector3D)D).mul( M23 ) )))); // CALC CHANGE IN START
			((CqVector3D)ddf).assignment( ((CqVector3D)A).mul( M10 ).add( ((CqVector3D)B).mul( M11 ).add( ((CqVector3D)C).mul( M12 ).add( ((CqVector3D)D).mul( M13 ) )))); // CALC CHANGE IN "CHANGE IN START"
			((CqVector3D)dddf).assignment( ((CqVector3D)A).mul( M00 ).add( ((CqVector3D)B).mul( M01 ).add( ((CqVector3D)C).mul( M02 ).add( ((CqVector3D)D).mul( M03 ) )))); // CALC CHANGE IN CHANGE IN "CHANGE IN START"  		
		}
		else if( A instanceof CqVector4D ) {
			((CqVector4D)f).assignment( ((CqVector4D)A) );	// STORE THE START VALUE
			((CqVector4D)df).assignment( ((CqVector4D)A).mul( M20 ).add( ((CqVector4D)B).mul( M21 ).add( ((CqVector4D)C).mul( M22 ).add( ((CqVector4D)D).mul( M23 ) )))); // CALC CHANGE IN START
			((CqVector4D)ddf).assignment( ((CqVector4D)A).mul( M10 ).add( ((CqVector4D)B).mul( M11 ).add( ((CqVector4D)C).mul( M12 ).add( ((CqVector4D)D).mul( M13 ) )))); // CALC CHANGE IN "CHANGE IN START"
			((CqVector4D)dddf).assignment( ((CqVector4D)A).mul( M00 ).add( ((CqVector4D)B).mul( M01 ).add( ((CqVector4D)C).mul( M02 ).add( ((CqVector4D)D).mul( M03 ) )))); // CALC CHANGE IN CHANGE IN "CHANGE IN START"  		
		}
		else if( A instanceof CqColor ) {
			((CqColor)f).assignment( ((CqColor)A) );		// STORE THE START VALUE
			((CqColor)df).assignment( ((CqColor)A).mul( M20 ).add( ((CqColor)B).mul( M21 ).add( ((CqColor)C).mul( M22 ).add( ((CqColor)D).mul( M23 ) )))); // CALC CHANGE IN START
			((CqColor)ddf).assignment( ((CqColor)A).mul( M10 ).add( ((CqColor)B).mul( M11 ).add( ((CqColor)C).mul( M12 ).add( ((CqColor)D).mul( M13 ) )))); // CALC CHANGE IN "CHANGE IN START"
			((CqColor)dddf).assignment( ((CqColor)A).mul( M00 ).add( ((CqColor)B).mul( M01 ).add( ((CqColor)C).mul( M02 ).add( ((CqColor)D).mul( M03 ) )))); // CALC CHANGE IN CHANGE IN "CHANGE IN START"  		
		}
		else if( A instanceof CqMatrix ) {
			((CqMatrix)f).assignment( ((CqMatrix)A) );		// STORE THE START VALUE
			((CqMatrix)df).assignment( ((CqMatrix)A).multiply( M20 ).add( ((CqMatrix)B).multiply( M21 )).add( ((CqMatrix)C).multiply( M22 )).add( ((CqMatrix)D).multiply( M23 ) )); // CALC CHANGE IN START
			((CqMatrix)ddf).assignment( ((CqMatrix)A).multiply( M10 ).add( ((CqMatrix)B).multiply( M11 )).add( ((CqMatrix)C).multiply( M12 )).add( ((CqMatrix)D).multiply( M13 ) )); // CALC CHANGE IN "CHANGE IN START"
			((CqMatrix)dddf).assignment( ((CqMatrix)A).multiply( M00 ).add( ((CqMatrix)B).multiply( M01 )).add( ((CqMatrix)C).multiply( M02 )).add( ((CqMatrix)D).multiply( M03 ) )); // CALC CHANGE IN CHANGE IN "CHANGE IN START"  		
		}
		else if( A instanceof p_float ) {
			((p_float)f).value = ((p_float)A).value;		// STORE THE START VALUE
			((p_float)df).value = ((p_float)A).value * M20 + ((p_float)B).value * M21 + ((p_float)C).value * M22 + ((p_float)D).value * M23 ; // CALC CHANGE IN START
			((p_float)ddf).value = ((p_float)A).value * M10 + ((p_float)B).value * M11 +((p_float)C).value * M12 + ((p_float)D).value * M13 ; // CALC CHANGE IN "CHANGE IN START"
			((p_float)dddf).value = ((p_float)A).value * M00 + ((p_float)B).value * M01 + ((p_float)C).value * M02 + ((p_float)D).value * M03 ; // CALC CHANGE IN CHANGE IN "CHANGE IN START"  		
		}
		else if( A instanceof p_int ) {
			((p_int)f).value = ((p_int)A).value;			// STORE THE START VALUE
			((p_int)df).value = (int)(((p_int)A).value * M20 + ((p_int)B).value * M21 + ((p_int)C).value * M22 + ((p_int)D).value * M23 ); // CALC CHANGE IN START
			((p_int)ddf).value = (int)(((p_int)A).value * M10 + ((p_int)B).value * M11 +((p_int)C).value * M12 + ((p_int)D).value * M13 ); // CALC CHANGE IN "CHANGE IN START"
			((p_int)dddf).value = (int)(((p_int)A).value * M00 + ((p_int)B).value * M01 + ((p_int)C).value * M02 + ((p_int)D).value * M03 ); // CALC CHANGE IN CHANGE IN "CHANGE IN START"  		
		}
		else if( A instanceof p_String ) {
			((p_String)f).value = ((p_String)A).value;
			((p_String)df).value = ((p_String)A).value + ((p_String)B).value + ((p_String)C).value + ((p_String)D).value ; // CALC CHANGE IN START
			((p_String)ddf).value = ((p_String)A).value + ((p_String)B).value + ((p_String)C).value + ((p_String)D).value ; // CALC CHANGE IN "CHANGE IN START"
			((p_String)dddf).value = ((p_String)A).value + ((p_String)B).value + ((p_String)C).value + ((p_String)D).value ; // CALC CHANGE IN CHANGE IN "CHANGE IN START"  		
		}
	}
	
	/**
	 * Returns the current value (f). Increments to the next step.
	 * 
	 * 
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public T GetValue()
	{
		T res;
		if( f instanceof CqVector3D ) {
			res = (T) new CqVector3D( (CqVector3D)f );					// store current value
			((CqVector3D)f).assignment( ((CqVector3D)f).add( (CqVector3D)df ));	// go to next step
			((CqVector3D)df).assignment( ((CqVector3D)df).add( (CqVector3D)ddf )); 
			((CqVector3D)ddf).assignment( ((CqVector3D)ddf).add( (CqVector3D)dddf ));	
		}
		else if( f instanceof CqVector4D ) {
			res = (T) new CqVector4D( (CqVector4D)f );					// store current value
			((CqVector4D)f).assignment( ((CqVector4D)f).add( (CqVector4D)df ));	// go to next step
			((CqVector4D)df).assignment( ((CqVector4D)df).add( (CqVector4D)ddf )); 
			((CqVector4D)ddf).assignment( ((CqVector4D)ddf).add( (CqVector4D)dddf ));	
		}
		else if( f instanceof CqColor ) {
			res = (T) new CqColor( (CqColor)f );					// store current value
			((CqColor)f).assignment( ((CqColor)f).add( (CqColor)df ));	// go to next step
			((CqColor)df).assignment( ((CqColor)df).add( (CqColor)ddf )); 
			((CqColor)ddf).assignment( ((CqColor)ddf).add( (CqColor)dddf ));	
		}
		else if( f instanceof CqMatrix ) {
			res = (T) new CqMatrix( (CqMatrix)f );						// store current value
			((CqMatrix)f).assignment( ((CqMatrix)f).add( (CqMatrix)df ));	// go to next step
			((CqMatrix)df).assignment( ((CqMatrix)df).add( (CqMatrix)ddf )); 
			((CqMatrix)ddf).assignment( ((CqMatrix)ddf).add( (CqMatrix)dddf ));	
		}
		else if( f instanceof p_float ) {
			res = (T) new p_float( ((p_float)f).value );				// store current value
			((p_float)f).value = ((p_float)f).value + ((p_float)df).value;  // go to next step
			((p_float)df).value = ((p_float)df).value + ((p_float)ddf).value; 
			((p_float)ddf).value = ((p_float)ddf).value + ((p_float)dddf).value;	
		}
		else if( f instanceof p_int ) {
			res = (T) new p_int( ((p_int)f).value );				// store current value
			((p_int)f).value = ((p_int)f).value + ((p_int)df).value;  // go to next step
			((p_int)df).value = ((p_int)df).value + ((p_int)ddf).value; 
			((p_int)ddf).value = ((p_int)ddf).value + ((p_int)dddf).value;	
		}
		else if( f instanceof p_String ) {
			res = (T) new p_String( ((p_String)f).value );				// store current value
			((p_String)f).value = ((p_String)f).value + ((p_String)df).value;  // go to next step
			((p_String)df).value = ((p_String)df).value + ((p_String)ddf).value; 
			((p_String)ddf).value = ((p_String)ddf).value + ((p_String)dddf).value;	
		}
		else
			res = null;
		
//		T res = f;										// store current value
//		f = f + df; df = df + ddf; ddf = ddf + dddf;	// go to next step
		return ( res );									// return previous
	}
	
	/**
	 * 
	 * 指定クラスのインスタンスを生成する。
	 * 
	 * @param cls	インスタンス化したいクラス
	 * @return	指定クラスのインスタンス
	 */
	private T newInstance( Class<? extends T> cls )
	{
		try{
			return cls.newInstance();
			
		}catch (Exception e) {
			//インターフェースや抽象クラス以外でExceptionが発生した場合、エラー内容を表示。
			int mod = cls.getModifiers();
			if( !Modifier.isInterface(mod) && !Modifier.isAbstract(mod) ) HimawariLogger.outputException( e );
			
			//なんかエラーがあればnull
			return null;
		}
	}
	
}
