/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.sis.coverage.grid;

import java.util.Arrays;
import java.awt.image.Raster;
import java.awt.image.DataBufferInt;
import org.apache.sis.coverage.SampleDimension;
import org.apache.sis.referencing.operation.transform.MathTransforms;

// Test dependencies
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import org.apache.sis.test.TestCase;
import org.apache.sis.referencing.crs.HardCodedCRS;


/**
 * Tests {@link BandAggregateGridCoverage}.
 *
 * @author  Martin Desruisseaux (Geomatys)
 */
public final class BandAggregateGridCoverageTest extends TestCase {
    /**
     * Width and height of images created for tests.
     */
    private static final int WIDTH = 3, HEIGHT = 2;

    /**
     * The processor to use for creating the aggregated grid coverage.
     */
    private final GridCoverageProcessor processor;

    /**
     * Creates a new test case.
     */
    public BandAggregateGridCoverageTest() {
        processor = new GridCoverageProcessor();
    }

    /**
     * Tests aggregation with two coverages having the same grid geometry.
     */
    @Test
    public void testSameGridGeometry() {
        final GridCoverage c1 = createCoverage(-2, 4, 3, -1, 100, 200);
        final GridCoverage c2 = createCoverage(-2, 4, 3, -1, 300);
        final GridCoverage cr = processor.aggregateRanges(c1, c2);
        assertEquals(c1.getGridGeometry(), cr.getGridGeometry());
        assertEquals(c2.getGridGeometry(), cr.getGridGeometry());
        assertEquals(3, cr.getSampleDimensions().size());
        assertPixelsEqual(cr, 100, 200, 300, 101, 201, 301, 102, 202, 302,
                              103, 203, 303, 104, 204, 304, 105, 205, 305);
    }

    /**
     * Tests aggregation with two coverages having a translation in their grid extents.
     * Their "grid to CRS" transforms are the same, which implies that the "real world"
     * coordinates are different. The intersection is not equal to any source extent.
     */
    @Test
    public void testDifferentExtent() {
        final GridCoverage c1 = createCoverage(-2, 4, 3, -1, 100, 200);
        final GridCoverage c2 = createCoverage(-1, 3, 3, -1, 300);
        final GridCoverage cr = processor.aggregateRanges(c1, c2);
        final GridExtent extent = cr.getGridGeometry().getExtent();
        assertNotEquals(c1.getGridGeometry().getExtent(), extent);
        assertNotEquals(c2.getGridGeometry().getExtent(), extent);
        assertEquals(extent(-1, 4, 1, 5), extent);
        assertEquals(3, cr.getSampleDimensions().size());
        assertPixelsEqual(cr, 101, 201, 303, 102, 202, 304);
    }

    /**
     * Tests aggregation with two coverages having equivalent extent but different "grid to CRS".
     * This test indirectly verifies that the {@link BandAggregateGridCoverage#gridTranslations}
     * array is correctly computed and used.
     */
    @Test
    public void testDifferentGridToCRS() {
        final GridCoverage c1 = createCoverage(-2, 4, 3, -1, 100, 200);
        final GridCoverage c2 = createCoverage(-1, 2, 2, +1, 300);
        final GridCoverage cr = processor.aggregateRanges(c1, c2);
        assertEquals   (c1.getGridGeometry(), cr.getGridGeometry());
        assertNotEquals(c2.getGridGeometry(), cr.getGridGeometry());
        assertEquals   (3, cr.getSampleDimensions().size());
        assertPixelsEqual(cr, 100, 200, 300, 101, 201, 301, 102, 202, 302,
                              103, 203, 303, 104, 204, 304, 105, 205, 305);
    }

    /**
     * Tests aggregation with two coverages having a translation in both grid extents and "grid to CRS" transforms.
     */
    @Test
    public void testDifferentExtentAndGridToCRS() {
        final GridCoverage c1 = createCoverage(-2, 4, 3, -1, 100, 200);
        final GridCoverage c2 = createCoverage( 0, 2, 2, +1, 300);
        final GridCoverage cr = processor.aggregateRanges(c1, c2);
        assertNotEquals(c1.getGridGeometry(), cr.getGridGeometry());
        assertNotEquals(c2.getGridGeometry(), cr.getGridGeometry());
        assertEquals   (3, cr.getSampleDimensions().size());
        assertPixelsEqual(cr, 101, 201, 300, 102, 202, 301,
                              104, 204, 303, 105, 205, 304);
    }

    /**
     * Tests aggregation of two coverages where one of them is itself another aggregation.
     */
    @Test
    public void testNestedAggregation() {
        final GridCoverage c1 = createCoverage(-2, 4, 3, -1, 100, 200);
        final GridCoverage c2 = createCoverage( 0, 2, 2, +1, 300);
        final GridCoverage c3 = createCoverage(-2, 4, 3, -1, 400);
        final GridCoverage cr = processor.aggregateRanges(
                                processor.aggregateRanges(c1, c2), c3);
        assertPixelsEqual(cr, 101, 201, 300, 401, 102, 202, 301, 402,
                              104, 204, 303, 404, 105, 205, 304, 405);
    }

    /**
     * Returns a two-dimensional grid extents with the given bounding box.
     * The maximal coordinates are exclusive.
     */
    private static GridExtent extent(final int minX, final int minY, final int maxX, final int maxY) {
        return new GridExtent(null, new long[] {minX, minY}, new long[] {maxX, maxY}, false);
    }

    /**
     * Creates a new grid coverage with bands starting with the given sample values.
     * The length of the {@code bandValues} array is the number of bands to create.
     * In a given band <var>b</var>, all pixels have the {@code bandValues[b]}.
     *
     * @param  minX        minimal <var>x</var> coordinate value of the grid extent.
     * @param  minY        minimal <var>y</var> coordinate value of the grid extent.
     * @param  translateX  <var>x</var> component of the "grid to CRS" translation.
     * @param  translateY  <var>y</var> component of the "grid to CRS" translation.
     * @param  bandValues  sample values for the first pixel.
     * @return a coverage with an image where all pixels have the specified sample values.
     */
    private static GridCoverage createCoverage(final int minX, final int minY,
            final int translateX, final int translateY, final int... bandValues)
    {
        final int numBands = bandValues.length;
        final var samples  = new SampleDimension[numBands];
        final var builder  = new SampleDimension.Builder();
        for (int i=0; i<numBands; i++) {
            samples[i] = builder.setName("Band with value " + bandValues[i]).build();
        }
        final int[] data = new int[WIDTH * HEIGHT * numBands];
        for (int i=0; i<data.length; i++) {
            data[i] = bandValues[i % numBands] + i / numBands;
        }
        final var values = new DataBufferInt(data, data.length);
        final var domain = new GridGeometry(extent(minX, minY, minX+WIDTH, minY+HEIGHT),
                PixelInCell.CELL_CORNER, MathTransforms.translation(translateX, translateY), HardCodedCRS.WGS84);
        return new BufferedGridCoverage(domain, Arrays.asList(samples), values);
    }

    /**
     * Asserts that all pixels from the given coverage have the expected values.
     */
    private static void assertPixelsEqual(final GridCoverage cr, final int... expected) {
        final Raster r = cr.render(null).getData();
        final int[] data = r.getPixels(r.getMinX(), r.getMinY(), r.getWidth(), r.getHeight(), (int[]) null);
        assertArrayEquals(expected, data);
    }
}
