/*
 * 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.net.URI;
import java.util.Arrays;

import org.junit.Test;
import org.locationtech.jts.geom.Polygon;

import com.google.common.collect.ImmutableMap;

import nz.org.riskscape.engine.RelationMatchers;
import nz.org.riskscape.engine.Tuple;
import nz.org.riskscape.engine.output.KmlFormat;
import nz.org.riskscape.engine.relation.Relation;
import nz.org.riskscape.engine.types.Nullable;
import nz.org.riskscape.engine.types.Referenced;
import nz.org.riskscape.engine.types.Struct;
import nz.org.riskscape.engine.types.Types;

/**
 * Tests that RiskScape can read the kml that it has produced.
 */
public class KmlIntegrationTest extends BaseFormatIntegrationTest {

  public KmlIntegrationTest() {
    super(new KmlFormat(), (eng) -> new KmlResolver(eng));
  }

  @Test
  public void writeReadWtihExtendedDataOnly() {
    // these attributes don't fit in the standard kml attributes, so they are all put in extended data
    Struct writeType = Struct.of("geom", Types.GEOMETRY, "foo", Types.TEXT, "bar", Types.INTEGER);
    URI written = write(
        Tuple.ofValues(writeType, crs84Helper.point(10, 20), "foo1", 10L),
        Tuple.ofValues(writeType, crs84Helper.point(15, 25), "foo2", 15L)
    );

    Relation read = from(bookmark(written)).get();
    Struct expectedType = baseKmlType()
        .and("foo", Types.TEXT)
        .and("bar", Types.INTEGER);
    assertThat(read.getType(), is(expectedType));

    assertThat(read, RelationMatchers.withTuples(Arrays.asList(
        Tuple.ofValues(expectedType, crs84Helper.point(10, 20), null, true, true, null, null, null, null, null,
            "foo1", 10L),
        Tuple.ofValues(expectedType, crs84Helper.point(15, 25), null, true, true, null, null, null, null, null,
            "foo2", 15L)

    )));
  }

  @Test
  public void writeReadWithKmlAttributes() {
    // these attributes are known in the KML2.2 Feature
    Struct type = Struct.of("Geometry", Referenced.of(Types.GEOMETRY, crs84Helper.getCrs()))
        .and("name", Types.TEXT)
        .and("visibility", Types.BOOLEAN)
        .and("open", Types.BOOLEAN)
        .and("address", Types.TEXT)
        .and("phoneNumber", Types.TEXT)
        .and("description", Types.TEXT)
        .and("LookAt", Referenced.of(Types.GEOMETRY, crs84Helper.getCrs()))
        .and("Region", Referenced.of(Types.GEOMETRY, crs84Helper.getCrs()));

    Tuple foo = Tuple.ofValues(type, crs84Helper.point(10, 20), "foo", false, true, "foo address",
        "foo phone", "foo foo");
    Tuple bar = Tuple.ofValues(type, crs84Helper.point(10, 20), "bar", true, false, "bar address",
        "bar phone", "bar bar");

    URI written = write(foo, bar);

    Relation read = from(bookmark(written)).get();
    assertThat(read.getType(), is(type));
    assertThat(read, RelationMatchers.relationWithTuples(foo, bar));
  }

  @Test
  public void writeReadStandardAndExtendedData() {
    // these attributes are known in the KML2.2 Feature
    Struct type = Struct.of("Geometry", Referenced.of(Types.GEOMETRY, crs84Helper.getCrs()))
        .and("custom1", Types.TEXT)
        .and("name", Types.TEXT)
        .and("visibility", Types.BOOLEAN)
        .and("open", Types.BOOLEAN)
        .and("address", Types.TEXT)
        .and("custom2", Types.TEXT)
        .and("phoneNumber", Types.TEXT)
        .and("description", Types.TEXT);

    Tuple foo = Tuple.ofValues(type, crs84Helper.point(10, 20), "foo-custom1", "foo", false, true, "foo address",
        "foo-custom2", "foo phone", "foo foo");
    Tuple bar = Tuple.ofValues(type, crs84Helper.point(10, 20), "bar-custom1", "bar", true, false, "bar address",
        "bar-custom2", "bar phone", "bar bar");

    URI written = write(foo, bar);

    // our expected type is mostly the same, only difference is that the custom fields have been moved
    // to the end.
    Struct expectedType = baseKmlType()
        .and("custom1", Types.TEXT)
        .and("custom2", Types.TEXT);

    Relation read = from(bookmark(written)).get();
    assertThat(read.getType(), is(expectedType));

    assertThat(read, RelationMatchers.relationWithTuples(
        Tuple.ofValues(expectedType, crs84Helper.point(10, 20), "foo", false, true, "foo address",
        "foo phone", "foo foo", null, null, "foo-custom1", "foo-custom2"),
        Tuple.ofValues(expectedType, crs84Helper.point(10, 20), "bar", true, false, "bar address",
        "bar phone", "bar bar", null, null, "bar-custom1", "bar-custom2")
    ));
  }

   @Test
  public void writeReadWtihDifferentGeometryTypes() {
    Struct writeType = Struct.of("Geometry", Referenced.of(Types.GEOMETRY, crs84Helper.getCrs()),
        "name", Types.TEXT);

    Tuple point = Tuple.ofValues(writeType, crs84Helper.point(10, 15), "pointy");
    Tuple line = Tuple.ofValues(writeType, crs84Helper.line(20, 25, 40, 30), "line");
    Polygon bigBox = crs84Helper.box(0, 0, 90, 90);
    Tuple simpleBox = Tuple.ofValues(writeType, bigBox, "box");

    Polygon hole1 = crs84Helper.box(5, 5, 10, 10);
    Polygon hole2 = crs84Helper.box(20, 20, 25, 25);
    Polygon boxWithHoles = crs84Helper.box(bigBox.getExteriorRing(), hole1.getExteriorRing(), hole2.getExteriorRing());
    Tuple complexBox = Tuple.ofValues(writeType, boxWithHoles, "box with holes");

    Tuple multiGeom = Tuple.ofValues(writeType, crs84Helper.multiBox(hole1, hole2), "multibox");

    URI written = write(
        point, line, simpleBox, complexBox, multiGeom
    );

    Relation read = from(bookmark(written,
        ImmutableMap.of("type", "struct(Geometry: geometry, name: text)"))).get();
    assertThat(read.getType(), is(writeType));

    assertThat(read, RelationMatchers.withTuples(Arrays.asList(
        point, line, simpleBox, complexBox, multiGeom
    )));
  }

  @Test
  public void writeReadWithNullableProperties() {
    Struct type = Struct.of("Geometry", Referenced.of(Types.GEOMETRY, crs84Helper.getCrs()),
        "foo", Nullable.TEXT, "bar", Nullable.INTEGER, "baz", Nullable.FLOATING);

    Tuple t1 = Tuple.ofValues(type, crs84Helper.point(10, 10), "foo", 10L, 12.2D);
    Tuple t2 = Tuple.ofValues(type, crs84Helper.point(10, 10), null, 10L, 12.2D);
    Tuple t3 = Tuple.ofValues(type, crs84Helper.point(10, 10), "foo", null, 12.2D);
    Tuple t4 = Tuple.ofValues(type, crs84Helper.point(10, 10), "foo", 10L, null);
    Tuple t5 = Tuple.ofValues(type, crs84Helper.point(10, 10), null, null, null);

    URI written = write(
        t1, t2, t3, t4, t5
    );

    Relation read = from(bookmark(written, ImmutableMap.of("type", "struct(Geometry: geometry, "
        + "foo: nullable(text), bar: nullable(integer), baz: nullable(floating))"))).get();
    assertThat(read.getType(), is(type));

    assertThat(read, RelationMatchers.withTuples(Arrays.asList(
        t1, t2, t3, t4, t5
    )));
  }

  /**
   * The KML schema has a bunch of attributes that are always present in the read type.
   */
  private Struct baseKmlType() {
    return Struct.of("Geometry", Referenced.of(Types.GEOMETRY, crs84Helper.getCrs()))
        .and("name", Types.TEXT)
        .and("visibility", Types.BOOLEAN)
        .and("open", Types.BOOLEAN)
        .and("address", Types.TEXT)
        .and("phoneNumber", Types.TEXT)
        .and("description", Types.TEXT)
        .and("LookAt", Referenced.of(Types.GEOMETRY, crs84Helper.getCrs()))
        .and("Region", Referenced.of(Types.GEOMETRY, crs84Helper.getCrs()));
  }

}
