package it.geosolutions.utils.imagepyramid;

import it.geosolutions.utils.progress.ExceptionEvent;
import it.geosolutions.utils.progress.ProcessingEvent;
import it.geosolutions.utils.progress.ProcessingEventListener;
import it.geosolutions.utils.progress.ProgressManager;

import java.awt.Rectangle;
import java.awt.geom.Rectangle2D;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.media.jai.Interpolation;
import javax.media.jai.InterpolationBilinear;
import javax.media.jai.InterpolationNearest;
import javax.media.jai.JAI;

import org.apache.commons.cli2.option.DefaultOption;
import org.apache.commons.cli2.option.GroupImpl;
import org.apache.commons.cli2.util.HelpFormatter;
import org.apache.commons.cli2.validation.InvalidArgumentException;
import org.apache.commons.cli2.validation.Validator;
import org.geotools.coverage.grid.GeneralGridRange;
import org.geotools.coverage.grid.GridCoverage2D;
import org.geotools.coverage.grid.GridGeometry2D;
import org.geotools.coverage.processing.DefaultProcessor;
import org.geotools.coverage.processing.operation.FilteredSubsample;
import org.geotools.coverage.processing.operation.Scale;
import org.geotools.coverage.processing.operation.SubsampleAverage;
import org.geotools.factory.Hints;
import org.geotools.gce.geotiff.GeoTiffWriter;
import org.geotools.gce.imagemosaic.ImageMosaicFormat;
import org.geotools.gce.imagemosaic.ImageMosaicReader;
import org.geotools.geometry.GeneralEnvelope;
import org.opengis.parameter.GeneralParameterValue;
import org.opengis.parameter.ParameterValue;
import org.opengis.parameter.ParameterValueGroup;

/**
 * <p>
 * Usage:<br/> <code>PyramidLayerBuilder -h -v -s -t -f -a -o -p -c</code>
 * </p>
 * 
 * <pre>
 *                                                                                                                                            
 *                         where:                                                                                                                   
 *                          -h : Prints a nice command line Help                                                                                    
 *                          -v : Prints the tools Version                                                                                           
 *                          -s : Is the path where the raster(s) is(are) located                                                                    
 *                          -t : Is the tile dimensions as a couple width,height in pixels (e.g. 512,512)                                           
 *                          -f : Represents the scale factor. If you want a raster which is 1/2 resolution                                          
 *                               of the original, f should be 2                                                                                     
 *                          -a : Represents the Scaling algorithm to use. You can choose among one of the following                                 
 *                               nn, bil, avg, filt                                                                                                 
 *                          -o : Represents the output format. It can be one of the following                                                       
 *                               tiff, tif, gtiff, gtif, png, jpeg                                                                                  
 *                          -p : Is the Thread Priority, a number between 1 and 10 -&gt; 1 [LOW] - 5 [MED] - 10 [HIGH]                              
 *                          -c : Represents the JAI TileCache dimension. This is an optional parameter which allows                                 
 *                               you to tune the tool performances.                                                                                 
 * </pre>
 * 
 * <p>
 * Example of usage:<br/>
 * <code>PyramidLayerBuilder -t "512,512" -s "/usr/home/tmp/tiled/world.200412.3x21600x21600.a1_ref.shp" -f 2 -a nn -c 512</code>
 * </p>
 * 
 * @author Simone Giannecchini
 * @author Alessio Fabiani
 * @version 0.2
 * 
 */
public class PyramidLayerBuilder extends ProgressManager implements Runnable,
		ProcessingEventListener {

	/** Static immutable ap for scaling algorithms. */
	private static List scalingAlgorithms;
	static {
		scalingAlgorithms = new ArrayList(4);
		scalingAlgorithms.add("nn");
		scalingAlgorithms.add("bil");
		scalingAlgorithms.add("avg");
		scalingAlgorithms.add("filt");
	}

	/** Static immutable ap for scaling algorithms. */
	private static List outputFormats;
	static {
		outputFormats = new ArrayList(6);
		outputFormats.add("tiff");
		outputFormats.add("tif");
		outputFormats.add("gtiff");
		outputFormats.add("gtif");
		outputFormats.add("png");
		outputFormats.add("jpeg");

	}

	/** Default Logger * */
	private final static Logger LOGGER = org.geotools.util.logging.Logging.getLogger(PyramidLayerBuilder.class.toString());

	/** Program Version */
	private final static String versionNumber = "0.2";

	private final static Hints LENIENT_HINT = new Hints(
			Hints.LENIENT_DATUM_SHIFT, Boolean.TRUE);

	private final static FilteredSubsample filteredSubsampleFactory = new FilteredSubsample();

	private static final SubsampleAverage subsampleAvgFactory = new SubsampleAverage();

	private static final Scale scaleFactory = new Scale();

	private DefaultOption inputLocationOpt;

	private DefaultOption outputLocationOpt;

	private DefaultOption tileDimOpt;

	private DefaultOption scaleAlgorithmOpt;

	private DefaultOption tileCacheSizeOpt;

	private DefaultOption outFormatOpt;

	private double tileW;

	private double tileH;

	private File inputLocation;

	private File outputLocation;

	private String scaleAlgorithm;

	private String outputFormat;

	private int tileCacheSize;

	private DefaultOption scaleFactorOpt;

	private int scaleFactor;

	public PyramidLayerBuilder() {
		// /////////////////////////////////////////////////////////////////////
		// Options for the command line
		// /////////////////////////////////////////////////////////////////////
		helpOpt = optionBuilder.withShortName("h").withShortName("?")
				.withLongName("helpOpt").withDescription("print this message.")
				.withRequired(false).create();

		versionOpt = optionBuilder.withShortName("v")
				.withLongName("versionOpt").withDescription(
						"print the versionOpt.").withRequired(false).create();

		inputLocationOpt = optionBuilder.withShortName("s").withLongName(
				"source_directory").withArgument(
				arguments.withName("source").withMinimum(1).withMaximum(1)
						.withValidator(new Validator() {

							public void validate(List args)
									throws InvalidArgumentException {
								final int size = args.size();
								if (size > 1)
									throw new InvalidArgumentException(
											"Only one location can be chosen");
								final File source = new File((String) args
										.get(0));
								if (!source.isFile() || !source.exists())
									throw new InvalidArgumentException(
											new StringBuffer(
													"The provided source is invalid! ")

											.toString());
							}

						}).create()).withDescription(
				"path where files are located").withRequired(true).create();

		outputLocationOpt = optionBuilder
				.withShortName("d")
				.withLongName("dest_directory")
				.withArgument(
						arguments.withName("destination").withMinimum(0)
								.withMaximum(1).create())
				.withDescription(
						"output directory, if none is provided, the \"tiled\" directory will be used")
				.withRequired(false).create();

		tileDimOpt = optionBuilder.withShortName("t").withLongName(
				"tiled_dimension").withArgument(
				arguments.withName("t").withMinimum(1).withMaximum(1).create())
				.withDescription(
						"tile dimensions as a couple width,height in pixels")
				.withRequired(true).create();

		scaleFactorOpt = optionBuilder
				.withShortName("f")
				.withLongName("scale_factor")
				.withArgument(
						arguments.withName("f").withMinimum(1).withMaximum(1)
								.withValidator(new Validator() {

									public void validate(List args)
											throws InvalidArgumentException {
										final int size = args.size();
										if (size > 1)
											throw new InvalidArgumentException(
													"Only one scaling algorithm at a time can be chosen");
										int factor = Integer
												.parseInt((String) args.get(0));
										if (factor <= 0)
											throw new InvalidArgumentException(
													new StringBuffer(
															"The provided scale factor is negative! ")

													.toString());
										if (factor == 1) {
											LOGGER
													.warning("The scale factor is 1!");
											System.exit(0);
										}
									}

								}).create()).withDescription(
						"integer scale factor")
				.withRequired(true).create();

		scaleAlgorithmOpt = optionBuilder
				.withShortName("a")
				.withLongName("scaling_algorithm")
				.withArgument(
						arguments.withName("a").withMinimum(0).withMaximum(1)
								.withValidator(new Validator() {

									public void validate(List args)
											throws InvalidArgumentException {
										final int size = args.size();
										if (size > 1)
											throw new InvalidArgumentException(
													"Only one scaling algorithm at a time can be chosen");
										if (!scalingAlgorithms.contains(args
												.get(0)))
											throw new InvalidArgumentException(
													new StringBuffer(
															"The output format ")
															.append(args.get(0))
															.append(
																	" is not permitted")
															.toString());

									}
								}).create())
				.withDescription(
						"name of the scaling algorithm, eeither one of average (a), filtered	 (f), bilinear (bil), nearest neigbhor (nn)")
				.withRequired(false).create();

		priorityOpt = optionBuilder.withShortName("p").withLongName(
				"thread_priority").withArgument(
				arguments.withName("thread_priority").withMinimum(0)
						.withMaximum(1).create()).withDescription(
				"priority for the underlying thread").withRequired(false)
				.create();

		outFormatOpt = optionBuilder
				.withShortName("o")
				.withLongName("out_format")
				.withArgument(
						arguments.withName("o").withMinimum(0).withMaximum(1)
								.withDescription("output format")
								// .withDefault("gtiff")
								.withValidator(new Validator() {

									public void validate(List args)
											throws InvalidArgumentException {
										final int size = args.size();
										if (size > 1)
											throw new InvalidArgumentException(
													"Only one output format at a time can be specified");
										if (!outputFormats
												.contains(args.get(0)))
											throw new InvalidArgumentException(
													new StringBuffer(
															"The output format ")
															.append(args.get(0))
															.append(
																	" is not permitted")
															.toString());

									}
								}).create()).withDescription("output format")
				.withRequired(false).create();

		tileCacheSizeOpt = optionBuilder.withShortName("c").withLongName(
				"cache_size").withArgument(
				arguments.withName("c").withMinimum(0).withMaximum(1).create())
				.withDescription("tile cache sized").withRequired(false)
				.create();

		cmdOpts.add(inputLocationOpt);
		cmdOpts.add(tileDimOpt);
		cmdOpts.add(scaleFactorOpt);
		cmdOpts.add(scaleAlgorithmOpt);
		cmdOpts.add(outFormatOpt);
		cmdOpts.add(priorityOpt);
		cmdOpts.add(tileCacheSizeOpt);
		cmdOpts.add(versionOpt);
		cmdOpts.add(helpOpt);

		optionsGroup = new GroupImpl(cmdOpts, "Options", "All the options", 0,
				9);

		// /////////////////////////////////////////////////////////////////////
		//
		// Help Formatter
		//
		// /////////////////////////////////////////////////////////////////////
		final HelpFormatter cmdHlp = new HelpFormatter("| ", "  ", " |", 75);
		cmdHlp.setShellCommand("PyramidLayerBuilder");
		cmdHlp.setHeader("Help");
		cmdHlp.setFooter(new StringBuffer(
				"PyramidLayerBuilder - GeoSolutions S.a.s (C) 2006 - v ")
				.append(PyramidLayerBuilder.versionNumber).toString());
		cmdHlp
				.setDivider("|-------------------------------------------------------------------------|");

		cmdParser.setGroup(optionsGroup);
		cmdParser.setHelpOption(helpOpt);
		cmdParser.setHelpFormatter(cmdHlp);
	}

	/**
	 * 
	 * @param args
	 * @throws IOException
	 * @throws IllegalArgumentException
	 * @throws InterruptedException
	 */
	public static void main(String[] args) throws IllegalArgumentException,
			IOException, InterruptedException {

		final PyramidLayerBuilder pyramidBuilder = new PyramidLayerBuilder();
		pyramidBuilder.addProcessingEventListener(pyramidBuilder);
		if (pyramidBuilder.parseArgs(args)) {
			final Thread t = new Thread(pyramidBuilder, "PyramidBuilder");
			t.setPriority(pyramidBuilder.priority);
			t.start();
			try {
				t.join();
			} catch (InterruptedException e) {
				LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e);
			}

		} else
			LOGGER.fine("Exiting...");

	}

	private boolean parseArgs(String[] args) {
		cmdLine = cmdParser.parseAndHelp(args);
		if (cmdLine != null && cmdLine.hasOption(versionOpt)) {
			LOGGER.fine(new StringBuffer(
					"PyramidBuilder - GeoSolutions S.a.s (C) 2006 - v").append(
					PyramidLayerBuilder.versionNumber).toString());
			System.exit(1);

		} else if (cmdLine != null) {
			// ////////////////////////////////////////////////////////////////
			//
			// parsing command line parameters and setting up
			// Mosaic Index Builder options
			//
			// ////////////////////////////////////////////////////////////////
			inputLocation = new File((String) cmdLine
					.getValue(inputLocationOpt));

			// tile dim
			final String tileDim = (String) cmdLine.getValue(tileDimOpt);
			final String[] pairs = tileDim.split(",");
			tileW = Integer.parseInt(pairs[0]);
			tileH = Integer.parseInt(pairs[1]);

			// //
			//
			// scale factor
			//
			// //
			final String scaleF = (String) cmdLine.getValue(scaleFactorOpt);
			scaleFactor = Integer.parseInt(scaleF);

			// output files' directory
			if (cmdLine.hasOption(outputLocationOpt))
				outputLocation = new File((String) cmdLine
						.getValue(outputLocationOpt));
			else
				outputLocation = new File(inputLocation.getParentFile(), String
						.valueOf(scaleFactor));

			// //
			//
			// scaling algorithm
			//
			// //
			scaleAlgorithm = (String) cmdLine.getValue(scaleAlgorithmOpt);
			if (scaleAlgorithm == null)
				scaleAlgorithm = "nn";

			// //

			// //
			//
			// output format
			//
			// //
			outputFormat = (String) cmdLine.getValue(outFormatOpt);

			// //
			//
			// Thread priority
			//
			// //
			// index name
			if (cmdLine.hasOption(priorityOpt))
				priority = Integer.parseInt((String) cmdLine
						.getValue(priorityOpt));

			// //
			//
			// Tile cache size
			//
			// //
			// index name
			if (cmdLine.hasOption(tileCacheSizeOpt)) {
				tileCacheSize = Integer.parseInt((String) cmdLine
						.getValue(tileCacheSizeOpt));
				JAI.getDefaultInstance().getTileCache().setMemoryCapacity(
						tileCacheSize * 1024 * 1024);
			}
			return true;

		}
		return false;

	}

	public void run() {

		// /////////////////////////////////////////////////////////////////////
		//
		//
		// PARSING INPUT PARAMETERS
		// 
		// 
		// /////////////////////////////////////////////////////////////////////
		StringBuffer message = new StringBuffer("Requested scale factor is ")
				.append(scaleFactor);
		if (LOGGER.isLoggable(Level.FINE))
			LOGGER.fine(message.toString());
		fireEvent(message.toString(), 0);

		// /////////////////////////////////////////////////////////////////////
		//
		//
		// Opening the base mosaic
		// 
		// 
		// /////////////////////////////////////////////////////////////////////
		// mosaic reader
		message = new StringBuffer("Acquiring a mosaic reader to mosaic ")
				.append(inputLocation);
		if (LOGGER.isLoggable(Level.FINE))
			LOGGER.fine(message.toString());
		fireEvent(message.toString(), 0);

		ImageMosaicReader inReader = null;
		try {
			inReader = new ImageMosaicReader(inputLocation, new Hints(
					Hints.IGNORE_COVERAGE_OVERVIEW, Boolean.TRUE));
		} catch (IOException e) {
			LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e);
			fireException(e);
			return;
		}

		// /////////////////////////////////////////////////////////////////////
		//
		//
		// Preparing all the params
		// 
		// 
		// /////////////////////////////////////////////////////////////////////
		// output files' directory
		if (!outputLocation.exists())
			outputLocation.mkdir();

		// getting envelope and other information about dimension
		final GeneralEnvelope envelope = inReader.getOriginalEnvelope();

		message = new StringBuffer("Original envelope is ").append(envelope
				.toString());
		if (LOGGER.isLoggable(Level.FINE))
			LOGGER.fine(message.toString());
		fireEvent(message.toString(), 0);

		final GeneralGridRange range = inReader.getOriginalGridRange();

		message = new StringBuffer("Original range is ").append(range
				.toString());
		if (LOGGER.isLoggable(Level.FINE))
			LOGGER.fine(message.toString());
		fireEvent(message.toString(), 0);

		// new number fo rows and columns
		final double newWidth = (range.getLength(0) * 1.0) / scaleFactor;
		final double newHeight = (range.getLength(1) * 1.0) / scaleFactor;
		if (tileW > newWidth)
			tileW = newWidth;
		if (tileH > newHeight)
			tileH = newHeight;

		message = new StringBuffer("New dimension is (W,H)==(")
				.append(newWidth).append(",").append(newHeight).append(")");
		if (LOGGER.isLoggable(Level.FINE))
			LOGGER.fine(message.toString());
		fireEvent(message.toString(), 0);

		int newCols = (int) Math.floor(newWidth / tileW);
		int newRows = (int) Math.floor(newHeight / tileH);
		final boolean hasRemainingColum = (newWidth % tileW) != 0;
		final boolean hasRemainingRow = (newHeight % tileH) != 0;
		final double remainingWidth = hasRemainingColum ? newWidth - tileW
				* (newCols) : tileW;
		final double remainingHeight = hasRemainingRow ? newHeight - tileH
				* (newRows) : tileH;

		message = new StringBuffer("New matrix dimension is (cols,rows)==(")
				.append(newCols).append(",").append(newRows).append(")");
		if (LOGGER.isLoggable(Level.FINE))
			LOGGER.fine(message.toString());
		fireEvent(message.toString(), 0);

		final double minx = envelope.getMinimum(0);
		final double miny = envelope.getMinimum(1);
		final double maxx = envelope.getMaximum(0);
		final double maxy = envelope.getMaximum(1);

		double _maxx = 0.0;
		double _maxy = 0.0;
		double _minx = 0.0;
		double _miny = 0.0;
		GridCoverage2D gc = null;
		File fileOut;
		GeoTiffWriter writerWI;
		ParameterValue gg;
		GeneralEnvelope cropEnvelope;

		// prescale algortihm
		int preScaleSteps = 0;
		int tempScale = scaleFactor;
		while ((tempScale /= 2) > 0) {
			preScaleSteps++;
		}
		boolean doPrescaleSteps = false;
		// we need to be carefufl to NOT exhaust the scale steps with the
		// prescaling

		if (Math.pow(2, preScaleSteps) == scaleFactor) {
			preScaleSteps--;
		}
		double finalScale = scaleFactor / Math.pow(2, preScaleSteps);

		// preparing parameters for the subsampling operation
		final DefaultProcessor processor = new DefaultProcessor(LENIENT_HINT);
		Interpolation interpolation = null;
		ParameterValueGroup subsamplingParams = null;
		if (scaleAlgorithm.equalsIgnoreCase("nn")) {
			subsamplingParams = processor.getOperation("Scale").getParameters();
			interpolation = new InterpolationNearest();
			doPrescaleSteps = true;
		} else if (scaleAlgorithm.equalsIgnoreCase("filt")) {
			subsamplingParams = processor.getOperation("FilteredSubsample")
					.getParameters();
			interpolation = new InterpolationNearest();
			doPrescaleSteps = scaleFactor % 2 == 0;
			if (!doPrescaleSteps)
				finalScale = scaleFactor;
		} else if (scaleAlgorithm.equalsIgnoreCase("bil")) {
			subsamplingParams = processor.getOperation("Scale").getParameters();
			interpolation = new InterpolationBilinear();
			doPrescaleSteps = true;
		} else if (scaleAlgorithm.equalsIgnoreCase("avg")) {
			subsamplingParams = processor.getOperation("SubsampleAverage")
					.getParameters();
			interpolation = new InterpolationNearest();
			doPrescaleSteps = scaleFactor % 2 == 0;
			if (!doPrescaleSteps)
				finalScale = scaleFactor;
		} else {
			throw new IllegalArgumentException(
					"The provided scale algorithm is not availaible");

		}

		// ///////////////////////////////////////////////////////////////////
		//
		// MAIN LOOP
		//
		//
		// ///////////////////////////////////////////////////////////////////
		newRows += hasRemainingRow ? 1 : 0;
		newCols += hasRemainingColum ? 1 : 0;
		final double totalNumberOfFile = newRows * newCols;

		// getting resolution of each tile
		final double tileGeoWidth = envelope.getLength(0) / newCols;
		final double tileGeoHeight = envelope.getLength(1) / newRows;

		final int uppers[] = range.getUppers();
		final double newRange[] = new double[] { uppers[0] / newCols,
				uppers[1] / newRows };

		for (int i = 0; i < newRows; i++)
			for (int j = 0; j < newCols; j++) {

				// //
				//
				// computing the bbox for this tile
				//
				// //
				_maxx = minx + (j + 1) * tileGeoWidth;
				_minx = minx + (j) * tileGeoWidth;
				_maxy = miny + (i + 1) * tileGeoHeight;
				_miny = miny + (i) * tileGeoHeight;
				if (_maxx > maxx)
					_maxx = maxx;
				if (_maxy > maxy)
					_maxy = maxy;

				fileOut = new File(outputLocation, new StringBuffer("mosaic")
						.append("_").append(Integer.toString(i * newCols + j))
						.append(".").append("tiff").toString());
				if (fileOut.exists())
					fileOut.delete();

				message = new StringBuffer("Preparing tile (col,row)==(")
						.append(j).append(",").append(i).append(") to file ")
						.append(fileOut);
				if (LOGGER.isLoggable(Level.FINE))
					LOGGER.fine(message.toString());
				fireEvent(message.toString(), (j + i * newCols)
						/ totalNumberOfFile);

				// //
				//
				// building gridgeometry for the read operation
				//
				// //
				gg = (ParameterValue) ImageMosaicFormat.READ_GRIDGEOMETRY2D
						.createValue();
				cropEnvelope = new GeneralEnvelope(
						new double[] { _minx, _miny }, new double[] { _maxx,
								_maxy });
				cropEnvelope.setCoordinateReferenceSystem(inReader.getCrs());
				gg.setValue(new GridGeometry2D(new GeneralGridRange(
						new Rectangle(0, 0, 800, 800)), cropEnvelope));

				message = new StringBuffer("Reading with grid envelope ")
						.append(cropEnvelope.toString());
				if (LOGGER.isLoggable(Level.FINE))
					LOGGER.fine(message.toString());
				fireEvent(message.toString(), (j + i * newCols)
						/ totalNumberOfFile);

				try {
					gc = (GridCoverage2D) inReader
							.read(new GeneralParameterValue[] { gg });

				} catch (IOException e) {
					LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e);
					fireEvent(e.getLocalizedMessage(), 0);
					return;
				}

				ParameterValueGroup param;

				param = processor.getOperation("CoverageCrop").getParameters();
				param.parameter("Source").setValue(gc);
				param.parameter("Envelope").setValue(cropEnvelope);

				GridCoverage2D cropped = (GridCoverage2D) processor
						.doOperation(param);

				// //
				// Adjusting the resolution
				// //
				final GeneralGridRange newGridrange = new GeneralGridRange(
						new Rectangle2D.Double(0.0, 0.0, newRange[0],
								newRange[1]).getBounds());
				final GridGeometry2D scaledGridGeometry = new GridGeometry2D(
						newGridrange, cropEnvelope);
				param = processor.getOperation("Resample").getParameters();
				param.parameter("Source").setValue(cropped);
				param.parameter("CoordinateReferenceSystem").setValue(
						inReader.getCrs());
				param.parameter("GridGeometry").setValue(scaledGridGeometry);
				param
						.parameter("InterpolationType")
						.setValue(
								Interpolation
										.getInstance(Interpolation.INTERP_NEAREST));

				gc = (GridCoverage2D) processor.doOperation(param);

				// //
				//
				// rescaling to the needed res
				//
				// //
				// pre scaling
				if (doPrescaleSteps && LOGGER.isLoggable(Level.FINE)) {
					message = new StringBuffer("Pre scaling...");
					if (LOGGER.isLoggable(Level.FINE))
						LOGGER.fine(message.toString());
					fireEvent(message.toString(), (j + i * newCols)
							/ totalNumberOfFile);
				}

				for (int k = 0; k < preScaleSteps && doPrescaleSteps; k++) {

					param = filteredSubsampleFactory.getParameters();
					param.parameter("source").setValue(gc);
					param.parameter("scaleX").setValue(new Integer(2));
					param.parameter("scaleY").setValue(new Integer(2));
					param.parameter("qsFilterArray")
							.setValue(new float[] { 1 });
					param.parameter("Interpolation").setValue(
							new InterpolationBilinear());
					gc = (GridCoverage2D) filteredSubsampleFactory.doOperation(
							param, null);
				}

				message = new StringBuffer("Scaling...");
				if (LOGGER.isLoggable(Level.FINE))
					LOGGER.fine(message.toString());
				fireEvent(message.toString(), 0);

				if (scaleAlgorithm.equalsIgnoreCase("nn")) {
					param = processor.getOperation("Scale").getParameters();
					param.parameter("Source").setValue(gc);
					param.parameter("xScale").setValue(
							new Float(1 / finalScale));
					param.parameter("yScale").setValue(
							new Float(1 / finalScale));
					param.parameter("xTrans").setValue(new Float(0));
					param.parameter("yTrans").setValue(new Float(0));
					param
							.parameter("Interpolation")
							.setValue(
									Interpolation
											.getInstance(Interpolation.INTERP_BILINEAR));
					gc = (GridCoverage2D) scaleFactory.doOperation(param, null);
				} else if (scaleAlgorithm.equalsIgnoreCase("filt")) {
					// scaling
					param = (ParameterValueGroup) subsamplingParams.clone();
					param.parameter("source").setValue(gc);
					param.parameter("scaleX").setValue(
							new Integer((int) finalScale));
					param.parameter("scaleY").setValue(
							new Integer((int) finalScale));
					param.parameter("qsFilterArray").setValue(
							new float[] { 0.5F, 1.0F / 3.0F, 0.0F,
									-1.0F / 12.0F });
					param.parameter("Interpolation").setValue(
							new InterpolationNearest());

					gc = (GridCoverage2D) filteredSubsampleFactory.doOperation(
							param, null);
				} else if (scaleAlgorithm.equalsIgnoreCase("bil")) {
					param = processor.getOperation("Scale").getParameters();
					param.parameter("Source").setValue(gc);
					param.parameter("xScale").setValue(
							new Float(1 / finalScale));
					param.parameter("yScale").setValue(
							new Float(1 / finalScale));
					param.parameter("xTrans").setValue(new Float(0));
					param.parameter("yTrans").setValue(new Float(0));
					param
							.parameter("Interpolation")
							.setValue(
									Interpolation
											.getInstance(Interpolation.INTERP_BILINEAR));
					gc = (GridCoverage2D) scaleFactory.doOperation(param, null);
				} else if (scaleAlgorithm.equalsIgnoreCase("avg")) {
					param = processor.getOperation("SubsampleAverage")
							.getParameters();
					param.parameter("Source").setValue(gc);
					param.parameter("scaleX").setValue(
							new Double(1 / finalScale));
					param.parameter("scaleY").setValue(
							new Double(1 / finalScale));
					param.parameter("Interpolation").setValue(interpolation);
					gc = (GridCoverage2D) subsampleAvgFactory.doOperation(
							param, null);
				} else {
					throw new IllegalArgumentException(
							"The provided scale algorithm is not availaible");

				}

				// //
				//
				// Writing out this coveragess
				//
				// //
				message = new StringBuffer("Writing out...");
				if (LOGGER.isLoggable(Level.FINE))
					LOGGER.fine(message.toString());
				fireEvent(message.toString(), (j + i * newCols)
						/ totalNumberOfFile);

				try {

					writerWI = new GeoTiffWriter(fileOut);
					writerWI.write(gc, null);

				} catch (IOException e) {
					LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e);
					fireEvent(e.getLocalizedMessage(), 0);
					return;
				}

			}

		message = new StringBuffer("Done...");
		if (LOGGER.isLoggable(Level.FINE))
			LOGGER.fine(message.toString());
		fireEvent(message.toString(), 100);

	}

	public void getNotification(ProcessingEvent event) {
		LOGGER.info(new StringBuffer("Progress is at ").append(
				event.getPercentage()).append("\n").append(
				"attached message is: ").append(event.getMessage()).toString());
	}

	public void exceptionOccurred(ExceptionEvent event) {
		LOGGER.log(Level.SEVERE, "An error occurred during processing", event
				.getException());
	}

	public final void setInputLocation(File inputLocation) {
		this.inputLocation = inputLocation;
	}

	public final void setOutputLocation(File outputLocation) {
		this.outputLocation = outputLocation;
	}

	/**
	 * @return the outputFormat
	 */
	public final String getOutputFormat() {
		return outputFormat;
	}

	/**
	 * @param outputFormat
	 *            the outputFormat to set
	 */
	public final void setOutputFormat(String outputFormat) {
		this.outputFormat = outputFormat;
	}

	/**
	 * @return the scaleAlgorithm
	 */
	public final String getScaleAlgorithm() {
		return scaleAlgorithm;
	}

	/**
	 * @param scaleAlgorithm
	 *            the scaleAlgorithm to set
	 */
	public final void setScaleAlgorithm(String scaleAlgorithm) {
		this.scaleAlgorithm = scaleAlgorithm;
	}

	/**
	 * @return the scaleFactor
	 */
	public final int getScaleFactor() {
		return scaleFactor;
	}

	/**
	 * @param scaleFactor
	 *            the scaleFactor to set
	 */
	public final void setScaleFactor(int scaleFactor) {
		this.scaleFactor = scaleFactor;
	}

	public File getInputLocation() {
		return inputLocation;
	}

	public File getOutputLocation() {
		return outputLocation;
	}

	/**
	 * @return the tileCacheSize
	 */
	public final int getTileCacheSize() {
		return tileCacheSize;
	}

	/**
	 * @param tileCacheSize
	 *            the tileCacheSize to set
	 */
	public final void setTileCacheSize(int tileCacheSize) {
		this.tileCacheSize = tileCacheSize;
	}

	/**
	 * @return the tileH
	 */
	public final double getTileH() {
		return tileH;
	}

	/**
	 * @param tileH
	 *            the tileH to set
	 */
	public final void setTileH(int tileH) {
		this.tileH = tileH;
	}

	/**
	 * @return the tileW
	 */
	public final double getTileW() {
		return tileW;
	}

	/**
	 * @param tileW
	 *            the tileW to set
	 */
	public final void setTileW(int tileW) {
		this.tileW = tileW;
	}

}
