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

import static com.datastax.driver.core.Assertions.assertThat;
import static com.datastax.driver.core.DataType.cint;
import static com.datastax.driver.core.DataType.map;
import static org.assertj.core.api.Assertions.entry;

import com.datastax.driver.core.utils.CassandraVersion;
import org.testng.annotations.Test;

@CassandraVersion("2.2.0")
@CCMConfig(config = "enable_user_defined_functions:true")
public class FunctionMetadataTest extends CCMTestsSupport {

  @Test(groups = "short")
  public void should_parse_and_format_simple_function() {
    // given
    String cql =
        String.format(
            "CREATE FUNCTION %s.plus(s int,v int) RETURNS NULL ON NULL INPUT RETURNS int LANGUAGE java AS 'return s+v;';",
            keyspace);
    // when
    session().execute(cql);
    // then
    KeyspaceMetadata keyspace = cluster().getMetadata().getKeyspace(this.keyspace);
    FunctionMetadata function = keyspace.getFunction("plus", cint(), cint());
    assertThat(function).isNotNull();
    assertThat(function.getKeyspace()).isEqualTo(keyspace);
    assertThat(function.getSignature()).isEqualTo("plus(int,int)");
    assertThat(function.getSimpleName()).isEqualTo("plus");
    assertThat(function.getReturnType()).isEqualTo(cint());
    assertThat(function.getArguments()).containsEntry("s", cint()).containsEntry("v", cint());
    assertThat(function.getLanguage()).isEqualTo("java");
    assertThat(function.getBody()).isEqualTo("return s+v;");
    assertThat(function.isCalledOnNullInput()).isFalse();
    assertThat(function.toString()).isEqualTo(cql);
    assertThat(function.exportAsString())
        .isEqualTo(
            String.format(
                "CREATE FUNCTION %s.plus(s int,v int)\n"
                    + "    RETURNS NULL ON NULL INPUT\n"
                    + "    RETURNS int\n"
                    + "    LANGUAGE java\n"
                    + "    AS 'return s+v;';",
                this.keyspace));
  }

  @Test(groups = "short")
  public void should_parse_and_format_function_with_no_arguments() {
    // given
    String cql =
        String.format(
            "CREATE FUNCTION %s.pi() CALLED ON NULL INPUT RETURNS double LANGUAGE java AS 'return Math.PI;';",
            keyspace);
    // when
    session().execute(cql);
    // then
    KeyspaceMetadata keyspace = cluster().getMetadata().getKeyspace(this.keyspace);
    FunctionMetadata function = keyspace.getFunction("pi");
    assertThat(function).isNotNull();
    assertThat(function.getKeyspace()).isEqualTo(keyspace);
    assertThat(function.getSignature()).isEqualTo("pi()");
    assertThat(function.getSimpleName()).isEqualTo("pi");
    assertThat(function.getReturnType()).isEqualTo(DataType.cdouble());
    assertThat(function.getArguments()).isEmpty();
    assertThat(function.getLanguage()).isEqualTo("java");
    assertThat(function.getBody()).isEqualTo("return Math.PI;");
    assertThat(function.isCalledOnNullInput()).isTrue();
    assertThat(function.toString()).isEqualTo(cql);
    assertThat(function.exportAsString())
        .isEqualTo(
            String.format(
                "CREATE FUNCTION %s.pi()\n"
                    + "    CALLED ON NULL INPUT\n"
                    + "    RETURNS double\n"
                    + "    LANGUAGE java\n"
                    + "    AS 'return Math.PI;';",
                this.keyspace));
  }

  @Test(groups = "short")
  public void should_parse_and_format_function_with_udts() {
    // given
    String body =
        "//If \"called on null input\", handle nulls\n"
            + "if(ADDRESS == null) return previous_total + 0;\n"
            + "//User types are converted to com.datastax.driver.core.UDTValue types\n"
            + "java.util.Set phones = ADDRESS.getSet(\"phones\", com.datastax.driver.core.UDTValue.class);\n"
            + "return previous_total + phones.size();\n";
    String cqlFunction =
        String.format(
            "CREATE FUNCTION %s.\"NUM_PHONES_ACCU\"(previous_total int,\"ADDRESS\" \"Address\") "
                + "CALLED ON NULL INPUT "
                + "RETURNS int "
                + "LANGUAGE java "
                + "AS "
                + "'"
                + body
                + "';",
            keyspace);
    // when
    session().execute(cqlFunction);
    // then
    KeyspaceMetadata keyspace = cluster().getMetadata().getKeyspace(this.keyspace);
    UserType addressType = keyspace.getUserType("\"Address\"");
    FunctionMetadata function = keyspace.getFunction("\"NUM_PHONES_ACCU\"", cint(), addressType);
    assertThat(function).isNotNull();
    assertThat(function.getKeyspace()).isEqualTo(keyspace);

    assertThat(function.getSignature()).isEqualTo("\"NUM_PHONES_ACCU\"(int,\"Address\")");
    assertThat(function.getSimpleName()).isEqualTo("NUM_PHONES_ACCU");
    assertThat(function.getReturnType()).isEqualTo(cint());
    assertThat(function.getArguments())
        .containsExactly(entry("previous_total", cint()), entry("ADDRESS", addressType));
    assertThat(function.getLanguage()).isEqualTo("java");
    assertThat(function.getBody()).isEqualTo(body);
    assertThat(function.isCalledOnNullInput()).isTrue();
    assertThat(function.toString()).isEqualTo(cqlFunction);
  }

  /**
   * Ensures that functions whose arguments contain complex types such as tuples and collections,
   * and nested combinations thereof, are correctly parsed.
   *
   * @jira_ticket JAVA-1137
   */
  @Test(groups = "short")
  public void should_parse_and_format_functions_with_complex_arguments() {
    // given
    String cql =
        String.format(
            "CREATE FUNCTION %s.complex(x tuple<tuple<int>, map<int, int>>) RETURNS NULL ON NULL INPUT RETURNS int LANGUAGE java AS 'return 42;';",
            keyspace);
    // when
    session().execute(cql);
    // then
    KeyspaceMetadata keyspace = cluster().getMetadata().getKeyspace(this.keyspace);
    DataType argumentType =
        cluster()
            .getMetadata()
            .newTupleType(cluster().getMetadata().newTupleType(cint()), map(cint(), cint()));
    FunctionMetadata function = keyspace.getFunction("complex", argumentType);
    assertThat(function).isNotNull();
    assertThat(function.getKeyspace()).isEqualTo(keyspace);
    assertThat(function.getSignature()).isEqualTo("complex(tuple<tuple<int>, map<int, int>>)");
    assertThat(function.getSimpleName()).isEqualTo("complex");
    assertThat(function.getReturnType()).isEqualTo(cint());
    assertThat(function.getArguments()).containsEntry("x", argumentType);
    assertThat(function.getLanguage()).isEqualTo("java");
    assertThat(function.getBody()).isEqualTo("return 42;");
    assertThat(function.isCalledOnNullInput()).isFalse();
    assertThat(function.toString()).isEqualTo(cql);
    assertThat(function.exportAsString())
        .isEqualTo(
            String.format(
                "CREATE FUNCTION %s.complex(x tuple<tuple<int>, map<int, int>>)\n"
                    + "    RETURNS NULL ON NULL INPUT\n"
                    + "    RETURNS int\n"
                    + "    LANGUAGE java\n"
                    + "    AS 'return 42;';",
                this.keyspace));
  }

  @Override
  public void onTestContextInitialized() {
    execute(
        String.format("CREATE TYPE IF NOT EXISTS %s.\"Phone\" (number text)", keyspace),
        String.format(
            "CREATE TYPE IF NOT EXISTS %s.\"Address\" ("
                + "    street text,"
                + "    city text,"
                + "    zip int,"
                + "    phones frozen<set<frozen<\"Phone\">>>,"
                + "    location frozen<tuple<float, float>>"
                + ")",
            keyspace));
  }
}
