/*
 * RiskScape™ Copyright New Zealand Institute for Earth Science Limited
 * (Earth Sciences New Zealand) is distributed for research purposes only
 * under the terms of AGPLv3.
 *
 * RiskScape™ Copyright 2026 New Zealand Institute for Earth Science
 * Limited (Earth Sciences New Zealand). All rights reserved. Source code
 * available under the AGPLv3.
 * 
 * This program is free software: you can redistribute it and/or modify it under
 *  the terms of the GNU Affero General Public License as published by the Free
 *  Software Foundation, either version 3 of the License, or (at your option) any
 *  later version.
 * 
 * This program is distributed for RESEARCH PURPOSES ONLY, in the hope that it will
 * be useful for research and education initiatives.
 * 
 * If you are not a researcher, or you are a researcher who wishes to use this
 * program on terms other than AGPLv3 (including those who wish to restrict the
 * distribution of any source code created using this program), please contact:
 * https://riskscape.org.nz
 * 
 * This program is distributed WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Affero General Public License for more details.  You should have received a copy
 * of the GNU Affero General Public License along with this program.  If not, see
 * <http://www.gnu.org/licenses/>.
 * 
 * By way of summary only, under the AGPLv3:
 *     • Permissions of this strongest copyleft license are conditioned
 *       on making available complete source code of licensed works and
 *       modifications, which include larger works using a licensed work,
 *       under the same license.
 *     • Copyright and license notices must be preserved.
 *     • Contributors provide an express grant of patent rights.
 *     • When a modified version is used to provide a service over a
 *       network, the complete source code of the modified version must be made
 *       available.
 */
package nz.org.riskscape.engine.defaults.data;

import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;

import java.util.List;
import java.util.stream.Collectors;

import org.geotools.api.data.DataStore;
import org.geotools.api.referencing.crs.CoordinateReferenceSystem;
import org.junit.Test;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;

import nz.org.riskscape.engine.SRIDSet;
import nz.org.riskscape.engine.StructMatchers;
import nz.org.riskscape.engine.Tuple;
import nz.org.riskscape.engine.TypeMatchers;
import nz.org.riskscape.engine.relation.FeatureSourceRelation;
import nz.org.riskscape.engine.relation.Relation;
import nz.org.riskscape.engine.types.MultiGeom;
import nz.org.riskscape.engine.types.Struct;
import nz.org.riskscape.engine.types.Types;
import nz.org.riskscape.problem.ResultOrProblems;

/**
 * GeoPackage test for curved geometry types.
 *
 * This test is actually testing the RiskScape extensions {@link RsGeoPkgDialect} and {@link RsGeoPkgGeomReader}
 * that allow curved geometries to be read from a GeoPackage.
 *
 * We make this testing a little easier by testing via the {@link GeoPackageRelationResolver} but we
 * override it to no-op the wrap relation that would otherwise linearize the curved geometries.
 *
 * So in this test there will be curved geometries output.
 */
public class GeoPackageCurvedGeometriesRelationResolverTest extends BaseBookmarkResolverTest {

  public GeoPackageCurvedGeometriesRelationResolverTest() {
    super("geopackage", engine -> new GeoPackageRelationResolver(engine) {
      @Override
      protected ResultOrProblems<Relation> wrapRelation(FeatureSourceBookmarkParams params, DataStore dataStore,
          FeatureSourceRelation relation, CoordinateReferenceSystem crs) {
        // GeoPackageRelationResolver wraps the produced relation in a LinearGeometryProjection to convert
        // the curved geometries to linear. But we don't want that to happen for this tests
        return ResultOrProblems.of(relation);
      }

    });
  }

  @Test
  public void canReadCompoundCurve() {
    Relation relation = from("test.gpkg", ImmutableMap.of(
        "layer", "compound-curve",
        "linearize-tolerance", "0.01"
    )).get();
    Struct relationType = relation.getType();

    assertThat(relationType, StructMatchers.isStruct(Lists.newArrayList(
        StructMatchers.isStructMember("geom", TypeMatchers.isReferencedType(SRIDSet.EPSG4326_LONLAT, Types.LINE)),
        StructMatchers.isStructMember("name", Types.TEXT)
    )));

    List<Tuple> tuples = relation.iterator().collect(Collectors.toList());
    assertThat(tuples, contains(
        Tuple.ofValues(relationType, crs84Helper.compoundCurve(
            crs84Helper.line(10, 10, 20, 10)
        ), "line")
    ));
  }

  @Test
  public void canReadCurvePolygon() {
    Relation relation = from("test.gpkg", ImmutableMap.of(
        "layer", "curve-polygons",
        "linearize-tolerance", "0.01"
    )).get();
    Struct relationType = relation.getType();

    assertThat(relationType, StructMatchers.isStruct(Lists.newArrayList(
        StructMatchers.isStructMember("geom", TypeMatchers.isReferencedType(SRIDSet.EPSG4326_LONLAT, Types.POLYGON)),
        StructMatchers.isStructMember("name", Types.TEXT)
    )));

    List<Tuple> tuples = relation.iterator().collect(Collectors.toList());
    assertThat(tuples, contains(
        Tuple.ofValues(relationType, crs84Helper.curvePolygon(
            crs84Helper.ring(
                crs84Helper.toCoordinate(10, 10),
                crs84Helper.toCoordinate(10, 20),
                crs84Helper.toCoordinate(20, 20),
                crs84Helper.toCoordinate(20, 10),
                crs84Helper.toCoordinate(10, 10)
            )
        ), "polygon"),
        Tuple.ofValues(relationType, crs84Helper.curvePolygon(
            crs84Helper.ring(
                crs84Helper.toCoordinate(50, 50),
                crs84Helper.toCoordinate(50, 60),
                crs84Helper.toCoordinate(60, 60),
                crs84Helper.toCoordinate(60, 50),
                crs84Helper.toCoordinate(50, 50)
            ),
            crs84Helper.ring(
                crs84Helper.toCoordinate(55, 55),
                crs84Helper.toCoordinate(56, 55),
                crs84Helper.toCoordinate(56, 56),
                crs84Helper.toCoordinate(55, 56),
                crs84Helper.toCoordinate(55, 55)
            )
        ), "keyed-polygon")
    ));
  }

  @Test
  public void canReadMultiCurve() {
    Relation relation = from("test.gpkg", ImmutableMap.of(
        "layer", "multi-curve",
        "linearize-tolerance", "0.01"
    )).get();
    Struct relationType = relation.getType();

    assertThat(relationType, StructMatchers.isStruct(Lists.newArrayList(
        StructMatchers.isStructMember("geom",
            TypeMatchers.isReferencedType(SRIDSet.EPSG4326_LONLAT, MultiGeom.MULTI_LINE)),
        StructMatchers.isStructMember("name", Types.TEXT)
    )));

    List<Tuple> tuples = relation.iterator().collect(Collectors.toList());
    assertThat(tuples, contains(
        Tuple.ofValues(relationType, crs84Helper.multiCurve(
            crs84Helper.line(10, 10, 20, 10),
            crs84Helper.line(20, 20, 30, 20)
        ), "mline")
    ));
  }

  @Test
  public void canReadMultiSurface() {
    Relation relation = from("test.gpkg", ImmutableMap.of(
        "layer", "multi-surface",
        "linearize-tolerance", "0.01"
    )).get();
    Struct relationType = relation.getType();

    assertThat(relationType, StructMatchers.isStruct(Lists.newArrayList(
        StructMatchers.isStructMember("geom",
            TypeMatchers.isReferencedType(SRIDSet.EPSG4326_LONLAT, MultiGeom.MULTI_POLYGON)),
        StructMatchers.isStructMember("name", Types.TEXT)
    )));

    List<Tuple> tuples = relation.iterator().collect(Collectors.toList());
    assertThat(tuples, contains(
        Tuple.ofValues(relationType, crs84Helper.multiSurface(
            crs84Helper.box(
                crs84Helper.ring(
                    crs84Helper.toCoordinate(10, 10),
                    crs84Helper.toCoordinate(10, 20),
                    crs84Helper.toCoordinate(20, 20),
                    crs84Helper.toCoordinate(20, 10),
                    crs84Helper.toCoordinate(10, 10)
                )
            ),
            crs84Helper.box(
                crs84Helper.ring(
                    crs84Helper.toCoordinate(50, 50),
                    crs84Helper.toCoordinate(50, 60),
                    crs84Helper.toCoordinate(60, 60),
                    crs84Helper.toCoordinate(60, 50),
                    crs84Helper.toCoordinate(50, 50)
                )
            )
        ), "mpolygon"),
        Tuple.ofValues(relationType, crs84Helper.multiSurface(
            crs84Helper.box(
                crs84Helper.ring(
                    crs84Helper.toCoordinate(10, 10),
                    crs84Helper.toCoordinate(10, 20),
                    crs84Helper.toCoordinate(20, 20),
                    crs84Helper.toCoordinate(20, 10),
                    crs84Helper.toCoordinate(10, 10)
                )),
            crs84Helper.box(
                crs84Helper.ring(
                    crs84Helper.toCoordinate(50, 50),
                    crs84Helper.toCoordinate(50, 60),
                    crs84Helper.toCoordinate(60, 60),
                    crs84Helper.toCoordinate(60, 50),
                    crs84Helper.toCoordinate(50, 50)
                ),
                crs84Helper.ring(
                    crs84Helper.toCoordinate(55, 55),
                    crs84Helper.toCoordinate(56, 55),
                    crs84Helper.toCoordinate(56, 56),
                    crs84Helper.toCoordinate(55, 56),
                    crs84Helper.toCoordinate(55, 55)
                )
            )
        ), "keyed-mpolygon")
    ));
  }

}
