// 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 static java.lang.Math.max;
import static net.cellcomputing.himawari.library.RiGlobal.QGetRenderContext;
import static net.cellcomputing.himawari.library.RiGlobal.USES;
import static net.cellcomputing.himawari.library.EqEnvVars.*;
import static net.cellcomputing.himawari.library.CqStats.STATS_INC;
import static net.cellcomputing.himawari.library.EqIntIndex.GPR_points;
import static net.cellcomputing.himawari.library.EqVariableType.*;
import static net.cellcomputing.himawari.library.EqVariableClass.*;
import net.cellcomputing.himawari.accessory.STLArrayList;
import net.cellcomputing.himawari.accessory.STLVector;
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;

/**
 * 
 * Class encapsulating the functionality of Points geometry.
 * 
 * @author NTT DATA Corporation
 */
public strictfp class CqPoints extends CqSurfaceMotionImple {
	
	public CqPoints( int nvertices, CqPolygonPoints pPoints )
	{
		CqMotionSpec( pPoints );
		m_nVertices = nvertices;
		m_KDTreeData = new CqPointsKDTreeData( this );
		m_KDTree = new CqKDTree<Integer>( m_KDTreeData, Integer.class );
		m_MaxWidth = 0;
		
		//assert( null != pPoints );
		assert( nvertices > 0 );
		
		m_widthParamIndex = -1;
		m_constantwidthParamIndex = -1;
		
		if( pPoints!=null )
		{
			// Store the reference to our points.
			AddTimeSlot( 0, pPoints );
		}
		
		//下記の通り書き換えた。
//		std::vector<CqParameter*>::iterator iUP;
//		int index = 0;
//		for( iUP = pPoints.aUserParams().begin(); iUP != pPoints.aUserParams().end(); iUP++, index++ )
//		if( (*iUP).strName() == "constantwidth" && (*iUP).Type() == type_float && (*iUP).Class() == class_constant )
//		m_constantwidthParamIndex = index;
//		else if( (*iUP).strName() == "width" && (*iUP).Type() == type_float && (*iUP).Class() == class_varying )
//		m_widthParamIndex = index;
		
		int index = 0;
		for( CqParameter iUP : pPoints.aUserParams() ){
			if( iUP.strName().compareTo("constantwidth")==0 && iUP.Type().getValue()==type_float && iUP.Class().getValue()==class_constant ){
				m_constantwidthParamIndex = index;
			}
			else if( iUP.strName().compareTo("width")==0 && iUP.Type().getValue()==type_float && iUP.Class().getValue()==class_varying ){
				m_widthParamIndex = index;
			}
			index++;
		}
		
		STATS_INC( GPR_points );
	}
	
	
	public int cUniform()
	{
		return ( 1 );
	}
	public int cVarying()
	{
		return ( m_nVertices );
	}
	public int cVertex()
	{
		return ( m_nVertices );
	}
	public int cFaceVarying()
	{
		///TODO Must work out what this value should be.
		return ( m_nVertices );
	}
	
	// Overrides from CqSurface
	
	/** 
	 * Dice the quadric into a grid of MPGs for rendering.
	 */
	public CqMicroPolyGridBase Dice()
	{
	    assert( pPoints()!=null );

//	    STLVector<CqMicroPolyGrid> apGrids = new STLVector<CqMicroPolyGrid>(CqMicroPolyGrid.class);

	    CqMicroPolyGridPoints pGrid = new CqMicroPolyGridPoints( nVertices(), 1, this );

	    int lUses = Uses();

	    // Dice the primitive variables.
	    if ( USES( lUses, EnvVars_Cs ) && ( pGrid.pVar(EnvVars_Cs)!=null ) )
	    {
	        if ( pPoints().bHasVar(EnvVars_Cs) )
	            NaturalDice( pPoints().Cs(), nVertices(), 1, pGrid.pVar(EnvVars_Cs) );
	        else if ( null != pAttributes() .GetColorAttribute( "System", "Color" ) )
	            pGrid.pVar(EnvVars_Cs) .SetColor( pAttributes() .GetColorAttribute( "System", "Color" ) [ 0 ] );
	        else
	            pGrid.pVar(EnvVars_Cs) .SetColor( new CqColor( 1, 1, 1 ) );
	    }

	    if ( USES( lUses, EnvVars_Os ) && ( null != pGrid.pVar(EnvVars_Os) ) )
	    {
	        if ( pPoints().bHasVar(EnvVars_Os) )
	            NaturalDice( pPoints().Os(), nVertices(), 1, pGrid.pVar(EnvVars_Os) );
			else if ( null != pAttributes() .GetColorAttribute( "System", "Opacity" ) )
	            pGrid.pVar(EnvVars_Os) .SetColor( pAttributes() .GetColorAttribute( "System", "Opacity" ) [ 0 ] );
	        else
	            pGrid.pVar(EnvVars_Os) .SetColor( new CqColor( 1, 1, 1 ) );
	    }

	    if ( USES( lUses, EnvVars_s ) && ( null != pGrid.pVar(EnvVars_s) ) && pPoints().bHasVar(EnvVars_s) )
	        NaturalDice( pPoints().s(), nVertices(), 1, pGrid.pVar(EnvVars_s) );

	    if ( USES( lUses, EnvVars_t ) && ( null != pGrid.pVar(EnvVars_t) ) && pPoints().bHasVar(EnvVars_t) )
	        NaturalDice( pPoints().t(), nVertices(), 1, pGrid.pVar(EnvVars_t) );

	    if ( USES( lUses, EnvVars_u ) && ( null != pGrid.pVar(EnvVars_u) ) && pPoints().bHasVar(EnvVars_u) )
	        NaturalDice( pPoints().u(), nVertices(), 1, pGrid.pVar(EnvVars_u) );

	    if ( USES( lUses, EnvVars_v ) && ( null != pGrid.pVar(EnvVars_v) ) && pPoints().bHasVar(EnvVars_v) )
	        NaturalDice( pPoints().v(), nVertices(), 1, pGrid.pVar(EnvVars_v) );


	    if ( null != pGrid.pVar(EnvVars_P) )
	        NaturalDice( pPoints( 0 ).P(), nVertices(), 1, pGrid.pVar(EnvVars_P) );

	    // If the shaders need N and they have been explicitly specified, then bilinearly interpolate them.
	    if ( USES( lUses, EnvVars_N ) && ( null != pGrid.pVar(EnvVars_N) ) && pPoints().bHasVar(EnvVars_N) )
	    {
	        NaturalDice( pPoints().N(), nVertices(), 1, pGrid.pVar(EnvVars_N) );
	        pGrid.SetbShadingNormals( true );
	    }

	    if ( USES( lUses, EnvVars_Ng ) )
	    {
	        CqVector3D N = new CqVector3D(0,0,1);
	        //N = QGetRenderContext() .matSpaceToSpace( "camera", "object", CqMatrix(), pGrid.matObjectToWorld() ) * N;
	        int u;
	        for ( u = 0; u <= nVertices(); u++ )
	        {
				boolean CSO = pTransform().GetHandedness(pTransform().Time(0));
				boolean O = pAttributes() .GetIntegerAttribute( "System", "Orientation" ) [ 0 ] != 0;
	            N = ( O == CSO ) ? N : N.negative();
	            pGrid.pVar(EnvVars_Ng).SetNormal( N, u );
	        }
	        pGrid.SetbGeometricNormals( true );
	    }

	    // Now we need to dice the user specified parameters as appropriate.
//	    std::vector<CqParameter*>::iterator iUP;
//	    std::vector<CqParameter*>::iterator end = pPoints().aUserParams().end();
//	    for ( iUP = pPoints().aUserParams().begin(); iUP != end ; iUP++ )
	    for( CqParameter iUP : pPoints().aUserParams() )
	    {
	        /// TODO: Must transform point/vector/normal/matrix parameter variables from 'object' space to current before setting.
	        IqShader pShader;
			if ( (pShader = pGrid.pAttributes() .pshadSurface(QGetRenderContext().Time()))!=null )
	            pShader.SetArgument( iUP, this );

	        if ( (pShader = pGrid.pAttributes() .pshadDisplacement(QGetRenderContext().Time()))!=null )
	            pShader.SetArgument( iUP, this );

	        if ( (pShader = pGrid.pAttributes() .pshadAtmosphere(QGetRenderContext().Time()))!=null )
	            pShader.SetArgument( iUP, this );
	    }

	    return( pGrid );

	}
	
	/** 
	 * Determine whether the quadric is suitable for dicing.
	 */
	public boolean Diceable()
	{
	    int gridsize = 256;

	    final int[] poptGridSize = QGetRenderContext().optCurrent().GetIntegerOption( "limits", "gridsize" );

//	    int m_XBucketSize = 16;		//読み取られていない
//	    int m_YBucketSize = 16;
//
//	    final int[] poptBucketSize = QGetRenderContext().optCurrent().GetIntegerOption( "limits", "bucketsize" );
//	    if ( poptBucketSize != null )
//	    {
//	        m_XBucketSize = poptBucketSize[ 0 ];
//	        m_YBucketSize = poptBucketSize[ 1 ];
//	    }

	    if ( poptGridSize != null )
	        gridsize = (int) poptGridSize[ 0 ];

	    if( nVertices() > gridsize )
	        return ( false );
	    else
	        return ( true );
	}
	
	public void RenderComplete()
	{
		ClearKDTree();
		super.RenderComplete();
	}
	
	/* 
	 * Determine whether the passed surface is valid to be used as a
	 *  frame in motion blur for this surface.
	 */
	public boolean IsMotionBlurMatch( CqBasicSurface pSurf )
	{
		return( false );
	}
	
	/** 
	 * Get the geometric bound of this GPrim in 'current' space.
	 */
	public CqBound Bound()
	{
	    CqBound	B = new CqBound();

	    int i;

	    int t;
	    for ( t = 0; t < iTimes(); t++ )
	    {
	        CqPolygonPoints pTimePoints = pPoints( t );
	        for( i = 0; i < nVertices(); i++ )
	            B.Encapsulate( new CqVector3D( ((CqVector4D)pTimePoints.P().pValue_get( m_KDTree.aLeaves().get( i ) ,0 )) ) );
	    }

	    // Expand the bound to take into account the width of the particles.
	    B.vecMax().assignAdd( new CqVector3D( m_MaxWidth, m_MaxWidth, m_MaxWidth ) );
	    B.vecMin().assignSub( new CqVector3D( m_MaxWidth, m_MaxWidth, m_MaxWidth ) );

	    return ( AdjustBoundForTransformationMotion( B ) );
	}
	
	/** 
	 * Split this GPrim into bicubic patches.
	 */
	public int Split( STLVector< CqBasicSurface > aSplits )
	{
	    int median = nVertices()/2;
	    // Split the KDTree and create two new primitives containing the split points set.
	    CqPoints pA = new CqPoints( this );
	    CqPoints pB = new CqPoints( this );

	    pA.m_nVertices = median;
	    pB.m_nVertices = nVertices()-median;

	    pA.SetSurfaceParameters( this );
	    pB.SetSurfaceParameters( this );

	    KDTree().Subdivide( pA.KDTree(), pB.KDTree() );

	    aSplits.add( pA );
	    aSplits.add( pB );

	    return( 2 );
	}
	
	
	public int nVertices()
	{
		return ( m_nVertices );
	}
	
	public CqPolygonPoints pPoints( int TimeIndex )
	{
		return( GetMotionObject( Time( TimeIndex ) ) );
	}
	public final CqPolygonPoints pPoints(  )
	{
		return( GetMotionObject( Time( 0 ) ) );
	}
	
	/** 
	 * Get a reference to the user parameter variables array
	 */
	//nttdata 高速化
	public STLVector<CqParameter> aUserParams()
	{
		return ( pPoints().aUserParams() );
	}
	
	/** 
	 * Split the points, taking the split information from the specified donor points surfaces.
	 */
	public int CopySplit( STLVector< CqBasicSurface > aSplits, CqPoints pFrom1, CqPoints pFrom2 )
	{
	    // Split the KDTree and create two new primitives containing the split points set.
	    CqPoints pA = new CqPoints( this );
	    CqPoints pB = new CqPoints( this );

	    pA.m_nVertices = pFrom1.m_nVertices;
	    pB.m_nVertices = pFrom2.m_nVertices;

	    pA.SetSurfaceParameters( this );
	    pB.SetSurfaceParameters( this );

	    pA.KDTree().assignment( pFrom1.KDTree() );
	    pB.KDTree().assignment( pFrom2.KDTree() );

	    aSplits.add( pA );
	    aSplits.add( pB );

	    return( 2 );

	}
	
	/** 
	 * Returns a const reference to the "constantwidth" parameter, or
	 * null if the parameter is not present. 
	 */
	public CqParameterTypedConstant<p_float, p_float> constantwidth()
	{
		if ( m_constantwidthParamIndex >= 0 )
		{
			return ( CqParameterTypedConstant<p_float, p_float >) ( aUserParams().get( m_constantwidthParamIndex ) );
		}
		else
		{
			return ( null );
		}
	}
	
	/** 
	 * Returns a reference to the "width" parameter, or null if
	 * the parameter is not present. 
	 */
	public CqParameterTypedVarying<p_float, p_float> width( int iTime )
	{
		if ( m_widthParamIndex >= 0 )
		{
			return (CqParameterTypedVarying<p_float, p_float>) ( pPoints( iTime ).aUserParams().get( m_widthParamIndex ) );
		}
		else
		{
			return ( null );
		}
		
	}
	
	
	/// Accessor function for the KDTree
	public CqKDTree<Integer> KDTree()
	{
		return( m_KDTree );
	}
	
	public void ClearKDTree()
	{
		m_KDTreeData.FreePoints();
	}
	
	/** 
	 * Initialise the KDTree to contain all the points in the list. Settign the
	 * index list to the canonical form.
	 */
	public void InitialiseKDTree()
	{
//	    m_KDTree.aLeaves().reserve( nVertices() );
		m_KDTree.aLeaves().ensureCapacity( nVertices() );
	    int i;
	    for( i = 0; i < nVertices(); i++ )
	        m_KDTree.aLeaves().add( i );
	}
	
	public void InitialiseMaxWidth()
	{
	    int cu = nVertices();	// Only need cu, as we know cv is 1.

	    CqMatrix matObjectToCamera = QGetRenderContext() .matSpaceToSpace( "object", "camera", new CqMatrix(), pTransform().matObjectToWorld(pTransform().Time(0)), QGetRenderContext().Time() );
	    final CqParameterTypedConstant<p_float, p_float> pConstantWidthParam = constantwidth( );

	    int iu;
//	    int gsmin1;
//	    gsmin1 = cu - 1;

	    CqVector3D Point0 = matObjectToCamera .multiply( new CqVector3D(0,0,0) );

	    float i_radius = 1.0f;
	    if( null != pConstantWidthParam )
	        i_radius = ((p_float)pConstantWidthParam.pValue_get( 0 , 0 )).value;
	    for ( iu = 0; iu < cu; iu++ )
	    {
	        float radius;
	        // Find out if the "width" parameter was specified.
	        CqParameterTypedVarying<p_float, p_float> pWidthParam = width( 0 );

	        if( null != pWidthParam )
	            i_radius = ((p_float)pWidthParam.pValue_get( KDTree().aLeaves().get( iu ) , 0 )).value;

	        radius = i_radius;
	        // Get point in camera space.
	        CqVector3D Point1 = matObjectToCamera .multiply( new CqVector3D(radius,0,0) );
	        radius = (Point1.sub(Point0)).Magnitude();

	        m_MaxWidth = max(m_MaxWidth, radius );
	    }

	}
	
	
	@SuppressWarnings("unchecked")
	public void NaturalDice( CqParameter pParameter, int uDiceSize, int vDiceSize, IqShaderData pData )
	{
	    switch ( pParameter.Type().getValue() )
	    {
	    case type_float:
	        {
	            CqParameterTyped<p_float, p_float> pTParam = (CqParameterTyped<p_float, p_float>)( pParameter );
	            TypedNaturalDice( pTParam, pData );
	            break;
	        }

	    case type_integer:
	        {
	            CqParameterTyped<p_int, p_float> pTParam = (CqParameterTyped<p_int, p_float>)( pParameter );
	            TypedNaturalDice( pTParam, pData );
	            break;
	        }

	    case type_point:
	    case type_vector:
	    case type_normal:
	        {
	            CqParameterTyped<CqVector3D, CqVector3D> pTParam = (CqParameterTyped<CqVector3D, CqVector3D>)( pParameter );
	            TypedNaturalDice( pTParam, pData );
	            break;
	        }

	    case type_hpoint:
	        {
	            CqParameterTyped<CqVector4D, CqVector3D> pTParam = (CqParameterTyped<CqVector4D, CqVector3D>)( pParameter );
	            TypedNaturalDice( pTParam, pData );
	            break;
	        }

	    case type_color:
	        {
	            CqParameterTyped<CqColor, CqColor> pTParam = (CqParameterTyped<CqColor, CqColor>)( pParameter );
	            TypedNaturalDice( pTParam, pData );
	            break;
	        }

	    case type_string:
	        {
	            CqParameterTyped<p_String, p_String> pTParam = (CqParameterTyped<p_String, p_String>)( pParameter );
	            TypedNaturalDice( pTParam, pData );
	            break;
	        }

	    case type_matrix:
	        {
	            CqParameterTyped<CqMatrix, CqMatrix> pTParam = (CqParameterTyped<CqMatrix, CqMatrix>)( pParameter );
	            TypedNaturalDice( pTParam, pData );
	            break;
	        }

	    default:
	        {
	            // left blank to avoid compiler warnings about unhandled types
	            break;
	        }
	    }
	}
	
	// Overrides from CqMotionSpec
	public void ClearMotionObject( CqPolygonPoints A )
	{}
	
	public CqPolygonPoints ConcatMotionObjects( final CqPolygonPoints A, final CqPolygonPoints B )
	{
		return ( A );
	}
	public CqPolygonPoints LinearInterpolateMotionObjects( float Fraction, final CqPolygonPoints A, final CqPolygonPoints B )
	{
		return ( A );
	}
	
	protected <T ,SLT> void TypedNaturalDice( CqParameterTyped<T, SLT> pParam, IqShaderData pData )
	{
		int i;
//		for ( i = 0; i < nVertices(); i++ )
//			pData.SetValue( (SLT)( pParam.pValue()[ m_KDTree.aLeaves().get( i ) ] ), i );
		for ( i = 0; i < nVertices(); i++ ){
			pData.SetValue( ( pParam.pValue_get( 0 , m_KDTree.aLeaves().get( i ) ) ), i, pParam.TClass );
		}
	}
	
	
	// The copy constructor and assignment operator are private because
	// there's a slight "gotcha" in how to use them.  You need to call
	// InitialiseKDTree() on a CqPoints after constructing it.  Since
	// this is only used from within CqPoints::Split*(), we can safely
	// make this private.
	
	private CqPoints( final CqPoints From )
	{
		CqMotionSpec( From.pPoints() );
		m_KDTreeData = new CqPointsKDTreeData( this );
		m_KDTree = new CqKDTree<Integer>( m_KDTreeData, Integer.class );
		
		this.assignment( From );
	}
	
	/** 
	 * Assignment operator.
	 */
	private CqPoints assignment( final CqPoints From )
	{
	    ((CqSurface)this).assignment( From );
	    m_nVertices = From.m_nVertices;

	    m_KDTreeData.SetpPoints( this );

	    // Release the reference to our points.
	    int i;
	    for ( i = 0; i < From.iTimes(); i++ )
	    {
	    	CqPolygonPoints pTimePoints = From.GetMotionObject( From.Time( i ) );
	        AddTimeSlot( From.Time( i ), pTimePoints );
	    }

	    m_widthParamIndex = From.m_widthParamIndex;
	    m_constantwidthParamIndex = From.m_constantwidthParamIndex;
	    m_MaxWidth = From.m_MaxWidth;

	    return ( this );
	}
	
//	private CqPolygonPoints m_pPoints;    ///< Pointer to the surface storing the primtive variables.
	private int m_nVertices;     ///< Number of points this surfaces represents.
	private CqPointsKDTreeData m_KDTreeData;  ///< KD Tree data handling class.
	private CqKDTree<Integer>  m_KDTree;  ///< KD Tree node for this part of the entire primitive.
	private int m_widthParamIndex;     ///< Index of the "width" primitive variable if specified, -1 if not.
	private int m_constantwidthParamIndex;   ///< Index of the "constantwidth" primitive variable if specified, -1 if not.
	private float m_MaxWidth;      ///< Maximum width of the points, used for bound calculation.
	
	
	
}
