/*
 * Copyright DataStax, Inc.
 *
 * Licensed 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 com.datastax.dse.driver.api.core.graph;

import static com.datastax.dse.driver.api.core.graph.TinkerGraphAssertions.assertThat;

import com.datastax.dse.driver.api.core.data.geometry.LineString;
import com.datastax.dse.driver.api.core.data.geometry.Point;
import com.datastax.dse.driver.api.core.data.geometry.Polygon;
import com.datastax.oss.driver.api.core.CqlSession;
import com.datastax.oss.driver.api.core.Version;
import com.datastax.oss.driver.api.testinfra.ccm.CcmBridge;
import com.datastax.oss.driver.api.testinfra.session.SessionRule;
import com.datastax.oss.driver.shaded.guava.common.base.Charsets;
import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableSet;
import com.datastax.oss.driver.shaded.guava.common.net.InetAddresses;
import com.tngtech.java.junit.dataprovider.DataProvider;
import com.tngtech.java.junit.dataprovider.DataProviderRunner;
import com.tngtech.java.junit.dataprovider.UseDataProvider;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalTime;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.tinkerpop.gremlin.structure.Vertex;
import org.junit.AssumptionViolatedException;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(DataProviderRunner.class)
public abstract class ClassicGraphDataTypeITBase {

  private static final boolean IS_DSE50 =
      CcmBridge.VERSION.compareTo(Objects.requireNonNull(Version.parse("5.1"))) < 0;
  private static final Set<String> TYPES_REQUIRING_DSE51 =
      ImmutableSet.of("Date()", "Time()", "Point()", "Linestring()", "Polygon()");

  private static final AtomicInteger SCHEMA_COUNTER = new AtomicInteger();

  @DataProvider
  public static Object[][] typeSamples() {
    return new Object[][] {
      // Types that DSE supports.
      {"Boolean()", true},
      {"Boolean()", false},
      {"Smallint()", Short.MAX_VALUE},
      {"Smallint()", Short.MIN_VALUE},
      {"Smallint()", (short) 0},
      {"Smallint()", (short) 42},
      {"Int()", Integer.MAX_VALUE},
      {"Int()", Integer.MIN_VALUE},
      {"Int()", 0},
      {"Int()", 42},
      {"Bigint()", Long.MAX_VALUE},
      {"Bigint()", Long.MIN_VALUE},
      {"Bigint()", 0L},
      {"Double()", Double.MAX_VALUE},
      {"Double()", Double.MIN_VALUE},
      {"Double()", 0.0d},
      {"Double()", Math.PI},
      {"Float()", Float.MAX_VALUE},
      {"Float()", Float.MIN_VALUE},
      {"Float()", 0.0f},
      {"Text()", ""},
      {"Text()", "75"},
      {"Text()", "Lorem Ipsum"},
      // Inet, UUID, Date
      {"Inet()", InetAddresses.forString("127.0.0.1")},
      {"Inet()", InetAddresses.forString("0:0:0:0:0:0:0:1")},
      {"Inet()", InetAddresses.forString("2001:db8:85a3:0:0:8a2e:370:7334")},
      {"Uuid()", UUID.randomUUID()},
      // Timestamps
      {"Timestamp()", Instant.ofEpochMilli(123)},
      {"Timestamp()", Instant.ofEpochMilli(1488313909)},
      {"Duration()", java.time.Duration.parse("P2DT3H4M")},
      {"Date()", LocalDate.of(2016, 5, 12)},
      {"Time()", LocalTime.parse("18:30:41.554")},
      {"Time()", LocalTime.parse("18:30:41.554010034")},
      // Blob
      {"Blob()", "Hello World!".getBytes(Charsets.UTF_8)},
      // BigDecimal/BigInteger
      {"Decimal()", new BigDecimal("8675309.9998")},
      {"Varint()", new BigInteger("8675309")},
      // Geospatial types
      {"Point().withBounds(-2, -2, 2, 2)", Point.fromCoordinates(0, 1)},
      {"Point().withBounds(-40, -40, 40, 40)", Point.fromCoordinates(-5, 20)},
      {
        "Linestring().withGeoBounds()",
        LineString.fromPoints(
            Point.fromCoordinates(30, 10),
            Point.fromCoordinates(10, 30),
            Point.fromCoordinates(40, 40))
      },
      {
        "Polygon().withGeoBounds()",
        Polygon.builder()
            .addRing(
                Point.fromCoordinates(35, 10),
                Point.fromCoordinates(45, 45),
                Point.fromCoordinates(15, 40),
                Point.fromCoordinates(10, 20),
                Point.fromCoordinates(35, 10))
            .addRing(
                Point.fromCoordinates(20, 30),
                Point.fromCoordinates(35, 35),
                Point.fromCoordinates(30, 20),
                Point.fromCoordinates(20, 30))
            .build()
      }
    };
  }

  @UseDataProvider("typeSamples")
  @Test
  public void should_create_and_retrieve_vertex_property_with_correct_type(
      String type, Object value) {
    if (IS_DSE50 && requiresDse51(type)) {
      throw new AssumptionViolatedException(type + " not supported in DSE " + CcmBridge.VERSION);
    }

    int id = SCHEMA_COUNTER.getAndIncrement();

    String vertexLabel = "vertex" + id;
    String propertyName = "prop" + id;
    GraphStatement<?> addVertexLabelAndProperty =
        ScriptGraphStatement.builder(
                "schema.propertyKey(property)."
                    + type
                    + ".create()\n"
                    + "schema.vertexLabel(vertexLabel).properties(property).create()")
            .setQueryParam("vertexLabel", vertexLabel)
            .setQueryParam("property", propertyName)
            .build();

    session().execute(addVertexLabelAndProperty);

    Vertex v = insertVertexAndReturn(vertexLabel, propertyName, value);

    assertThat(v).hasProperty(propertyName, value);
  }

  private boolean requiresDse51(String type) {
    for (String prefix : TYPES_REQUIRING_DSE51) {
      if (type.startsWith(prefix)) {
        return true;
      }
    }
    return false;
  }

  public abstract Vertex insertVertexAndReturn(
      String vertexLabel, String propertyName, Object value);

  /**
   * Note that the {@link SessionRule} (and setupSchema method) must be redeclared in each subclass,
   * since it depends on the CCM rule that can't be shared across serial tests.
   */
  public abstract CqlSession session();
}
