/*
 *                    BioJava development code
 *
 * This code may be freely distributed and modified under the
 * terms of the GNU Lesser General Public Licence.  This should
 * be distributed with the code.  If you do not have a copy,
 * see:
 *
 *      http://www.gnu.org/copyleft/lesser.html
 *
 * Copyright for this code is held jointly by the individual
 * authors.  These should be listed in @author doc comments.
 *
 * For more information on the BioJava project and its aims,
 * or to join the biojava-l mailing list, visit the home page
 * at:
 *
 *      http://www.biojava.org/
 *
 */
package org.biojava.bio.symbol;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import org.biojava.bio.BioError;
import org.biojava.utils.AbstractChangeable;
import org.biojava.utils.ChangeEvent;
import org.biojava.utils.ChangeListener;
import org.biojava.utils.ChangeVetoException;

/**
 * Implementation of AlphabetIndex that handles CrossProductAlphabets
 *
 * @author Kalle N�slund
 * @author Matthew Pocock
 * @author David Huen
 *
 * @since 1.3
 */

// things to change and improve perhaps ?
// 1) need to unregister changelistener at destruction otherwise the Alphabet
//    we index might hold reference and this object will never be garbage
//    collected ?
// 2) just dont rethrow the IndexOutOfBounds exception in the
//    symbolForIndex method    

class CrossProductAlphabetIndex extends AbstractChangeable
implements AlphabetIndex, ChangeListener, Serializable
{	
	// The alphabet we are indexing
	final FiniteAlphabet	Alpha;
	// list holding AlphabetIndexes for the alphabets making upp the alphabet we index
	List		alphaIndexes;
  List    revIndexes;

	public CrossProductAlphabetIndex( FiniteAlphabet theAlpha ) {
		// the alpha we index over
		Alpha = theAlpha;
		// listen to changes in our alpha
		Alpha.addChangeListener( this, Alphabet.SYMBOLS );
		// we need this to compute the index
		alphaIndexes = buildIndexList( theAlpha );
    revIndexes = new ArrayList(alphaIndexes);
    Collections.reverse(revIndexes);
	}

	public FiniteAlphabet getAlphabet() {
		return Alpha;
	}

	/**
	* This just takes the alphabet, and splits it up into its sub alphabets
	* then gets an AlphabetIndex for each sub alphabet and stores that in an
	* List. This list is then used for the index computation
	*/
	private List buildIndexList( FiniteAlphabet fA ) {
		List	subAlphas 	= fA.getAlphabets();
		List	retList		= new ArrayList();

		for( Iterator it = subAlphas.iterator() ; it.hasNext() ; ) {
			// no need to check its FiniteAlphabets as we start with a FiniteAlphabet
			FiniteAlphabet currAlpha = ( FiniteAlphabet )it.next();
			AlphabetIndex currInd = AlphabetManager.getAlphabetIndex( currAlpha );
			retList.add( currInd );
		}
		//System.out.println( retList );
		return retList; 
	}
	
	public void preChange( ChangeEvent cE ) throws ChangeVetoException {
		// Should perhaps ask eventual listeners to this object if they think
		// the change is ok, and refuse if they dont think it is ????????????????
	}

	public void postChange( ChangeEvent cE ) {
		// ok, alphabet have changed, so we better rebuild or list of AlphabetIndexes 
		alphaIndexes = buildIndexList( Alpha );
    revIndexes = new ArrayList(alphaIndexes);
    Collections.reverse(revIndexes);
	}

	public int indexForSymbol( Symbol s ) throws IllegalSymbolException {
		// ok, split the symbol upp into its factors
		List	factors = ( ( BasisSymbol )s ).getSymbols();
    if(factors.size() != getAlphabet().size()) {
      getAlphabet().validate(s);
    }

		int	index	= 0; // this is the index we want to compute
		
		// iterate over each factor symbol, and at the same time iterate over the AlphabetIndexList
		// and feed each symbol to its corresponding AlphabetIndex this should perhasp be iteration
		// via arrays instead ? if that speeds it up
		Iterator indexIt  = alphaIndexes.iterator();
		Iterator symbolIt = factors.iterator();
		
    try {
      while( symbolIt.hasNext() ) {
        Symbol 		currentSymbol 	= ( Symbol )symbolIt.next();
        AlphabetIndex 	currentAlphaInd = ( AlphabetIndex )indexIt.next();
        FiniteAlphabet	currentAlphabet	= currentAlphaInd.getAlphabet();
        
        index = index * currentAlphabet.size() + currentAlphaInd.indexForSymbol( currentSymbol );
      }
    } catch (IllegalSymbolException ise) {
      getAlphabet().validate(s);
    }
    
    if(symbolIt.hasNext()) {
      getAlphabet().validate(s);
      throw new BioError("Assertion failure: Ran out of indexers for symbols");
    }
    
		return index;
	}

	public Symbol symbolForIndex( int index ) throws IndexOutOfBoundsException {
		List	symbols	= new ArrayList();
		try { 
			for ( Iterator it = revIndexes.iterator() ; it.hasNext() ; ) {
				AlphabetIndex	curAlpIndex 	= ( AlphabetIndex ) it.next();
				int		curAlpSize 	= curAlpIndex.getAlphabet().size();
				int		curSymIndValue 	= index % curAlpSize;
				index = index / curAlpSize;
				symbols.add( 0, curAlpIndex.symbolForIndex( curSymIndValue ) );
			}
		
			return( Alpha.getSymbol( symbols ) );
		}
		catch( IllegalSymbolException isE ) {
			throw new BioError(isE);
		}
		catch( IndexOutOfBoundsException ioobE ) {
			// this is most likely bad, but i just rethrow the exception, i should do something
			// clever here
			throw ioobE;
		}
		
	}
}
