/*
 * $RCSfile: ModuleSpec.java,v $
 * $Revision: 1.1 $
 * $Date: 2005/02/11 05:01:58 $
 * $State: Exp $
 *
 * Class:                   ModuleSpec
 *
 * Description:             Generic class for storing module specs
 *
 *                           from WTFilterSpec (Diego Santa Cruz)
 *
 * COPYRIGHT:
 *
 * This software module was originally developed by Raphaël Grosbois and
 * Diego Santa Cruz (Swiss Federal Institute of Technology-EPFL); Joel
 * Askelöf (Ericsson Radio Systems AB); and Bertrand Berthelot, David
 * Bouchard, Félix Henry, Gerard Mozelle and Patrice Onno (Canon Research
 * Centre France S.A) in the course of development of the JPEG2000
 * standard as specified by ISO/IEC 15444 (JPEG 2000 Standard). This
 * software module is an implementation of a part of the JPEG 2000
 * Standard. Swiss Federal Institute of Technology-EPFL, Ericsson Radio
 * Systems AB and Canon Research Centre France S.A (collectively JJ2000
 * Partners) agree not to assert against ISO/IEC and users of the JPEG
 * 2000 Standard (Users) any of their rights under the copyright, not
 * including other intellectual property rights, for this software module
 * with respect to the usage by ISO/IEC and Users of this software module
 * or modifications thereof for use in hardware or software products
 * claiming conformance to the JPEG 2000 Standard. Those intending to use
 * this software module in hardware or software products are advised that
 * their use may infringe existing patents. The original developers of
 * this software module, JJ2000 Partners and ISO/IEC assume no liability
 * for use of this software module or modifications thereof. No license
 * or right to this software module is granted for non JPEG 2000 Standard
 * conforming products. JJ2000 Partners have full right to use this
 * software module for his/her own purpose, assign or donate this
 * software module to any third party and to inhibit third parties from
 * using this software module for non JPEG 2000 Standard conforming
 * products. This copyright notice must be included in all copies or
 * derivative works of this software module.
 *
 * Copyright (c) 1999/2000 JJ2000 Partners.
 * */
package jj2000.j2k;

import java.awt.Point;
import java.util.Enumeration;
import java.util.Hashtable;

/**
 * This generic class is used to handle values to be used by a module for each
 * tile and component.  It uses attribute to determine which value to use. It
 * should be extended by each module needing this feature.
 *
 * This class might be used for values that are only tile specific or
 * component specific but not both.
 *
 * <P>The attributes to use are defined by a hierarchy. The hierarchy is:
 *
 * <ul>
 * <li> Tile and component specific attribute</li>
 * <li> Tile specific default attribute</li>
 * <li> Component main default attribute</li>
 * <li> Main default attribute</li>
 * </ul>
 * */

public class ModuleSpec implements Cloneable {

    /** The identifier for a specification module that applies only to
     * components */
    public final static byte SPEC_TYPE_COMP = 0;

    /** The identifier for a specification module that applies only to
        tiles */
    public final static byte SPEC_TYPE_TILE = 1;

    /** The identifier for a specification module that applies both to
     * tiles and components */
    public final static byte SPEC_TYPE_TILE_COMP = 2;

    /** The identifier for default specification */
    public final static byte SPEC_DEF = 0;

    /** The identifier for "component default" specification */
    public final static byte SPEC_COMP_DEF = 1;

    /** The identifier for "tile default" specification */
    public final static byte SPEC_TILE_DEF = 2;

    /** The identifier for a "tile-component" specification */
    public final static byte SPEC_TILE_COMP = 3;

    /** The type of the specification module */
    protected int specType;

    /** The number of tiles */
    protected int nTiles = 0;

    /** The number of components */
    protected int nComp = 0;

    /** The spec type for each tile-component. The first index is
     * the tile index, the second is the component index.
     */
    protected byte[][] specValType;

    /** Default value for each tile-component */
    protected Object def = null;

    /** The default value for each component. Null if no component
        specific value is defined */
    protected Object[] compDef = null;

    /** The default value for each tile. Null if no tile specific
        value is defined */
    protected Object[] tileDef = null;

    /** The specific value for each tile-component. Value of tile 16 component
     * 3 is accessible through the hash value "t16c3". Null if no
     * tile-component specific value is defined */
    protected Hashtable tileCompVal;

    /** The specified value in string format */
    protected String specified;

    public ModuleSpec getCopy() {
        return (ModuleSpec)this.clone();
    }

    /**
     * Constructs a 'ModuleSpec' object, initializing all the components and
     * tiles to the 'SPEC_DEF' spec val type, for the specified number of
     * components and tiles.
     *
     * @param nt The number of tiles
     *
     * @param nc The number of components
     *
     * @param type the type of the specification module i.e. tile specific,
     * component specific or both.
     * */
    public ModuleSpec(int nt, int nc, byte type) {

	nTiles = nt;
	nComp = nc;
        specValType = new byte[nt][nc];
        switch (type) {
        case SPEC_TYPE_TILE:
            specType = SPEC_TYPE_TILE;
            break;
        case SPEC_TYPE_COMP:
            specType = SPEC_TYPE_COMP;
            break;
        case SPEC_TYPE_TILE_COMP:
            specType = SPEC_TYPE_TILE_COMP;
            break;
        }
    }

    protected Object clone() {
        ModuleSpec ms;
        try {
            ms = (ModuleSpec)super.clone();
        } catch(CloneNotSupportedException e) {
            throw new Error("Error when cloning ModuleSpec instance");
        }
        // Create a copy of the specValType array
        ms.specValType = new byte[nTiles][nComp];
        for(int t=0; t<nTiles; t++) {
            for(int c=0; c<nComp; c++) {
                ms.specValType[t][c] = specValType[t][c];
            }
        }
        // Create a copy of tileDef
        if(tileDef!=null) {
            ms.tileDef = new Object[nTiles];
            for(int t=0; t<nTiles; t++) {
                ms.tileDef[t] = tileDef[t];
            }
        }
        // Create a copy of tileCompVal
        if(tileCompVal!=null) {
            ms.tileCompVal = new Hashtable();
            String tmpKey;
            Object tmpVal;
            for(Enumeration e=tileCompVal.keys(); e.hasMoreElements(); ) {
                tmpKey = (String)e.nextElement();
                tmpVal = tileCompVal.get(tmpKey);
                ms.tileCompVal.put(tmpKey,tmpVal);
            }
        }
        return ms;
    }

    /** 
     * Rotate the ModuleSpec instance by 90 degrees (this modifies only tile
     * and tile-component specifications).
     *
     * @param nT Number of tiles along horizontal and vertical axis after
     * rotation. 
     * */
    public void rotate90(Point anT) {
        // Rotate specValType
        byte[][] tmpsvt = new byte[nTiles][];
        int ax,ay;
        Point bnT = new Point(anT.y,anT.x);
        for(int by=0; by<bnT.y; by++) {
            for(int bx=0; bx<bnT.x; bx++) {
                ay = bx;
                ax = bnT.y-by-1;
                tmpsvt[ay*anT.x+ax] = specValType[by*bnT.x+bx];
            }
        }
        specValType = tmpsvt;

        // Rotate tileDef
        if(tileDef!=null) {
            Object[] tmptd = new Object[nTiles];
            for(int by=0; by<bnT.y; by++) {
                for(int bx=0; bx<bnT.x; bx++) {
                    ay = bx;
                    ax = bnT.y-by-1;
                    tmptd[ay*anT.x+ax] = tileDef[by*bnT.x+bx];
                }
            }
            tileDef = tmptd;
        }

        // Rotate tileCompVal
        if(tileCompVal!=null && tileCompVal.size()>0) {
            Hashtable tmptcv = new Hashtable();
            String tmpKey;
            Object tmpVal;
            int btIdx,atIdx;
            int i1,i2;
            int bx,by;
            for(Enumeration e=tileCompVal.keys(); e.hasMoreElements(); ) {
                tmpKey = (String)e.nextElement();
                tmpVal = tileCompVal.get(tmpKey);
                i1 = tmpKey.indexOf('t');
                i2 = tmpKey.indexOf('c');
                btIdx = (new Integer(tmpKey.substring(i1+1,i2))).intValue();
                bx = btIdx%bnT.x;
                by = btIdx/bnT.x;
                ay = bx;
                ax = bnT.y-by-1;
                atIdx = ax+ay*anT.x;
                tmptcv.put("t"+atIdx+tmpKey.substring(i2),tmpVal);
            }
            tileCompVal = tmptcv;
        }
    }

    /**
     * Sets default value for this module
     * */
    public void setDefault(Object value){
	def = value;
    }

    /**
     * Gets default value for this module.
     *
     * @return The default value (Must be casted before use)
     * */
    public Object getDefault(){
	return def;
    }

    /**
     * Sets default value for specified component and specValType tag if
     * allowed by its priority.
     *
     * @param c Component index
     * */
    public void setCompDef(int c, Object value){
        if ( specType == SPEC_TYPE_TILE ) {
            String errMsg = "Option whose value is '"+value+"' cannot be "
                +"specified for components as it is a 'tile only' specific "
                +"option";
            throw new Error(errMsg);
        }
	if(compDef==null)
	    compDef = new Object[nComp];
	for(int i=0; i<nTiles; i++){
	    if(specValType[i][c]<SPEC_COMP_DEF) {
		specValType[i][c] = SPEC_COMP_DEF;
            }
	}
	compDef[c] = value;
    }

    /**
     * Gets default value of the specified component. If no specification have
     * been entered for this component, returns default value.
     *
     * @param c Component index
     *
     * @return The default value for this component (Must be casted before
     * use)
     *
     * @see #setCompDef
     * */
    public Object getCompDef(int c){
        if ( specType == SPEC_TYPE_TILE ) {
            throw new Error("Illegal use of ModuleSpec class");
        }
	if(compDef==null || compDef[c]==null){
	    return getDefault();
	}
	else
	    return compDef[c];
    }

    /**
     * Sets default value for specified tile and specValType tag if
     * allowed by its priority.
     *
     * @param c Tile index.
     * */
    public void setTileDef(int t, Object value){
        if ( specType == SPEC_TYPE_COMP ) {
            String errMsg = "Option whose value is '"+value+"' cannot be "
                + "specified for tiles as it is a 'component only' specific "
                + "option";
            throw new Error(errMsg);
        }
	if(tileDef==null)
	    tileDef = new Object[nTiles];
	for(int i=0; i<nComp; i++){
	    if(specValType[t][i]<SPEC_TILE_DEF){
		specValType[t][i] = SPEC_TILE_DEF;
	    }
	}
	tileDef[t] = value;
    }

    /**
     * Gets default value of the specified tile. If no specification
     * has been entered, it returns the default value.
     *
     * @param t Tile index
     *
     * @return The default value for this tile (Must be casted before use)
     *
     * @see #setTileDef
     * */
    public Object getTileDef(int t){
        if ( specType == SPEC_TYPE_COMP ) {
            throw new Error("Illegal use of ModuleSpec class");
        }
	if(tileDef==null || tileDef[t]==null){
	    return getDefault();
	}
	else
	    return tileDef[t];
    }

    /**
     * Sets value for specified tile-component.
     *
     * @param t Tie index
     *
     * @param c Component index
     * */
    public void setTileCompVal(int t,int c, Object value){
        if ( specType != SPEC_TYPE_TILE_COMP ) {
            String errMsg = "Option whose value is '"+value+"' cannot be "
                + "specified for ";
            switch (specType) {
            case SPEC_TYPE_TILE:
                errMsg += "components as it is a 'tile only' specific option";
                break;
            case SPEC_TYPE_COMP:
                errMsg += "tiles as it is a 'component only' specific option";
                break;
            }
            throw new Error(errMsg);
        }
	if(tileCompVal==null)
	    tileCompVal = new Hashtable();
	specValType[t][c] = SPEC_TILE_COMP;
	tileCompVal.put("t"+t+"c"+c,value);
    }

    /**
     * Gets value of specified tile-component. This method calls getSpec but
     * has a public access.
     *
     * @param t Tile index
     *
     * @param c Component index
     *
     * @return The value of this tile-component (Must be casted before use)
     *
     * @see #setTileCompVal
     *
     * @see #getSpec
     * */
    public Object getTileCompVal(int t,int c){
        if ( specType != SPEC_TYPE_TILE_COMP ) {
            throw new Error("Illegal use of ModuleSpec class");
        }
	return getSpec(t,c);
    }

    /**
     * Gets value of specified tile-component without knowing if a
     * specific tile-component value has been previously entered. It
     * first check if a tile-component specific value has been
     * entered, then if a tile specific value exist, then if a
     * component specific value exist. If not the default value is
     * returned.
     *
     * @param t Tile index
     *
     * @param c Component index
     *
     * @return Value for this tile component.
     * */
    protected Object getSpec(int t,int c){
	switch(specValType[t][c]){
	case SPEC_DEF:
	    return getDefault();
	case SPEC_COMP_DEF:
	    return getCompDef(c);
	case SPEC_TILE_DEF:
	    return getTileDef(t);
	case SPEC_TILE_COMP:
	    return tileCompVal.get("t"+t+"c"+c);
	default:
	    throw new IllegalArgumentException("Not recognized spec type");
	}
    }

    /**
     * Return the spec type of the given tile-component.
     *
     * @param t Tile index
     *
     * @param c Component index
     * */
    public byte getSpecValType(int t,int c){
	return specValType[t][c];
    }

    /**
     * Whether or not specifications have been entered for the given
     * component.
     *
     * @param c Index of the component
     *
     * @return True if component specification has been defined
     * */
    public boolean isCompSpecified(int c){
	if(compDef==null || compDef[c]==null)
	    return false;
	else
	    return true;
    }

    /**
     * Whether or not specifications have been entered for the given
     * tile.
     *
     * @param t Index of the tile
     *
     * @return True if tile specification has been entered
     * */
    public boolean isTileSpecified(int t){
	if(tileDef==null || tileDef[t]==null)
	    return false;
	else
	    return true;
    }

    /**
     * Whether or not a tile-component specification has been defined
     *
     * @param t Tile index
     *
     * @param c Component index
     *
     * @return True if a tile-component specification has been defined.
     * */
    public boolean isTileCompSpecified(int t,int c){
	if(tileCompVal==null || tileCompVal.get("t"+t+"c"+c)==null)
	    return false;
	else
	    return true;
    }

    /**
     * This method is responsible of parsing tile indexes set and
     * component indexes set for an option. Such an argument must
     * follow the following policy:<br>
     *
     * <tt>t\<indexes set\></tt> or <tt>c\<indexes set\></tt> where
     * tile or component indexes are separated by commas or a
     * dashes.
     *
     * <p><u>Example:</u><br>
     * <li> <tt>t0,3,4</tt> means tiles with indexes 0, 3 and 4.<br>
     * <li> <tt>t2-4</tt> means tiles with indexes 2,3 and 4.<br>
     *
     * It returns a boolean array skteching which tile or component are
     * concerned by the next parameters.
     *
     * @param word The word to parse.
     *
     * @param maxIdx Maximum authorized index
     *
     * @return Indexes concerned by this parameter.
     * */
    public static final boolean[] parseIdx(String word, int maxIdx){
	int nChar = word.length(); // Number of characters
	char c = word.charAt(0);   // current character
	int idx = -1;              // Current (tile or component) index
	int lastIdx = -1;          // Last (tile or component) index
	boolean isDash = false;    // Whether or not last separator was a dash

	boolean[] idxSet = new boolean[maxIdx];
	int i=1; // index of the current character

	while(i<nChar){
	    c = word.charAt(i);
	    if(Character.isDigit(c)){
		if(idx==-1)
		    idx = 0;
		idx = idx*10+ (c-'0');
	    }
	    else{
		if(idx==-1 || (c!=',' && c!='-')){
		   throw new IllegalArgumentException("Bad construction for "+
						      "parameter: "+word);
		}
		if(idx<0 || idx>=maxIdx){
		    throw new IllegalArgumentException("Out of range index in "+
						       "parameter `"+word+"' : "+
						       +idx);
		}

		// Found a comma
		if(c==','){
		    if(isDash){ // Previously found a dash, fill idxSet
			for(int j=lastIdx+1; j<idx; j++){
			    idxSet[j] = true;
			}
		    }
		    isDash = false;
		}
		else // Found a dash
		    isDash = true;

		// Udate idxSet
		idxSet[idx] = true;
		lastIdx = idx;
		idx=-1;
	    }
	    i++;
	}

	// Process last found index
	if(idx<0 || idx>=maxIdx){
	    throw new IllegalArgumentException("Out of range index in "+
					       "parameter `"+word+"' : "+idx);
	}
	if(isDash)
	    for(int j=lastIdx+1; j<idx; j++){
		idxSet[j] = true;
	    }
	idxSet[idx] = true;

	return idxSet;
    }

    /**
     * Returns a tile-component representative using default value.
     *
     * @return Tile component index in an array (first element: tile
     * index, second element: component index).
     * */
/*
    public int[] getDefRep(){
	int[] tcidx = new int[2];
	for(int t=nTiles-1; t>=0; t--){
	    for(int c=nComp-1; c>=0; c--){
		if(specValType[t][c]==SPEC_DEF){
		    tcidx[0] = t;
		    tcidx[1] = c;
		    return tcidx;
		}
	    }
	}

        throw new IllegalArgumentException("No representative for "+
                                           "default value");
    }
*/
    /**
     * Returns a component representative using tile default value.
     *
     * @param t Tile index
     *
     * @return component index of the representant
     * */
/*
    public int getTileDefRep(int t){
	for(int c=nComp-1; c>=0; c--)
	    if(specValType[t][c]==SPEC_TILE_DEF){
		return c;
	    }

	throw new IllegalArgumentException("No representative for tile "+
					   "default value");
    }
*/
    /**
     * Returns a tile representative using component default value.
     *
     * @param c Component index
     *
     * @return tile index of the representant
     * */
/*
    public int getCompDefRep(int c){
	for(int t=nTiles-1; t>=0; t--) {
	    if(specValType[t][c]==SPEC_COMP_DEF){
		return t;
	    }
        }

	throw new IllegalArgumentException("No representative for component "+
					   "default value, c="+c);
    }
*/
/*
    public String getSpecified() {
        return specified;
    }
*/
}
