/**
 * MapGenerator Sample Extensions
 */
package mycompany.map.modificator;

import java.util.logging.Level;
import java.util.logging.Logger;

import org.w3c.dom.Element;

import idea.map.generator.MapGenerator;
import idea.map.matrix.AbstractMatrix;
import idea.map.matrix.Matrixes;
import idea.map.matrix.ResultMatrix;
import idea.map.modificator.AbstractModificator;
import idea.mapgen.util.StatisticsCollector;


/**
 * Combine values from 3 input matrixes A, B and C
 * and store result to output matrix D, by following formula:
 * 
 * D = (A + B) / C
 * 
 *  Usage:
 *  
 *  <Modificator class="mycompany.map.combinator.UserCombinator matrixA="xxx" ..."/>
 *  
 *  
 * 
 * @author Lumir Vanek, vanek@idea-envi.cz
 *
 */
public class UserCombinator extends AbstractModificator
{
	/**
	 * Matrixes ID's to combine 
	 */
	private final String matrixIdA;
	private final String matrixIdB;
	private final String matrixIdC;
	private final String matrixIdD;
	
	/**
	 * Matrixes to combine 
	 */
	private AbstractMatrix matrixA;
	private AbstractMatrix matrixB;
	private AbstractMatrix matrixC;
	
	/**
	 * Result matrix
	 */
	private AbstractMatrix matrixD;
	
	/**
	 * Constructor
	 * 
	 * @param matrixA ID of matrix A
	 * @param matrixB ID of matrix B
	 * @param matrixC ID of matrix C
	 * @param matrixD ID of matrix D
	 */
	public UserCombinator(String matrixA, String matrixB, String matrixC, String matrixD)
	{
		super();
		matrixIdA = matrixA;
		matrixIdB = matrixB;
		matrixIdC = matrixC;
		matrixIdD = matrixD;
	}
	
	/**
	 * XML Runset constructor, initialize yourself from given DOM element
	 * 
	 * @param domElement DOM element
	 */
	public UserCombinator(Element domElement)
	{
		super(domElement);
	
		if(!domElement.hasAttribute("matrixA"))
		{
			throw new RuntimeException("Missing matrixA attribute for " + getXmlElementName());
		}
		if(!domElement.hasAttribute("matrixB"))
		{
			throw new RuntimeException("Missing matrixB attribute for " + getXmlElementName());
		}
		if(!domElement.hasAttribute("matrixC"))
		{
			throw new RuntimeException("Missing matrixC attribute for " + getXmlElementName());
		}
		if(!domElement.hasAttribute("matrixD"))
		{
			throw new RuntimeException("Missing matrixD attribute for " + getXmlElementName());
		}
			
		matrixIdA = domElement.getAttribute("matrixA");
		matrixIdB = domElement.getAttribute("matrixB");
		matrixIdC = domElement.getAttribute("matrixC");
		matrixIdD = domElement.getAttribute("matrixD");
	}

	/**
	 * Combine 3 matrixes, specified in configuration,
	 * create new matrix as result and put in to list
	 * 
	 * @param matrixes Matrixes list to combine
	 * @param collector The statistics collector
	 */
	public void modify(Matrixes matrixes, StatisticsCollector collector)
	{
		// Get matrixes A, B and C
		matrixA = matrixes.get(matrixIdA);
		if(matrixA == null) throw new RuntimeException("Matrix A not found in " + getXmlElementName());
		matrixB = matrixes.get(matrixIdB);
		if(matrixB == null) throw new RuntimeException("Matrix B not found in " + getXmlElementName());
		matrixC = matrixes.get(matrixIdC);
		if(matrixC == null) throw new RuntimeException("Matrix C not found in " + getXmlElementName());
		
		// Create result matrix
		if(matrixes.containsKey(matrixIdD)) throw new RuntimeException("Matrix D in " + getXmlElementName() + " already exists");
		matrixD = new ResultMatrix(matrixes, matrixIdD);
		
		// Combine matrixes
		if(matrixes.getRows() < getThreads())
		{
			throw new RuntimeException("Matrixes rows number must be less than number of threads");
		}
	
		try
		{
			float elapsedTime = runThreads(matrixes.getRows(), collector);
			matrixes.put(matrixIdD, matrixD);
			
			Logger.getLogger(MapGenerator.loggerName).fine("User Combination of matrixes " + 
					matrixA.getId() + ", " + matrixB.getId() + ", " + matrixC.getId() + " -> " + matrixD.getId() + 
					" finished in " + elapsedTime + " sec.");
		}
		catch(InterruptedException e)
		{
			Logger.getLogger(MapGenerator.loggerName).log(Level.SEVERE, 
						"UserCombinationThread interrupted", e);
				
			throw new RuntimeException("UserCombinationThread interrupted");
		}
		finally
		{
			this.matrixA = null;
			this.matrixB = null;
			this.matrixC = null;
			this.matrixD = null;
		}
	}
	
	/**
	 * Create thread 
	 * 
	 * @param startRow First matrix row, computed by created thread 
	 * @param endRow Last matrix row, computed by created thread 
	 * @param nameAppendix Appendix for thread name
	 * @return Thread, that make required work
	 */
	protected Runnable createThread(int startRow, int endRow, String nameAppendix)
	{
		return new UserCombinationThread("UserCombinationThread" + nameAppendix, 
				matrixA, 
				matrixB, 
				matrixC, 
				matrixD, 
				startRow, 
				endRow);
	}
}