/*
 * 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 org.apache.calcite.sql.dialect;

import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.sql.SqlBasicTypeNameSpec;
import org.apache.calcite.sql.SqlCall;
import org.apache.calcite.sql.SqlDataTypeSpec;
import org.apache.calcite.sql.SqlDialect;
import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.sql.SqlNode;
import org.apache.calcite.sql.SqlWriter;
import org.apache.calcite.sql.parser.SqlParserPos;

import com.google.common.collect.ImmutableList;

import org.checkerframework.checker.nullness.qual.Nullable;

import java.util.List;
import java.util.Locale;
import java.util.regex.Pattern;

/**
 * A <code>SqlDialect</code> implementation for the Exasol database.
 */
public class ExasolSqlDialect extends SqlDialect {
  public static final SqlDialect.Context DEFAULT_CONTEXT =
      SqlDialect.EMPTY_CONTEXT
          .withDatabaseProduct(DatabaseProduct.EXASOL)
          .withIdentifierQuoteString("\"");

  public static final SqlDialect DEFAULT = new ExasolSqlDialect(DEFAULT_CONTEXT);

  /** An unquoted Exasol identifier must start with a letter and be followed
   * by zero or more letters, digits or _. */
  private static final Pattern IDENTIFIER_REGEX =
      Pattern.compile("[A-Za-z][A-Za-z0-9_]*");

  private static final List<String> RESERVED_KEYWORDS =
      ImmutableList.of("ABS", "ACCESS", "ACOS", "ADAPTER", "ADD_DAYS",
          "ADD_HOURS", "ADD_MINUTES", "ADD_MONTHS", "ADD_SECONDS", "ADD_WEEKS",
          "ADD_YEARS", "ADMIN", "ALIGN", "ALWAYS", "ANALYZE", "ANSI",
          "APPROXIMATE_COUNT_DISTINCT", "ASCII", "ASIN", "ASSIGNMENT",
          "ASYMMETRIC", "ATAN", "ATAN2", "ATOMIC", "ATTEMPTS", "AUDIT",
          "AUTHENTICATED", "AUTO", "AVG", "BACKUP", "BERNOULLI", "BIT_AND",
          "BIT_CHECK", "BIT_LENGTH", "BIT_LROTATE", "BIT_LSHIFT", "BIT_NOT",
          "BIT_OR", "BIT_RROTATE", "BIT_RSHIFT", "BIT_SET", "BIT_TO_NUM",
          "BIT_XOR", "BREADTH", "CEIL", "CEILING", "CHANGE", "CHARACTERS",
          "CHARACTER_LENGTH", "CHR", "CLEAR", "COBOL", "COLOGNE_PHONETIC",
          "COMMENT", "COMMENTS", "COMMITTED", "CONCAT", "CONNECT", "CONVERT_TZ",
          "CORR", "COS", "COSH", "COT", "COUNT", "COVAR_POP", "COVAR_SAMP",
          "CREATED", "CROSS", "CURDATE", "DATABASE", "DATE_TRUNC",
          "DAYS_BETWEEN", "DECODE", "DEFAULTS", "DEFAULT_PRIORITY_GROUP",
          "DEGREES", "DELIMIT", "DELIMITER", "DENSE_RANK", "DEPTH",
          "DIAGNOSTICS", "DICTIONARY", "DISTRIBUTE", "DISTRIBUTION", "DIV",
          "DOWN", "DUMP", "EDIT_DISTANCE", "ENCODING", "ESTIMATE", "EVALUATE",
          "EVERY", "EXA", "EXCLUDE", "EXCLUDING", "EXP", "EXPIRE", "EXPLAIN",
          "EXPRESSION", "FAILED", "FIRST_VALUE", "FLOOR", "FLUSH", "FOREIGN",
          "FORTRAN", "FROM_POSIX_TIME", "GREATEST", "GROUPING_ID", "HANDLER",
          "HAS", "HASH", "HASH_MD5", "HASH_SHA", "HASH_SHA1", "HASH_SHA256",
          "HASH_SHA512", "HASH_TIGER", "HIERARCHY", "HOURS_BETWEEN",
          "IDENTIFIED", "IGNORE", "IMPERSONATION", "INCLUDING", "INITCAP",
          "INITIALLY", "INSTR", "INVALID", "IPROC", "ISOLATION", "IS_BOOLEAN",
          "IS_DATE", "IS_DSINTERVAL", "IS_NUMBER", "IS_TIMESTAMP",
          "IS_YMINTERVAL", "JAVA", "JAVASCRIPT", "KEEP", "KEY", "KEYS", "KILL",
          "LAG", "LANGUAGE", "LAST_VALUE", "LCASE", "LEAD", "LEAST", "LENGTH",
          "LINK", "LN", "LOCATE", "LOCK", "LOG10", "LOG2", "LOGIN", "LOGS",
          "LONG", "LOWER", "LPAD", "LTRIM", "LUA", "MANAGE", "MAX", "MAXIMAL",
          "MEDIAN", "MID", "MIN", "MINUTES_BETWEEN", "MONTHS_BETWEEN", "MUMPS",
          "NEVER", "NICE", "NORMALIZED", "NOW", "NPROC", "NULLIFZERO", "NULLS",
          "NUMTODSINTERVAL", "NUMTOYMINTERVAL", "NVL", "NVL2", "OCTETS",
          "OCTET_LENGTH", "OFFSET", "OPTIMIZE", "ORA", "OWNER", "PADDING",
          "PARTITION", "PASCAL", "PASSWORD", "PASSWORD_EXPIRY_POLICY",
          "PASSWORD_SECURITY_POLICY", "PERCENTILE_CONT", "PERCENTILE_DISC",
          "PI", "PLI", "POSIX_TIME", "POWER", "PRECISION", "PRELOAD", "PRIMARY",
          "PRIORITY", "PRIVILEGE", "PYTHON", "QUERY_CACHE", "QUERY_TIMEOUT",
          "R", "RADIANS", "RAND", "RANK", "RATIO_TO_REPORT", "RAW_SIZE_LIMIT",
          "RECOMPRESS", "RECORD", "REGEXP_INSTR", "REGEXP_REPLACE",
          "REGEXP_SUBSTR", "REGR_AVGX", "REGR_AVGY", "REGR_COUNT",
          "REGR_INTERCEPT", "REGR_R2", "REGR_SLOPE", "REGR_SXX", "REGR_SXY",
          "REGR_SYY", "REJECT", "REORGANIZE", "REPEATABLE", "RESET", "REVERSE",
          "ROLE", "ROUND", "ROWID", "ROW_NUMBER", "RPAD", "RTRIM", "SCALAR",
          "SCHEMAS", "SCHEME", "SCRIPT_LANGUAGES", "SCRIPT_OUTPUT_ADDRESS",
          "SECONDS_BETWEEN", "SECURE", "SERIALIZABLE", "SHUT", "SIGN", "SIMPLE",
          "SIN", "SINH", "SIZE", "SKIP", "SOUNDEX", "SQRT", "STATISTICS",
          "STDDEV", "STDDEV_POP", "STDDEV_SAMP", "ST_AREA", "ST_BOUNDARY",
          "ST_BUFFER", "ST_CENTROID", "ST_CONTAINS", "ST_CONVEXHULL",
          "ST_CROSSES", "ST_DIFFERENCE", "ST_DIMENSION", "ST_DISJOINT",
          "ST_DISTANCE", "ST_ENDPOINT", "ST_ENVELOPE", "ST_EQUALS",
          "ST_EXTERIORRING", "ST_FORCE2D", "ST_GEOMETRYN", "ST_GEOMETRYTYPE",
          "ST_INTERIORRINGN", "ST_INTERSECTION", "ST_INTERSECTS", "ST_ISCLOSED",
          "ST_ISEMPTY", "ST_ISRING", "ST_ISSIMPLE", "ST_LENGTH",
          "ST_NUMGEOMETRIES", "ST_NUMINTERIORRINGS", "ST_NUMPOINTS",
          "ST_OVERLAPS", "ST_POINTN", "ST_SETSRID", "ST_STARTPOINT",
          "ST_SYMDIFFERENCE", "ST_TOUCHES", "ST_TRANSFORM", "ST_UNION",
          "ST_WITHIN", "ST_X", "ST_Y", "SUBSTR", "SUM", "SYMMETRIC",
          "SYS_CONNECT_BY_PATH", "SYS_GUID", "TABLES", "TABLESAMPLE", "TAN",
          "TANH", "TASKS", "TIES", "TIMESTAMP_ARITHMETIC_BEHAVIOR", "TIME_ZONE",
          "TIME_ZONE_BEHAVIOR", "TO_CHAR", "TO_DATE", "TO_DSINTERVAL",
          "TO_NUMBER", "TO_TIMESTAMP", "TO_YMINTERVAL", "TRANSLATE", "TRUNC",
          "TYPE", "UCASE", "UNBOUNDED", "UNCOMMITTED", "UNDO", "UNICODE",
          "UNICODECHR", "UNLIMITED", "UPPER", "UTF8", "VALUE2PROC", "VARIANCE",
          "VARYING", "VAR_POP", "VAR_SAMP", "VIRTUAL", "WEEK", "WEIGHT",
          "WRITE", "YEARS_BETWEEN", "ZEROIFNULL");

  /** Creates a ExasolSqlDialect. */
  public ExasolSqlDialect(Context context) {
    super(context);
  }

  @Override public boolean supportsCharSet() {
    return false;
  }

  @Override public boolean supportsNestedAggregations() {
    return false;
  }

  @Override public boolean supportsAggregateFunctionFilter() {
    return false;
  }

  @Override public boolean supportsAggregateFunction(SqlKind kind) {
    switch (kind) {
    case AVG:
    case COUNT:
    case COVAR_POP:
    case COVAR_SAMP:
    case MAX:
    case MIN:
    case STDDEV_POP:
    case STDDEV_SAMP:
    case SUM:
    case VAR_POP:
    case VAR_SAMP:
      return true;
    default:
      return false;
    }
  }

  @Override protected boolean identifierNeedsQuote(String val) {
    return !IDENTIFIER_REGEX.matcher(val).matches()
        || RESERVED_KEYWORDS.contains(val.toUpperCase(Locale.ROOT));
  }

  @Override public @Nullable SqlNode getCastSpec(RelDataType type) {
    switch (type.getSqlTypeName()) {
    case TIMESTAMP:
      // Exasol does not support TIMESTAMP with precision.
      return new SqlDataTypeSpec(
        new SqlBasicTypeNameSpec(type.getSqlTypeName(), SqlParserPos.ZERO),
        SqlParserPos.ZERO);
    default:
      return super.getCastSpec(type);
    }
  }

  @Override public void unparseOffsetFetch(SqlWriter writer, @Nullable SqlNode offset,
    @Nullable SqlNode fetch) {
    unparseFetchUsingLimit(writer, offset, fetch);
  }

  @Override public void unparseCall(SqlWriter writer, SqlCall call,
    int leftPrec, int rightPrec) {
    // Same as PostgreSQL implementation
    PostgresqlSqlDialect.DEFAULT.unparseCall(writer, call, leftPrec, rightPrec);
  }
}
