/*
 * 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.map.coverage;

import java.util.List;
import java.awt.image.RenderedImage;
import org.opengis.geometry.DirectPosition;
import org.opengis.referencing.datum.PixelInCell;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.TransformException;
import org.apache.sis.referencing.operation.transform.LinearTransform;
import org.apache.sis.referencing.operation.transform.MathTransforms;
import org.apache.sis.coverage.SampleDimension;
import org.apache.sis.coverage.grid.GridExtent;
import org.apache.sis.coverage.grid.GridGeometry;
import org.apache.sis.coverage.grid.GridCoverage;
import org.apache.sis.storage.AbstractGridCoverageResource;
import org.apache.sis.storage.DataStoreException;
import org.apache.sis.util.iso.Names;

// Test dependencies
import org.junit.Test;
import org.apache.sis.test.TestCase;

import static org.junit.Assert.*;


/**
 * Test {@link MultiResolutionCoverageLoader}.
 *
 * @author  Martin Desruisseaux (Geomatys)
 */
public final class MultiResolutionCoverageLoaderTest extends TestCase {
    /**
     * The loader being tested.
     */
    private final MultiResolutionCoverageLoader loader;

    /**
     * Transform from data CRS to the CRS for rendering, or {@code null} if none.
     */
    private MathTransform dataToObjective;

    /**
     * Point where to compute resolution, in coordinates of objective CRS.
     * Can be null if {@link #dataToObjective} is null or linear.
     */
    private DirectPosition objectivePOI;

    /**
     * Verifies that a transform with the given scale factors result in the given level to be found.
     */
    private void assertLevelEquals(final double sx, final double sy, final double sz, final int expected)
            throws TransformException
    {
        final LinearTransform objectiveToDisplay = MathTransforms.scale(1/sx, 1/sy, 1/sz);
        final int level = loader.findPyramidLevel(dataToObjective, objectiveToDisplay, objectivePOI);
        assertEquals(expected, level);
    }

    /**
     * Verifies that loading a coverage at the specified level result in a grid coverage
     * with the given scale factors.
     */
    private void assertLoadEquals(final int level, final double sx, final double sy, final double sz)
            throws DataStoreException
    {
        final GridCoverage coverage = loader.getOrLoad(level);
        final MathTransform gridToCRS = coverage.getGridGeometry().getGridToCRS(PixelInCell.CELL_CORNER);
        final MathTransform expected = MathTransforms.scale(sx, sy, sz);
        assertEquals(expected, gridToCRS);
        assertSame(coverage, loader.getOrLoad(level));
    }

    /**
     * Creates a new test case with a loader for a dummy resource.
     *
     * @throws DataStoreException if an error occurred while querying the dummy resource.
     */
    public MultiResolutionCoverageLoaderTest() throws DataStoreException {
        loader = new MultiResolutionCoverageLoader(new DummyResource(), null, null);
    }

    /**
     * A dummy resource with arbitrary resolutions for testing purpose.
     * Resolutions are ordered from finest (smallest numbers) to coarsest (largest numbers).
     */
    private static final class DummyResource extends AbstractGridCoverageResource {
        /** Creates a dummy resource. */
        DummyResource() {
            super(null, false);
        }

        /** Returns the preferred resolutions in units of CRS axes. */
        @Override public List<double[]> getResolutions() {
            return List.of(new double[] {2, 3, 1},
                           new double[] {4, 4, 3},
                           new double[] {8, 9, 5});
        }

        /** Returns a grid geometry with the resolution of finest level. */
        @Override public GridGeometry getGridGeometry() {
            return new GridGeometry(new GridExtent(null, null, new long[] {10, 10, 10}, true),
                                PixelInCell.CELL_CORNER, MathTransforms.scale(2, 3, 1), null);
        }

        /** Not needed for this test. */
        @Override public List<SampleDimension> getSampleDimensions() {
            throw new UnsupportedOperationException();
        }

        /** Returns a dummy value (will not be used by this test). */
        @Override public GridCoverage read(final GridGeometry domain, final int... ranges) {
            final SampleDimension band = new SampleDimension(Names.createLocalName(null, null, "dummy"), null, List.of());
            return new GridCoverage(domain, List.of(band)) {
                @Override public RenderedImage render(GridExtent sliceExtent) {
                    throw new UnsupportedOperationException();                      // Not needed by this test.
                }
            };
        }
    }

    /**
     * Tests {@link MultiResolutionCoverageLoader#findPyramidLevel(MathTransform, LinearTransform, DirectPosition)}
     * with no "data to objective" transform.
     *
     * @throws TransformException if an error occurred while computing the resolution from a transform.
     */
    @Test
    public void testFindPyramidLevel() throws TransformException {
        assertLevelEquals(3, 2, 2, 0);
        assertLevelEquals(4, 5, 2, 0);
        assertLevelEquals(4, 5, 4, 1);
        assertLevelEquals(9, 9, 5, 2);
        assertLevelEquals(9, 8, 5, 1);
    }

    /**
     * Tests {@link MultiResolutionCoverageLoader#findPyramidLevel(MathTransform, LinearTransform, DirectPosition)}
     * with a "data to objective" transform set to a translation. Because translation has no effect on scale factors,
     * the result should be identical to {@link #testFindPyramidLevel()}.
     *
     * @throws TransformException if an error occurred while computing the resolution from a transform.
     */
    @Test
    public void testFindWithTranslation() throws TransformException {
        dataToObjective = MathTransforms.translation(-5, 7, 3);
        testFindPyramidLevel();
    }

    /**
     * Tests {@link MultiResolutionCoverageLoader#getOrLoad(int)}.
     *
     * @throws DataStoreException if an error occurred while querying the dummy resource.
     */
    @Test
    public void testGetOrLoad() throws DataStoreException {
        assertLoadEquals(2, 8, 9, 5);
        assertLoadEquals(0, 2, 3, 1);
        assertLoadEquals(1, 4, 4, 3);
    }
}
