package mil.nga.giat.geowave.raster.adapter.merge.nodata;

import java.awt.image.Raster;
import java.awt.image.SampleModel;
import java.awt.image.WritableRaster;

import mil.nga.giat.geowave.raster.FitToIndexGridCoverage;
import mil.nga.giat.geowave.raster.adapter.MergeableRasterTile;
import mil.nga.giat.geowave.raster.adapter.RasterDataAdapter;
import mil.nga.giat.geowave.raster.adapter.RasterTile;
import mil.nga.giat.geowave.raster.adapter.merge.RasterTileMergeStrategy;
import mil.nga.giat.geowave.raster.adapter.merge.nodata.NoDataMetadata.SampleIndex;
import org.apache.log4j.Logger;
import org.opengis.coverage.grid.GridCoverage;

public class NoDataMergeStrategy implements
		RasterTileMergeStrategy<NoDataMetadata>
{
	public NoDataMergeStrategy() {}

	private static final long serialVersionUID = 38473874l;
	private static final Logger LOGGER = Logger.getLogger(NoDataMergeStrategy.class);

	@Override
	public void merge(
			final RasterTile<NoDataMetadata> thisTile,
			final RasterTile<NoDataMetadata> nextTile,
			final SampleModel sampleModel ) {

		// this strategy aims for latest tile with data values, but where there
		// is no data in the latest and there is data in the earlier tile, it
		// fills the data from the earlier tile

		// if next tile is null or if this tile does not have metadata, just
		// keep this tile as is
		if ((nextTile != null) && (thisTile.getMetadata() != null)) {
			if (nextTile instanceof MergeableRasterTile) {
				final NoDataMetadata thisTileMetadata = thisTile.getMetadata();
				final NoDataMetadata nextTileMetadata = nextTile.getMetadata();

				final WritableRaster thisRaster = Raster.createWritableRaster(
						sampleModel,
						thisTile.getDataBuffer(),
						null);
				final WritableRaster nextRaster = Raster.createWritableRaster(
						sampleModel,
						nextTile.getDataBuffer(),
						null);
				final int maxX = thisRaster.getMinX() + thisRaster.getWidth();
				final int maxY = thisRaster.getMinY() + thisRaster.getHeight();
				boolean recalculateMetadata = false;
				for (int b = 0; b < thisRaster.getNumBands(); b++) {
					for (int x = thisRaster.getMinX(); x < maxX; x++) {
						for (int y = thisRaster.getMinY(); y < maxY; y++) {
							if (thisTileMetadata.isNoData(
									new SampleIndex(
											x,
											y,
											b),
									thisRaster.getSampleDouble(
											x,
											y,
											b))) {
								final double sample = nextRaster.getSampleDouble(
										x,
										y,
										b);
								if ((nextTileMetadata == null) || !nextTileMetadata.isNoData(
										new SampleIndex(
												x,
												y,
												b),
										sample)) {
									// we only need to recalculate metadata if
									// the raster is overwritten,
									// otherwise just use this raster's
									// metadata
									recalculateMetadata = true;
									thisRaster.setSample(
											x,
											y,
											b,
											sample);
								}
							}
						}
					}
				}
				if (recalculateMetadata) {
					if (nextTileMetadata == null) {
						LOGGER.error("Error merging raster tiles, nextTileMetadata was null.");
					}
					else {
						thisTile.setMetadata(NoDataMetadataFactory.mergeMetadata(
								thisTileMetadata,
								thisRaster,
								nextTileMetadata,
								nextRaster));

					}
				}
			}
		}
	}

	@Override
	public boolean equals(
			final Object obj ) {
		if (this == obj) {
			return true;
		}
		if (obj == null) {
			return false;
		}
		if (getClass() != obj.getClass()) {
			return false;
		}
		return true;
	}

	@Override
	public int hashCode() {
		return (int) serialVersionUID;
		// this looks correct based on behaviour of equals?!? should return the
		// same hash code for all instances
	}

	@Override
	public byte[] toBinary() {
		return new byte[] {};
	}

	@Override
	public void fromBinary(
			final byte[] bytes ) {}

	@Override
	public NoDataMetadata getMetadata(
			final GridCoverage tileGridCoverage,
			final RasterDataAdapter dataAdapter ) {
		if (tileGridCoverage instanceof FitToIndexGridCoverage) {
			return NoDataMetadataFactory.createMetadata(
					dataAdapter.getNoDataValuesPerBand(),
					((FitToIndexGridCoverage) tileGridCoverage).getFootprintScreenGeometry(),
					tileGridCoverage.getRenderedImage().getData());
		}
		return NoDataMetadataFactory.createMetadata(
				dataAdapter.getNoDataValuesPerBand(),
				null,
				tileGridCoverage.getRenderedImage().getData());
	}
}
