/*
 * 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.drill.exec.resolver;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.apache.drill.common.types.TypeProtos.MinorType;

public class ResolverTypePrecedence {


  public static final Map<MinorType, Integer> precedenceMap;
  public static final Map<MinorType, Set<MinorType>> secondaryImplicitCastRules;
  public static int MAX_IMPLICIT_CAST_COST;

  static {
    /* The precedenceMap is used to decide whether it's allowed to implicitly "promote"
     * one type to another type.
     *
     * The order that each type is inserted into HASHMAP decides its precedence.
     * First in ==> lowest precedence.
     * A type of lower precedence can be implicitly "promoted" to type of higher precedence.
     * For instance, NULL could be promoted to any other type;
     * tinyint could be promoted into int; but int could NOT be promoted into tinyint (due to possible precision loss).
     */
    int i = 0;
    precedenceMap = new HashMap<>();
    precedenceMap.put(MinorType.NULL, i += 2);       // NULL is legal to implicitly be promoted to any other type
    precedenceMap.put(MinorType.FIXEDBINARY, i += 2); // Fixed-length is promoted to var length
    precedenceMap.put(MinorType.VARBINARY, i += 2);
    precedenceMap.put(MinorType.FIXEDCHAR, i += 2);
    precedenceMap.put(MinorType.VARCHAR, i += 2);
    precedenceMap.put(MinorType.FIXED16CHAR, i += 2);
    precedenceMap.put(MinorType.VAR16CHAR, i += 2);
    precedenceMap.put(MinorType.BIT, i += 2);
    precedenceMap.put(MinorType.TINYINT, i += 2);   //type with few bytes is promoted to type with more bytes ==> no data loss.
    precedenceMap.put(MinorType.UINT1, i += 2);     //signed is legal to implicitly be promoted to unsigned.
    precedenceMap.put(MinorType.SMALLINT, i += 2);
    precedenceMap.put(MinorType.UINT2, i += 2);
    precedenceMap.put(MinorType.INT, i += 2);
    precedenceMap.put(MinorType.UINT4, i += 2);
    precedenceMap.put(MinorType.BIGINT, i += 2);
    precedenceMap.put(MinorType.UINT8, i += 2);
    precedenceMap.put(MinorType.MONEY, i += 2);
    precedenceMap.put(MinorType.FLOAT4, i += 2);
    precedenceMap.put(MinorType.DECIMAL9, i += 2);
    precedenceMap.put(MinorType.DECIMAL18, i += 2);
    precedenceMap.put(MinorType.DECIMAL28DENSE, i += 2);
    precedenceMap.put(MinorType.DECIMAL28SPARSE, i += 2);
    precedenceMap.put(MinorType.DECIMAL38DENSE, i += 2);
    precedenceMap.put(MinorType.DECIMAL38SPARSE, i += 2);
    precedenceMap.put(MinorType.VARDECIMAL, i += 2);
    precedenceMap.put(MinorType.FLOAT8, i += 2);
    precedenceMap.put(MinorType.DATE, i += 2);
    precedenceMap.put(MinorType.TIMESTAMP, i += 2);
    precedenceMap.put(MinorType.TIMETZ, i += 2);
    precedenceMap.put(MinorType.TIMESTAMPTZ, i += 2);
    precedenceMap.put(MinorType.TIME, i += 2);
    precedenceMap.put(MinorType.INTERVALDAY, i+= 2);
    precedenceMap.put(MinorType.INTERVALYEAR, i+= 2);
    precedenceMap.put(MinorType.INTERVAL, i+= 2);
    precedenceMap.put(MinorType.MAP, i += 2);
    precedenceMap.put(MinorType.LIST, i += 2);
    precedenceMap.put(MinorType.UNION, i += 2);

    MAX_IMPLICIT_CAST_COST = i;

    /* Currently implicit cast follows the precedence rules.
     * It may be useful to perform an implicit cast in
     * the opposite direction as specified by the precedence rules.
     *
     * For example: As per the precedence rules we can implicitly cast
     * from VARCHAR ---> BIGINT , but based upon some functions (eg: substr, concat)
     * it may be useful to implicitly cast from BIGINT ---> VARCHAR.
     *
     * To allow for such cases we have a secondary set of rules which will allow the reverse
     * implicit casts. Currently we only allow the reverse implicit cast to VARCHAR so we don't
     * need any cost associated with it, if we add more of these that may collide we can add costs.
     */
    secondaryImplicitCastRules = new HashMap<>();
    HashSet<MinorType> rule = new HashSet<>();

    // Following cast functions should exist
    rule.add(MinorType.TINYINT);
    rule.add(MinorType.SMALLINT);
    rule.add(MinorType.INT);
    rule.add(MinorType.BIGINT);
    rule.add(MinorType.UINT1);
    rule.add(MinorType.UINT2);
    rule.add(MinorType.UINT4);
    rule.add(MinorType.UINT8);
    rule.add(MinorType.VARDECIMAL);
    rule.add(MinorType.MONEY);
    rule.add(MinorType.FLOAT4);
    rule.add(MinorType.FLOAT8);
    rule.add(MinorType.BIT);
    rule.add(MinorType.FIXEDCHAR);
    rule.add(MinorType.FIXED16CHAR);
    rule.add(MinorType.VARCHAR);
    rule.add(MinorType.DATE);
    rule.add(MinorType.TIME);
    rule.add(MinorType.TIMESTAMP);
    rule.add(MinorType.TIMESTAMPTZ);
    rule.add(MinorType.INTERVAL);
    rule.add(MinorType.INTERVALYEAR);
    rule.add(MinorType.INTERVALDAY);
    secondaryImplicitCastRules.put(MinorType.VARCHAR, rule);

    rule = new HashSet<>();

    // Be able to implicitly cast to VARBINARY
    rule.add(MinorType.INT);
    rule.add(MinorType.BIGINT);
    rule.add(MinorType.FLOAT4);
    rule.add(MinorType.FLOAT8);
    rule.add(MinorType.VARCHAR);
    secondaryImplicitCastRules.put(MinorType.VARBINARY, rule);

    rule = new HashSet<>();

    // Be able to implicitly cast to VARDECIMAL
    rule.add(MinorType.FLOAT8);
    secondaryImplicitCastRules.put(MinorType.VARDECIMAL, rule);

    rule = new HashSet<>();

    // Be able to implicitly cast to DECIMAL9
    rule.add(MinorType.VARDECIMAL);
    secondaryImplicitCastRules.put(MinorType.DECIMAL9, rule);

    rule = new HashSet<>();

    // Be able to implicitly cast to DECIMAL18
    rule.add(MinorType.VARDECIMAL);
    secondaryImplicitCastRules.put(MinorType.DECIMAL18, rule);

    rule = new HashSet<>();

    // Be able to implicitly cast to DECIMAL28SPARSE
    rule.add(MinorType.VARDECIMAL);
    secondaryImplicitCastRules.put(MinorType.DECIMAL28SPARSE, rule);

    rule = new HashSet<>();

    // Be able to implicitly cast to DECIMAL38SPARSE
    rule.add(MinorType.VARDECIMAL);
    secondaryImplicitCastRules.put(MinorType.DECIMAL38SPARSE, rule);
  }
}
