/*
 * 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 com.datastax.driver.extras.codecs.date;

import static com.google.common.collect.Lists.newArrayList;
import static org.assertj.core.api.Assertions.assertThat;

import com.datastax.driver.core.BoundStatement;
import com.datastax.driver.core.CCMTestsSupport;
import com.datastax.driver.core.CodecRegistry;
import com.datastax.driver.core.LocalDate;
import com.datastax.driver.core.ResultSet;
import com.datastax.driver.core.Row;
import com.datastax.driver.core.utils.CassandraVersion;
import com.datastax.driver.mapping.Mapper;
import com.datastax.driver.mapping.MappingManager;
import com.datastax.driver.mapping.annotations.Column;
import com.datastax.driver.mapping.annotations.PartitionKey;
import com.datastax.driver.mapping.annotations.Table;
import com.google.common.collect.ImmutableMap;
import java.util.Date;
import java.util.List;
import java.util.Map;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;

@CassandraVersion("2.2.0")
public class SimpleDateCodecsTest extends CCMTestsSupport {

  @Override
  public void onTestContextInitialized() {
    execute(
        "CREATE TABLE IF NOT EXISTS foo ("
            + "c1 text PRIMARY KEY, "
            + "cdate date, "
            + "ctimestamp timestamp, "
            + "cdates frozen<list<date>>, "
            + "ctimestamps frozen<map<text,timestamp>> "
            + ")");
  }

  @BeforeClass(groups = "short")
  public void registerCodecs() throws Exception {
    CodecRegistry codecRegistry = cluster().getConfiguration().getCodecRegistry();
    codecRegistry.register(SimpleDateCodec.instance).register(SimpleTimestampCodec.instance);
  }

  /**
   * Validates that a <code>date</code> column can be mapped to an int by using {@link
   * SimpleDateCodec}.
   *
   * @test_category data_types:serialization
   * @expected_result properly maps.
   * @since 2.2.0
   */
  @Test(groups = "short")
  public void should_map_date_to_days_since_epoch() {
    // given
    int days = 12345;
    LocalDate localDate = LocalDate.fromDaysSinceEpoch(days);
    // when
    // note: these codecs cannot work with simple statements!
    BoundStatement bs =
        session()
            .prepare("insert into foo (c1, cdate) values (?, ?)")
            .bind("should_map_date_to_days_since_epoch", days);
    session().execute(bs);
    ResultSet result =
        session()
            .execute("select cdate from foo where c1=?", "should_map_date_to_days_since_epoch");
    // then
    assertThat(result.getAvailableWithoutFetching()).isEqualTo(1);
    Row row = result.one();
    assertThat(row.getInt("cdate")).isEqualTo(days);
    assertThat(row.get("cdate", Integer.class)).isEqualTo(days);
    assertThat(row.getDate("cdate")).isEqualTo(localDate);
    assertThat(row.get("cdate", LocalDate.class)).isEqualTo(localDate);
  }

  /**
   * Validates that a <code>timestamp</code> column can be mapped to a long by using {@link
   * SimpleTimestampCodec}.
   *
   * @test_category data_types:serialization
   * @expected_result properly maps.
   * @since 2.2.0
   */
  @Test(groups = "short")
  public void should_map_timestamp_to_millis_since_epoch() {
    // given
    long millis = new Date().getTime();
    Date date = new Date(millis);
    // when
    // note: these codecs cannot work with simple statements!
    BoundStatement bs =
        session()
            .prepare("insert into foo (c1, ctimestamp) values (?, ?)")
            .bind("should_map_timestamp_to_millis_since_epoch", millis);
    session().execute(bs);
    ResultSet result =
        session()
            .execute(
                "select ctimestamp from foo where c1=?",
                "should_map_timestamp_to_millis_since_epoch");
    // then
    assertThat(result.getAvailableWithoutFetching()).isEqualTo(1);
    Row row = result.one();
    assertThat(row.getLong("ctimestamp")).isEqualTo(millis);
    assertThat(row.get("ctimestamp", Long.class)).isEqualTo(millis);
    assertThat(row.getTimestamp("ctimestamp")).isEqualTo(date);
    assertThat(row.get("ctimestamp", Date.class)).isEqualTo(date);
  }

  @Test(groups = "short")
  public void should_use_mapper_to_store_and_retrieve_values_with_simple_date_codecs() {
    // given
    MappingManager manager = new MappingManager(session());
    Mapper<Mapped> mapper = manager.mapper(Mapped.class);
    // when
    Mapped pojo = new Mapped();
    mapper.save(pojo);
    Mapped actual = mapper.get("mapper");
    // then
    assertThat(actual).isEqualToComparingFieldByField(pojo);
  }

  @SuppressWarnings("unused")
  @Table(name = "foo")
  public static class Mapped {

    @PartitionKey private String c1;

    @Column(name = "ctimestamp")
    private long millis;

    @Column(name = "cdate")
    private int days;

    @Column(name = "ctimestamps")
    private Map<String, Long> mapOfMillis;

    @Column(name = "cdates")
    private List<Integer> listOfDays;

    public Mapped() {
      c1 = "mapper";
      millis = 123456;
      days = 123;
      mapOfMillis = ImmutableMap.of("foo", 123456L);
      listOfDays = newArrayList(123, 456);
    }

    public String getC1() {
      return c1;
    }

    public void setC1(String c1) {
      this.c1 = c1;
    }

    public long getMillis() {
      return millis;
    }

    public void setMillis(long millis) {
      this.millis = millis;
    }

    public int getDays() {
      return days;
    }

    public void setDays(int days) {
      this.days = days;
    }

    public Map<String, Long> getMapOfMillis() {
      return mapOfMillis;
    }

    public void setMapOfMillis(Map<String, Long> mapOfMillis) {
      this.mapOfMillis = mapOfMillis;
    }

    public List<Integer> getListOfDays() {
      return listOfDays;
    }

    public void setListOfDays(List<Integer> listOfDays) {
      this.listOfDays = listOfDays;
    }
  }
}
