/*-
 * #%L
 * athena-bigquery
 * %%
 * Copyright (C) 2019 - 2022 Amazon Web Services
 * %%
 * 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.
 * #L%
 */
package com.amazonaws.athena.connectors.google.bigquery;

import com.amazonaws.athena.connector.lambda.domain.predicate.functions.FunctionName;
import com.amazonaws.athena.connector.lambda.domain.predicate.functions.OperatorType;
import com.amazonaws.athena.connector.lambda.domain.predicate.functions.StandardFunctions;
import com.amazonaws.athena.connectors.jdbc.manager.FederationExpressionParser;
import com.google.common.base.Joiner;
import org.apache.arrow.vector.types.pojo.ArrowType;
import org.apache.commons.lang3.NotImplementedException;

import java.util.List;

/**
 * Based on com.amazonaws.athena.connectors.jdbc.manager.JdbcFederationExpressionParser class
 */
public class BigQueryFederationExpressionParser extends FederationExpressionParser
{
    public String writeArrayConstructorClause(ArrowType type, List<String> arguments)
    {
        return Joiner.on(", ").join(arguments);
    }

    @Override
    public String mapFunctionToDataSourceSyntax(FunctionName functionName, ArrowType type, List<String> arguments)
    {
        StandardFunctions functionEnum = StandardFunctions.fromFunctionName(functionName);
        OperatorType operatorType = functionEnum.getOperatorType();

        if (arguments == null || arguments.size() == 0) {
            throw new IllegalArgumentException("Arguments cannot be null or empty.");
        }
        switch (operatorType) {
            case UNARY:
                if (arguments.size() != 1) {
                    throw new IllegalArgumentException("Unary function type " + functionName.getFunctionName() + " was provided with " + arguments.size() + " arguments.");
                }
                break;
            case BINARY:
                if (arguments.size() != 2) {
                    throw new IllegalArgumentException("Binary function type " + functionName.getFunctionName() + " was provided with " + arguments.size() + " arguments.");
                }
                break;
            case VARARG:
                break;
            default:
                throw new RuntimeException("A new operator type was introduced without adding support for it.");
        }

        String clause = "";
        switch (functionEnum) {
            case ADD_FUNCTION_NAME:
                clause = Joiner.on(" + ").join(arguments);
                break;
            case AND_FUNCTION_NAME:
                clause = Joiner.on(" AND ").join(arguments);
                break;
            case ARRAY_CONSTRUCTOR_FUNCTION_NAME: // up to subclass
                clause = writeArrayConstructorClause(type, arguments);
                break;
            case DIVIDE_FUNCTION_NAME:
                clause = Joiner.on(" / ").join(arguments);
                break;
            case EQUAL_OPERATOR_FUNCTION_NAME:
                clause = Joiner.on(" = ").join(arguments);
                break;
            case GREATER_THAN_OPERATOR_FUNCTION_NAME:
                clause = Joiner.on(" > ").join(arguments);
                break;
            case GREATER_THAN_OR_EQUAL_OPERATOR_FUNCTION_NAME:
                clause = Joiner.on(" >= ").join(arguments);
                break;
            case IN_PREDICATE_FUNCTION_NAME:
                clause = arguments.get(0) + " IN " + arguments.get(1);
                break;
            case IS_DISTINCT_FROM_OPERATOR_FUNCTION_NAME:
                String argZero = arguments.get(0);
                String argOne = arguments.get(1);
                clause = argZero + " IS DISTINCT FROM " + argOne;
                break;
            case IS_NULL_FUNCTION_NAME:
                clause = arguments.get(0) + " IS NULL";
                break;
            case LESS_THAN_OPERATOR_FUNCTION_NAME:
                clause = Joiner.on(" < ").join(arguments);
                break;
            case LESS_THAN_OR_EQUAL_OPERATOR_FUNCTION_NAME:
                clause = Joiner.on(" <= ").join(arguments);
                break;
            case LIKE_PATTERN_FUNCTION_NAME:
                clause = arguments.get(0) + " LIKE " + arguments.get(1);
                break;
            case MODULUS_FUNCTION_NAME:
                clause = " MOD(" + arguments.get(0) + "," + arguments.get(1) + ")";
                break;
            case MULTIPLY_FUNCTION_NAME:
                clause = Joiner.on(" * ").join(arguments);
                break;
            case NEGATE_FUNCTION_NAME:
                clause = "-" + arguments.get(0);
                break;
            case NOT_EQUAL_OPERATOR_FUNCTION_NAME:
                clause = Joiner.on(" <> ").join(arguments);
                break;
            case NOT_FUNCTION_NAME:
                clause = " NOT " + arguments.get(0);
                break;
            case NULLIF_FUNCTION_NAME:
                clause = "NULLIF(" + arguments.get(0) + ", " + arguments.get(1) + ")";
                break;
            case OR_FUNCTION_NAME:
                clause = Joiner.on(" OR ").join(arguments);
                break;
            case SUBTRACT_FUNCTION_NAME:
                clause = Joiner.on(" - ").join(arguments);
                break;
            default:
                throw new NotImplementedException("The function " + functionName.getFunctionName() + " does not have an implementation");
        }
        if (clause == null) {
            return "";
        }
        return "(" + clause + ")";
    }
}
