// 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 net.cellcomputing.himawari.library.RiGlobal.QGetRenderContext;
import static net.cellcomputing.himawari.library.RiGlobal.QGetRenderContextI;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.HashMap;

import net.cellcomputing.himawari.accessory.Valarray;
import net.cellcomputing.himawari.accessory.primitive.p_float;
import net.cellcomputing.himawari.display.ZFileRequest;
import net.cellcomputing.himawari.library.types.CqMatrix;
import net.cellcomputing.himawari.library.types.CqRandom;
import net.cellcomputing.himawari.library.types.CqVector3D;
import net.cellcomputing.himawari.util.HimawariLogger;

/**
 * @author NTT DATA Corporation
 *
 */
public strictfp class CqShadowMap extends CqTextureMap{

	//shadowmap.cppに定義されている定数
	static final int NumSamples = 16;
	static final int MinSamples = 3;
	
	static	int	m_rand_index=-1;			///< Static random number table index.
	static	float[]	m_aRand_no = new float[ 256 ];		///< Random no. table used for jittering the shadow sampling.
	
    float	m_bias;
    float m_bias0;
    float m_bias1;

    ArrayList<CqMatrix>	m_WorldToCameraMatrices = new ArrayList<CqMatrix>();		///< Matrix to convert points from world space to light space.
    ArrayList<CqMatrix>	m_WorldToScreenMatrices = new ArrayList<CqMatrix>();		///< Matrix to convert points from world space to screen space.
    ArrayList<CqMatrix>	m_ITTCameraToLightMatrices = new ArrayList<CqMatrix>();
    int	m_NumberOfMaps;
    

    static CqRandom rand;

    
	public CqShadowMap( final String strName ){
    	super(strName);
    	rand = new CqRandom();
        // Initialise the random table the first time it is needed.
        if( m_rand_index < 0 )
        {
            int i;
            for ( i = 0; i < 256; i++ )
                m_aRand_no[ i ] = ( rand.RandomFloat( 2.0f ) - 1.0f );
            m_rand_index = 0;
        }
    }
	
	public int	NumPages(){ return(m_NumberOfMaps); }
	
	public void PrepareSampleOptions(HashMap<String, IqShaderData> paramMap)
	{
	    super.PrepareSampleOptions( paramMap );

	    // Extend the shadow() call to accept bias, if set, override global bias
	    m_bias = 0.0f;
	    m_bias0 = 0.0f;
	    m_bias1 = 0.0f;

	    p_float temp_float = new p_float();
	    
	    if ( ( !paramMap.isEmpty() ) && ( paramMap.containsKey( "bias" ) ) )
	    {
	        paramMap.get( "bias" ).GetFloat( temp_float);
	        m_bias = temp_float.value;
	    }
	    else
	    {
	        // Add in the bias at this point in camera coordinates.
	        float[] poptBias = QGetRenderContextI().GetFloatOption( "shadow", "bias0" );
	        if ( poptBias != null )
	            m_bias0 = poptBias[ 0 ];

	        poptBias = QGetRenderContextI() .GetFloatOption( "shadow", "bias1" );
	        if ( poptBias != null )
	            m_bias1 = poptBias[ 0 ];

	        // Get bias, if set override bias0 and bias1
	        poptBias = QGetRenderContextI() .GetFloatOption( "shadow", "bias" );
	        if ( poptBias != null )
	            m_bias = poptBias[ 0 ];
	    }
	}
	
	
    public void destruct()
    {
    	super.destruct();
    }

    public	EqMapType	Type() 
    {
        return ( IsValid() ? new EqMapType(EqMapType.MapType_Shadow) : new EqMapType(EqMapType.MapType_Invalid) );
    }

    /** Get the matrix used to convert points from work into camera space.
     */
    public CqMatrix matWorldToCamera(int index)
    {
        assert( index < m_WorldToCameraMatrices.size() );
        return ( m_WorldToCameraMatrices.get(index) );
    }
    
    public void ReadMatrices()
    {
        
//        float	WToC;
//        float	WToS;
        CqMatrix	matWToC = new CqMatrix();
        CqMatrix 	matWToS = new CqMatrix();

        RandomAccessFile file = null;
        // Set the number of shadow maps initially to 0.
        m_NumberOfMaps = 0;

        CqMatrix matCToW = new CqMatrix(QGetRenderContextI() .matSpaceToSpace( "camera", "world", new CqMatrix(), new CqMatrix(), QGetRenderContextI().Time() ));
		//--追加実装 SearchPathに対応 2006/11/07
	    // Find the file required.
	    CqRiFile fileImage = new CqRiFile( m_strName, "texture" );
	    
	    if ( !fileImage.IsValid() )
	    {
	        HimawariLogger.getLogger().error("Cannot open texture file \"" + m_strName  +"\"\n");
	        return;
	    }
	    
	    String strRealName  =  fileImage.strRealName();
	    fileImage.Close();
		//--追加
	    
        //ファイルからカメラとスクリーンの位置を取得する
        try {
//			file = new RandomAccessFile(m_strName,"r");//追加実装 SearchPathに対応 2006/11/07
			file = new RandomAccessFile(strRealName,"r");
			String str;
			while((str = file.readLine())!= null)
			{
				float temp_f;
				if(str.equals(ZFileRequest.SHADOW_CAMERA_POINT)){
					matWToC = new CqMatrix();
					for(int i = 0; i < 4 ; i++){
						for(int j=0; j< 4 ;j++){
							temp_f = file.readFloat();
							matWToC.m_aaElement[i][j] = temp_f;
						}
					}
					continue;
				}
				
				if(str.equals(ZFileRequest.SHADOW_SCREEN_POINT)){
					matWToS = new CqMatrix();
					for(int i = 0; i < 4 ; i++){
						for(int j=0; j< 4 ;j++){
							temp_f = file.readFloat();
							matWToS.m_aaElement[i][j] =temp_f;
						}
					}
					continue;        		
				}

				if(str.equals(ZFileRequest.END_SHADOW_HEADER)){

			    // Set the matrixes to general, not Identity as default.
			    matWToC.SetfIdentity( false);
			    matWToS.SetfIdentity( false);

			    matWToC.assignMul(matCToW);
			    matWToS.assignMul(matCToW);

			    // Generate normal conversion matrices to save time.
			    CqMatrix matITTCToL = new CqMatrix(matWToC);
			    matITTCToL.m_aaElement[ 3 ][ 0 ] = 0f;
			    matITTCToL.m_aaElement[ 3 ][ 1 ] = 0f;
			    matITTCToL.m_aaElement[ 3 ][ 2 ] = 0f;
			    matITTCToL.m_aaElement[ 0 ][ 3 ] = 0f;
			    matITTCToL.m_aaElement[ 1 ][ 3 ] = 0f;
			    matITTCToL.m_aaElement[ 2 ][ 3 ] = 0f;
			    matITTCToL.m_aaElement[ 3 ][ 3 ] = 1f;
			    matITTCToL.Inverse();
			    matITTCToL.Transpose();

			    m_WorldToCameraMatrices.add( matWToC );
			    m_WorldToScreenMatrices.add( matWToS );
			    m_ITTCameraToLightMatrices.add( matITTCToL );

			    m_NumberOfMaps++;	// Increment the number of maps.

				file.skipBytes((int)(m_YRes * m_XRes *4));
				}
			}
		} catch (FileNotFoundException e) {
			HimawariLogger.getLogger().error("Cannot open texture file \""+ m_strName +"\"\n");
		} catch (IOException e) {
			HimawariLogger.outputException(e);
		}finally{
			try {
				if(file != null)
					file.close();
			} catch (IOException e) {
				HimawariLogger.outputException(e);
			}
		}
    }
    
    public CqMatrix matWorldToCamera(){
    	return matWorldToCamera(0);
    }
    
    /** Get the matrix used to convert points from work into screen space.
     */
    public CqMatrix matWorldToScreen(int index)
    {
        assert( index < m_WorldToScreenMatrices.size() );
        return ( m_WorldToScreenMatrices.get(index) );
    }

    public CqMatrix matWorldToScreen(){
    	return matWorldToScreen(0);
    }


    public void	AllocateMap( int XRes, int YRes ){
        for (CqTextureMapBuffer s : m_apSegments)
            s.destruct();

        m_XRes = XRes;
        m_YRes = YRes;
        m_apSegments.add( CreateBuffer( 0, 0, (long)m_XRes, (long)m_YRes, 1 ) );
    }
//    public float	Sample( final CqVector3D	vecPoint );
    public void	SaveZFile(){
    	
    }
    public void	LoadZFile(){
    	
    }
    public void	SaveShadowMap( final String strShadowName, boolean  append){
    	
    }
    public void	SaveShadowMap( final String strShadowName){
    	SaveShadowMap(strShadowName,false);
    }
    static CqRandom random = new CqRandom( 42 );
    
    public void SampleMap( CqVector3D vecPoint, CqVector3D swidth, CqVector3D twidth, Valarray val, int index, p_float average_depth, p_float shadow_depth )
    {
        if ( m_XRes != 0 )
        {
            SampleMap( vecPoint, vecPoint, vecPoint, vecPoint, val, index, average_depth, shadow_depth );
        }
        else
        {
            // If no map defined, not in shadow.
            val.resize( 1 );
            val.setValueAt( 0 , 0.0f);
        }
    }
    
    public	void	SampleMap( CqVector3D R1, CqVector3D R2, CqVector3D R3,CqVector3D R4, Valarray val, int index, p_float average_depth, p_float shadow_depth){
        // Check the memory and make sure we don't abuse it
        CriticalMeasure();

        QGetRenderContext().Stats().TextureMapTimer().Start();

        // If no map defined, not in shadow.
        val.resize( 1 );
        val.setValueAt(0  ,0.0f);

        CqVector3D	vecR1l = new CqVector3D();
        CqVector3D	vecR1m = new CqVector3D();
        CqVector3D	vecR2m = new CqVector3D();
        CqVector3D	vecR3m = new CqVector3D();
        CqVector3D	vecR4m = new CqVector3D();


        // If bias not set ( i.e. 0 ), try to use bias0 and bias1
        if ( !( m_bias > 0 ) )
        {
            if ( m_bias1 >= m_bias0 )
            {
                m_bias = random.RandomFloat( m_bias1 - m_bias0 ) + m_bias0;
                if ( m_bias > m_bias1 ) m_bias = m_bias1;
            }
            else
            {
                m_bias = random.RandomFloat( m_bias0 - m_bias1 ) + m_bias1;
                if ( m_bias > m_bias0 ) m_bias = m_bias0;
            }
        }

        CqVector3D vecBias = new CqVector3D( 0, 0, m_bias );
        // Generate a matrix to transform points from camera space into the space of the light source used in the
        // definition of the shadow map.
        CqMatrix matCameraToLight = new CqMatrix(matWorldToCamera( index ));
        // Generate a matrix to transform points from camera space into the space of the shadow map.
        CqMatrix matCameraToMap = new CqMatrix(matWorldToScreen( index ));

        R1.assignSub(vecBias);
        R2.assignSub(vecBias);
        R3.assignSub(vecBias);
        R4.assignSub(vecBias);
        vecR1l.assignment(matCameraToLight.multiply(( ( R1.add( R2).add( R3).add(R4) ).mul(0.25f) )));
        float z = vecR1l.z;

        vecR1m.assignment(matCameraToMap.multiply(R1));
        vecR2m.assignment(matCameraToMap.multiply(R2));
        vecR3m.assignment(matCameraToMap.multiply(R3));
        vecR4m.assignment(matCameraToMap.multiply(R4));


        float sbo2 = m_sblur * (m_XRes - 1) * 0.5f;
        float tbo2 = m_tblur * (m_YRes - 1) * 0.5f;

        // If point is behind light, call it not in shadow.
        //if(z1<0.0)	return;

        float xro2 = (m_XRes - 1) * 0.5f;
        float yro2 = (m_YRes - 1) * 0.5f;


        float s1 = vecR1m.x * xro2 + xro2 -m_pswidth/2.0f;
        float t1 = m_YRes - ( vecR1m.y * yro2 + yro2 ) - 1 - m_ptwidth/2.0f;
        float s2 = vecR2m.x * xro2 + xro2 + m_pswidth/2.0f;
        float t2 = m_YRes - ( vecR2m.y * yro2 + yro2 ) - 1 + m_ptwidth/2.0f;
        float s3 = vecR3m.x * xro2 + xro2 + m_pswidth/2.0f;
        float t3 = m_YRes - ( vecR3m.y * yro2 + yro2 ) - 1 - m_ptwidth/2.0f;
        float s4 = vecR4m.x * xro2 + xro2 - m_pswidth/2.0f;
        float t4 = m_YRes - ( vecR4m.y * yro2 + yro2 ) - 1 + m_ptwidth/2.0f;

        float smin = ( s1 < s2 ) ? s1 : ( s2 < s3 ) ? s2 : ( s3 < s4 ) ? s3 : s4;
        float smax = ( s1 > s2 ) ? s1 : ( s2 > s3 ) ? s2 : ( s3 > s4 ) ? s3 : s4;
        float tmin = ( t1 < t2 ) ? t1 : ( t2 < t3 ) ? t2 : ( t3 < t4 ) ? t3 : t4;
        float tmax = ( t1 > t2 ) ? t1 : ( t2 > t3 ) ? t2 : ( t3 > t4 ) ? t3 : t4;

        // Cull if outside bounding box.
        long lu = (int)( Math.floor( smin ) );
        long hu = (int)( Math.ceil( smax ) );
        long lv = (int)( Math.floor( tmin ) );
        long hv = (int)( Math.ceil ( tmax ) );

        lu -= (int)( sbo2 );
        lv -= (int)( tbo2 );
        hu += (int)( sbo2 );
        hv += (int)( tbo2 );

        if ( lu >= m_XRes || hu < 0 || lv >= m_YRes || hv < 0 )
            return ;

        lu = Math.max(0,lu);
        lv = Math.max(0,lv);
        hu = Math.min(m_XRes - 1,hu);
        hv = Math.min(m_YRes - 1,hv);

        float sres = hu - lu;
        float tres = hv - lv;

        // Calculate no. of samples.
        int nt, ns;
        if ( m_samples > 0 )
        {
            nt = ns = (int)( Math.ceil( Math.sqrt( m_samples ) ) );
        }
        else
        {
            if ( sres * tres * 4.0 < NumSamples )
            {
                ns = (int)( sres * 2.0 + 0.5 );
                ns = ( ns < MinSamples ? MinSamples : ( ns > NumSamples ? NumSamples : ns ) );
                nt = (int)( tres * 2.0 + 0.5 );
                nt = ( nt < MinSamples ? MinSamples : ( nt > NumSamples ? NumSamples : nt ) );
            }
            else
            {
                nt = (int)( Math.sqrt( tres * NumSamples / sres ) + 0.5f );
                nt = ( nt < MinSamples ? MinSamples : ( nt > NumSamples ? NumSamples : nt ) );
                ns = (int)( ( NumSamples ) / (float)nt + 0.5f );
                ns = ( ns < MinSamples ? MinSamples : ( ns > NumSamples ? NumSamples : ns ) );
            }
        }

        // Setup jitter variables
        float ds = sres / ns;
        float dt = tres / nt;
        float js = ds * 0.5f;
        float jt = dt * 0.5f;

        // Test the samples.
        int inshadow = 0;
        float avz = 0.0f;
        float sample_z = 0.0f; // How deep we're in the shadow

        float s = lu;
        int i;
        CqTextureMapBuffer  pTMBa = null;
        for ( i = 0; i < ns; i++ )
        {
            float t = lv;
            int j;
            for ( j = 0; j < nt; j++ )
            {
                // Jitter s and t
                m_rand_index = ( m_rand_index + 1 ) & 255;
                int iu = (int)( s + m_aRand_no[ m_rand_index ] * js );
                m_rand_index = ( m_rand_index + 1 ) & 255;
                int iv = (int)( t + m_aRand_no[ m_rand_index ] * jt );

                if( iu < 0 || iu > m_XRes || iv < 0 || iv > m_YRes )
                    continue;

                if( ( pTMBa == null )  || !pTMBa.IsValid( iu, iv, index ) )
                    pTMBa = GetBuffer( iu, iv, index );
                if ( pTMBa != null && pTMBa.pVoidBufferData() != null )
                {
                    iu -= pTMBa.sOrigin();
                    iv -= pTMBa.tOrigin();
                    float mapz = pTMBa.GetValue( iu, iv, 0 );
                    if ( z > mapz )
                    {
                        inshadow += 1;
                        sample_z = z - mapz;
                    }
                    avz += mapz;
                }
                t += dt;
            }
            s += ds;
        }

        if( null != average_depth )
        {
            avz /= ( ns * nt );
            average_depth.value = avz;
        }

        if( null != shadow_depth )
        {
            sample_z /= ( ns * nt );

            shadow_depth.value = sample_z;
        }

        val.setValueAt( 0 , ((float)( inshadow ) / ( ns * nt ) ));
               QGetRenderContext().Stats().TextureMapTimer().Stop();
    }

	/** Get a pointer to the cache buffer segment which contains the specifed sample point.
	 * 
	 * @param s  Horizontal sample position.
	 * @param t Vertical sample position.
	 * @param directory TIFF directory index.
	 * @param fProt A boolean value, true if the buffer should be protected from removal by the cache management system.
	 */
	
	public CqTextureMapBuffer GetBuffer( long s, long t, int directory, boolean fProt )
	{
		QGetRenderContext().Stats().IncTextureMisses( 4 );
		
		if ( m_apSegments.size() > 0 && m_apSegments.getFirst().IsValid( s, t, directory ) )
		{
			QGetRenderContext().Stats().IncTextureHits( 1, 4 );
			return( m_apSegments.getFirst() );
		}
		
		// Search already cached segments first.
		for ( CqTextureMapBuffer i : m_apSegments)
		{
			if ( ( i ).IsValid( s, t, directory ) )
			{
				QGetRenderContext().Stats().IncTextureHits( 1, 4 );
				// Move this segment to the top of the list, so that next time it is found first. This
				// allows us to take advantage of likely spatial coherence during shading.
				CqTextureMapBuffer pbuffer = i;
//				m_apSegments.erase(i);
				m_apSegments.remove(i);
				m_apSegments.addFirst(pbuffer);
				return ( pbuffer );
			}
		}
		
		// If we got here, segment is not currently loaded, so load the correct segement and store it in the cache.
		CqTextureMapBuffer pTMB = null;
		//--独自実装
			long tsx = m_XRes;
			long tsy = m_YRes;
			long ox = ( s / tsx ) * tsx;
			long oy = ( t / tsy ) * tsy;
			
			//--追加実装 SearchPathに対応 2006/11/07
		    // Find the file required.
		    CqRiFile fileImage = new CqRiFile( m_strName, "texture" );
		    
		    if ( !fileImage.IsValid() )
		    {
		        HimawariLogger.getLogger().error("Cannot open texture file \"" + m_strName  +"\"\n");
		        return null;
		    }
		    
		    String strRealName  =  fileImage.strRealName();
		    fileImage.Close();
			//--追加
			pTMB = CreateBuffer( ox, oy, tsx, tsy, directory, fProt );

			Float[] pdata = (Float[])pTMB.pVoidBufferData();
			//--カウンタを設置
			int count = 0;
			RandomAccessFile file = null;
			int directory_count = 0;
			try {
//				file = new RandomAccessFile(m_strName,"r"); //追加実装 SearchPathに対応 2006/11/07
				file = new RandomAccessFile(strRealName,"r");
					String str;
					//--目的のdirectoryまで探索する
					for(directory_count = 0; directory_count <directory;directory_count++){
						while((str = file.readLine()) != null){
							if(str.equals(ZFileRequest.END_SHADOW_HEADER))
								break;
						}
						//ヘッダの最後でなければエラー
						if(!str.equals(ZFileRequest.END_SHADOW_HEADER)){
							HimawariLogger.getLogger().error("File invalid:" + m_strName+"\n");
							return null;
						}
//						long temp_length = file.getFilePointer();
//						if(!(temp_length + m_YRes * m_XRes*4 > file.length()))
//							file.skipBytes((int)(m_YRes * m_XRes *4));
//						else
//							break;
						
					}

					while((str = file.readLine()) != null){
						if(str.equals(ZFileRequest.END_SHADOW_HEADER))
							break;
					}
					//ヘッダの最後でなければエラー
					if(!str.equals(ZFileRequest.END_SHADOW_HEADER)){
						HimawariLogger.getLogger().error("File invalid:" + m_strName+"\n");
						return null;
					}
					
					
//					for(int i = 0; i < m_YRes ;i++){
//						for(int j=0;j<m_XRes;j++){
//							float f = file.readFloat();
////							//--shadowmapから深度情報を取得する場所
//							pdata[count] = f;
//							count++;
//						}
//					}
					//--追加実装 10/31
					FloatBuffer fbuf;
					ByteBuffer buf = ByteBuffer.allocate(8192);
					
					long max = m_XRes * m_YRes;
					long read = 0;
					FileChannel channel = file.getChannel();

					while(read < max ){
						long tmpread = channel.read(buf);

						buf.flip();
						fbuf = buf.asFloatBuffer();
						tmpread = count;
						while(fbuf.position() < fbuf.limit()){
						
							pdata[count] = fbuf.get();
							count++;
						}
						tmpread = count - tmpread;
//						System.arraycopy(fbuf.array(),fbuf.position(),pdata,count,fbuf.limit() - fbuf.position());
//						count += fbuf.limit() - fbuf.position();
//						tmpread = fbuf.limit() - fbuf.position();
						read += tmpread;
						buf.clear();
					}
					
					m_apSegments.addFirst(pTMB);
				} catch (FileNotFoundException e) {
					HimawariLogger.outputException(e);
				} catch (IOException e) {
					HimawariLogger.outputException(e);
				}finally{
					try {
						if(file != null)
							file.close();
					} catch (IOException e) {
						HimawariLogger.outputException(e);
					}
				}
		return ( pTMB );
	}
    
    public 	CqTextureMapBuffer CreateBuffer( long xorigin, long yorigin, long width, long height, int directory, boolean fProt)
    {
        CqTextureMapBuffer pRes = new CqShadowMapBuffer();
        pRes.Init( xorigin, yorigin, width, height, m_SamplesPerPixel, directory, fProt );
        return( pRes );
    }
	
    public  CqMatrix GetMatrix( int which, int index)
    {
        if ( which == 0 ) return matWorldToCamera(index);
        else if ( which == 1 ) return matWorldToScreen(index);
        else if ( which == 2 ) return m_ITTCameraToLightMatrices.get(index);
        return ( matWorldToCamera(index) );
    }

}
