/*
 AbstractKeywordParser.java
 Copyright (C) 2003 Gerardo Horvilleur Martinez

 This program 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 program 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 program; 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.parser.keywords;

import static net.cellcomputing.himawari.library.EqVariableType.type_color;
import static net.cellcomputing.himawari.library.EqVariableType.type_float;
import static net.cellcomputing.himawari.library.EqVariableType.type_hpoint;
import static net.cellcomputing.himawari.library.EqVariableType.type_integer;
import static net.cellcomputing.himawari.library.EqVariableType.type_invalid;
import static net.cellcomputing.himawari.library.EqVariableType.type_matrix;
import static net.cellcomputing.himawari.library.EqVariableType.type_normal;
import static net.cellcomputing.himawari.library.EqVariableType.type_point;
import static net.cellcomputing.himawari.library.EqVariableType.type_string;
import static net.cellcomputing.himawari.library.EqVariableType.type_triple;
import static net.cellcomputing.himawari.library.EqVariableType.type_vector;
import static net.cellcomputing.himawari.parser.Global.LookupParameterTip;
import static net.cellcomputing.himawari.parser.Global.LookupParameterType;
import static net.cellcomputing.himawari.parser.Global.LookupParameterValue;

import java.io.IOException;
import java.io.StreamTokenizer;
import java.util.ArrayList;
import java.util.List;

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.parser.Parser;
import net.cellcomputing.himawari.parser.Tokenizer;
import net.cellcomputing.himawari.util.HimawariLogger;

/**
 * ribファイルを解析するクラス
 * 
 * 
 * @author NTT DATA Corporation
 */
public abstract strictfp class AbstractKeywordParser implements KeywordParser {
	
	public final static int TK_KEYWORD = StreamTokenizer.TT_WORD;
	public final static int TK_NUMBER = StreamTokenizer.TT_NUMBER;
	public final static int TK_EOF = StreamTokenizer.TT_EOF;
	public final static int TK_EOL = StreamTokenizer.TT_EOL;
	public final static int TK_STRING = '"';
	public final static int TK_LBRACE = '[';
	public final static int TK_RBRACE = ']';

    private static STLVector<Object> numbers = new STLVector<Object>( Object.class );
    private static STLVector<String> strings = new STLVector<String>( String.class );
    private static int arraySize; 
    private int elemSize;
    protected Parser parser;
    HimawariLogger logger = HimawariLogger.getLogger();

    /* (non-Javadoc)
     * @see net.cellcomputing.himawari.parser.keywords.KeywordParser#setParser(net.cellcomputing.himawari.parser.Parser)
     */
    public void setParser(Parser parser) {
        this.parser = parser;
    }

    /**
     * トークナイザーで解析された属性を返す。
     * 
     * 
     * @param token　トークン
     * @return result トークン属性の文字列
     */
    private String tokenToString(int token) {
        String result;
        switch (token) {
            case TK_KEYWORD :
                result = "keyword";
                break;
            case TK_NUMBER :
                result = "number";
                break;
            case TK_EOF :
                result = "EOF";
                break;
            case TK_EOL :
                result = "EOL";
                break;
            case TK_STRING :
                result = "string";
                break;
            default :
                result = "" + (char) token;
                break;
        }
        return result;
    }

	
	/**
	 * パラメータタイプを文字列として返す。
	 * 
	 * @param type	パラメータタイプを示す番号
	 * @return	result	文字列
	 */
	private static String typeToString( int type )
	{
		String result;
		switch (type) {
		case type_invalid :
			result = "unknown";
			break;
		case type_float :
			result = "float";
			break;
		case type_integer :
			result = "integer";
			break;
		case type_point :
			result = "point";
			break;
		case type_string :
			result = "string";
			break;
		case type_color :
			result = "color";
			break;
		case type_triple :
			result = "triple";
			break;
		case type_hpoint :
			result = "hpoint";
			break;
		case type_normal :
			result = "normal";
			break;
		case type_vector :
			result = "vector";
			break;
		case type_matrix :
			result = "matrix";
			break;
		default :
			result = "unknown";
		break;
		}
		return result;
	}
	
	/**
	 * パラメータタイプを文字列として返す。
	 * 
	 * @param type	パラメータタイプを示す番号
	 * @return	result	文字列
	 */
	public static String paramToString( Class myclass )
	{
		if( myclass == p_float[].class )	return "Float";
		if( myclass == p_String[].class )	return "String";
		
		return "unknown";
	}
	
    /**
     * 解析するトークンが予期したものと一致するかどうかを判定。
     * 予期しない場合、Exceptionをスローする。
     * 
     * @param st	トークン
     * @param expected	トークン属性値
     * @throws Exception	
     */
    protected void match(Tokenizer st, int expected) throws Exception
    {
    	int found = st.nextToken();
    	if (found != expected)
    	{
    		if(found == TK_KEYWORD)
    			st.pushBack();
    		throw new Exception(
    				"\nParsing error.\n"
    				+ "Expected: "
    				+ tokenToString(expected)
    				+ "\n"
    				+ "Found: "
    				+ tokenToString(found));
    	}
    }
    
    /**
	 * パラメータリストで使用されるシェーダ変数のタイプが
	 * 定義された型と一致するかどうかを判定。
	 * 予期しないものの場合、ログエラーを出す。
	 * 
	 * @param name	トークン名ベクタ
	 * @param numbers	トークン値ベクタ
	 * @param count	count	最後に解析したトークン値の要素数
	 * @return	boolean	パラメータタイプの判定
	 */
	public boolean MatchType( int token )
	{
		String name = tokens()[arraySize - 1];
		int type = LookupParameterType( name ).getValue();
		
		//Error : empty parameter
		if( values() == null || countElem() == 0 ){
			logger.error("Not Exist Parameter["+name+"], ExpectedType["+typeToString( type )+"]\n");
			return false ;
		}
		Object value = values()[arraySize-1];
		Class myclass = value.getClass();
		
		//Error : unknown parameter type
		if( type == type_invalid ){
			logger.error("Undeclared! "+paramToString( myclass )+" Parameter["+name+"]\n");
			return false;
		}
		
		//Error : mismatch parameter type 
		int apex = LookupParameterValue( name );
		boolean tip = LookupParameterTip( name ) ;
		
		if( myclass == p_String[].class ){
			if( type == type_string )
			{
				if(((p_String[])value ).length == apex )
					return true ;
				else if( tip && (( p_String[])value).length > apex && ((p_String[])value).length % apex == 0 )
					return true ;
			}
		}
		else {
			if((( p_float[])value).length == apex ) 
				return true ;
			else if( tip && (( p_float[])value).length > apex && (( p_float[])value).length % apex == 0 )
				return true ;
		}
		
		// token == "Pz"
		if( name.equals( "Pz" ) )	return true;
		
		logger.error( paramToString( myclass )+ " Parameter Type Mismatch["+name+"], ExpectedType["+typeToString(type)+"]\n" );
		return false;
	}
    
    /**
     * バウンドの設定値を取得
     * 
     * 
     * @param st	トークン
     * @return	取得したバウンド値
     * @throws IOException
     * @throws Exception
     */
     protected float[] readBounds(Tokenizer st) throws IOException, Exception {
        
        boolean array = false;
        int token = st.nextToken();
        // Check por array
        if (token == TK_LBRACE)
            array = true;
        else
            st.pushBack();
        // Expect x min
        match(st, TK_NUMBER);
        float xMin = (float) st.nval;
        // Expect x max
        match(st, TK_NUMBER);
        float xMax = (float) st.nval;
        // Expect y min
        match(st, TK_NUMBER);
        float yMin = (float) st.nval;
        // Expect y max
        match(st, TK_NUMBER);
        float yMax = (float) st.nval;
        // Expect z min
        match(st, TK_NUMBER);
        float zMin = (float) st.nval;
        // Expect z max
        match(st, TK_NUMBER);
        float zMax = (float) st.nval;
        if (array)
            match(st, TK_RBRACE);
        
        float[] bounds = { xMin, xMax, yMin, yMax, zMin, zMax };
        return bounds;
     }


    /**
     * マトリクス設定値を取得
     * 
     * 
     * @param st	トークン	
     * @return	transform　マトリクス値
     * @throws Exception
     */
     protected float[][] readMatrix(Tokenizer st) throws Exception {
 
    	// Expect array start
        match(st, TK_LBRACE);
        // Expect a
        match(st, TK_NUMBER);
        float a = (float) st.nval;
        // Expect b
        match(st, TK_NUMBER);
        float b = (float) st.nval;
        // Expect c
        match(st, TK_NUMBER);
        float c = (float) st.nval;
        // Expect d
        match(st, TK_NUMBER);
        float d = (float) st.nval;
        // Expect e
        match(st, TK_NUMBER);
        float e = (float) st.nval;
        // Expect f
        match(st, TK_NUMBER);
        float f = (float) st.nval;
        // Expect g
        match(st, TK_NUMBER);
        float g = (float) st.nval;
        // Expect h
        match(st, TK_NUMBER);
        float h = (float) st.nval;
        // Expect i
        match(st, TK_NUMBER);
        float i = (float) st.nval;
        // Expect j
        match(st, TK_NUMBER);
        float j = (float) st.nval;
        // Expect k
        match(st, TK_NUMBER);
        float k = (float) st.nval;
        // Expect l
        match(st, TK_NUMBER);
        float l = (float) st.nval;
        // Expect m
        match(st, TK_NUMBER);
        float m = (float) st.nval;
        // Expect n
        match(st, TK_NUMBER);
        float n = (float) st.nval;
        // Expect o
        match(st, TK_NUMBER);
        float o = (float) st.nval;
        // Expect p
        match(st, TK_NUMBER);
        float p = (float) st.nval;
        // Expect array end
        match(st, TK_RBRACE);
        // Renderman uses row vectors and Java3D uses column vectors....
        
        float[][] transform = { { a, b, c, d},
        						{ e, f, g, h},
        						{ i, j, k, l},
        						{ m, n, o, p}
        						};
        return transform;
    }

    /**
     * カラー設定値を取得
     * 
     * 
     * @param st	トークン
     * @return	color　色情報
     * @throws IOException
     * @throws Exception
     */
    protected float[] parseColor(Tokenizer st) throws IOException, Exception {

    	boolean array = false;
        int token = st.nextToken();
        // Check por array
        if (token == TK_LBRACE)
            array = true;
        else
            st.pushBack();
        // Expect red
        match(st, TK_NUMBER);
        float red = (float) st.nval;
        // Expect green
        match(st, TK_NUMBER);
        float green = (float) st.nval;
        // Expect blue
        match(st, TK_NUMBER);
        float blue = (float) st.nval;
        
        if (array)
            match(st, TK_RBRACE);
        
        float[] color = { red, green, blue };
        return color;
    }

//    public Set getValidStates() {
//        return validStates;
//    }

    /**
     * float型の要素をもつ配列を解析。
     * その他はエラーとする。
     * 
     * @param st	トークン
     * @return	result　解析結果
     * @throws Exception
     * @throws IOException
     */
    protected float[] readFloatArray(Tokenizer st) throws Exception, IOException {
    	
        List<Float> floats = new ArrayList<Float>();
        
        // Expect array start
        match(st, TK_LBRACE);
        
        // Expect a variable length sequence of numbers
        while (st.nextToken() == TK_NUMBER)
            floats.add((float)st.nval);
        st.pushBack();
        
        float[] result = new float[floats.size()];
        for (int i = 0; i < floats.size(); i++)
            result[i] = ((Float) floats.get(i)).floatValue();
        
        // Expect array end
        match(st, TK_RBRACE);
        return result;
    }
    

    /**
     * 配列解析用メソッド。
     * パラメータリストの配列解析にも使用される。
     * 
     * @param st	トークン
     * @throws Exception	IOException
     */
    protected void parseArray(Tokenizer st) throws Exception {
        
        // Expect array start
        match(st, TK_LBRACE);
        
        // Expect a variable length sequence of numbers or strings
        elemSize = 0;
    	int token = 0;
    	STLVector<p_float> tmpNum = new STLVector<p_float>(p_float.class);
    	STLVector<p_String> tmpStr = new STLVector<p_String>(p_String.class);
    	
    	while ((token = st.nextToken()) == TK_NUMBER || token == TK_STRING )
    	{
    		switch (token)
    		{
    		case TK_NUMBER :
    			tmpNum.add( new p_float( (float)st.nval ));
    			elemSize++ ;
    			break;
    		case TK_STRING :
    			if( st.sval.length() == 0 )
    				break;
    			tmpStr.add( new p_String( st.sval ));
    			elemSize++ ; 
    			break;
    		}
    	}
    	
    	if( tmpNum.size() > 0 ){	
    		numbers.add( tmpNum.toArray() );
    		arraySize++;
    	}
    	else{
    		numbers.add( tmpStr.toArray() );
    		arraySize++;
    	}
        st.pushBack();
        
        // Expect array end
        match(st, TK_RBRACE);
        
    }
    
    /**
     * パラメーターリストの解析を行う。 
     * 
     * 
     * @param st	取得トークン
     * @throws Exception	IOException
     */
    protected void parseParameterList(Tokenizer st) throws Exception 
    {
    	clearParameterList();
    	
    	int token = 0;
    	while ( (token=st.nextToken()) == TK_STRING || (token != TK_KEYWORD && token != TK_EOF )) 
    	{
    		if( st.sval == null )  
    			continue;
    		
    		strings.add( st.sval );
    		token = st.nextToken();
    		
    		switch( token )
    		{
    		case TK_LBRACE :
    			st.pushBack();
    			parseArray(st);
    			break;
    		case TK_NUMBER :
    			numbers.add( new p_float[]{ new p_float( (float)st.nval )} );
    			arraySize++;
    			elemSize = 1;
    			break;
    		case TK_STRING :
    			if( st.sval.length() == 0 )
    				break;
    			numbers.add( new p_String[]{ new p_String( st.sval )} );
    			arraySize++;
    			elemSize = 1;
    			break;
    		default :
    			strings.remove( arraySize );
    			st.pushBack();
    		return ;
    		}
    		
    		if( !MatchType( token ) ){
				strings.remove(arraySize-1);
				numbers.remove(arraySize-1);
				arraySize--;
			}
    	 }
    	st.pushBack();
    }
    
    
    /**
     * パラメータリストに含まれる"P"の要素数をカウント<br>
     * 三次元座標であることが条件で、3の倍数に満たないものは切り捨てする。<br>
     * "P"の要素数 = ポリゴンの頂点数
     * 
     * @param name	パラメータリスト・トークン名配列
     * @param values	パラメータリスト・トークン値配列
     * @return	"P"が示す頂点数の数
     */
    protected int getVarying( String[] name, Object[] values ) throws Exception{
    	
    	int length = 0;
    	for( int i = 0; i < count(); i++ ){
    		if( name[i].equals("P") ){
    			length = ((p_float[])values[i]).length / 3;
    		}
    		else if( name[i].equals("Pw") ){
    			length = ((p_float[])values[i]).length / 4;
    		}
    	}
    	
    	if( length < 1 )
    		throw new Exception("Expecting P / Pw \n");
    		
    	return length;
    }
    
    
    /**
     * parseArrayで解析した配列を　int[] に変更。
     * 
     * 
     * @return　res int[]に変換したnumbers[0](メンバ変数)。
     */
    protected  int[] toInteger(){
    	
    	Object[] tmp = values();
    	int[] res = new int[ countElem() ];
    	int index = count() - 1;
    	
    	for( int i = 0; i < countElem(); i++ )
    		res[ i ] = (int)((p_float[])tmp[ index ])[ i ].value;
    	
    	return res;
    }
    
    
    /**
     * parseArrayで解析した配列を　float[] に変更。
     * 
     * 
     * @return	res float[]に変換したnumbers[0](メンバ変数)。
     */
    protected float[] toFloat(){
    	
    	Object[] tmp = values();
    	float[] res = new float[ countElem() ];
    	int index = count() - 1;
    	
    	for( int i = 0; i < countElem(); i++ )
    		res[ i ] = ((p_float[])tmp[ index ])[ i ].value;
    	
    	return res;
    }
    
    /**
     * parseArrayで解析した配列を　String[] に変更。
     * 
     * 
     * @return	res String[]に変換したnumbers[0](メンバ変数)。
     */
    protected String[] toStringArray(){
    	
    	Object[] tmp = values();
    	String[] res = new String[ countElem() ];
    	int index = count() - 1;
    	
    	for( int i = 0; i < countElem(); i++ )
    		res[ i ] = ((p_String[])tmp[ index ])[ i ].value;
    	
    	return res;
    }
    
    //protected void clearParameterList()
    public void clearParameterList()
    {
    	numbers.clear();
    	strings.clear();
    	arraySize = 0;
    }
    
    
    // for Aqsis's PARAMETERLIST.
    public int count(){
    	return arraySize;
    }
    public String[] tokens(){
    	return strings.toArray();
    }
    public Object[] values(){
    	return numbers.toArray();
    }
    public int countElem(){
    	return elemSize;
    }
    
}
