/*
 * Copyright (c) 1998, 2018 Oracle and/or its affiliates. All rights reserved.
 * Copyright (c) 2008 Markus KARG(markus-karg@users.sourceforge.net).
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0,
 * or the Eclipse Distribution License v. 1.0 which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
 */

// Contributors:
//     Markus Karg and Oracle - initial API and implementation from Oracle TopLink and TopLink Essentials
//     05/16/2008-1.0 Markus Karg
//       - 237843: CONCAT must be translated into || instead of +
//     02/23/2015-2.6 Dalia Abo Sheasha
//       - 460607: Change DatabasePlatform StoredProcedureTerminationToken to be configurable
package org.eclipse.persistence.platform.database;

import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Hashtable;

import org.eclipse.persistence.exceptions.ValidationException;
import org.eclipse.persistence.expressions.ExpressionOperator;
import org.eclipse.persistence.internal.databaseaccess.FieldTypeDefinition;
import org.eclipse.persistence.internal.helper.DatabaseTable;
import org.eclipse.persistence.queries.ValueReadQuery;

/**
 * Provides SQL Anywhere specific behaviour.
 *
 * @author Markus KARG (markus-karg@users.sourceforge.net)
 */
public class SQLAnywherePlatform extends SybasePlatform {

    public SQLAnywherePlatform() {
        super();
        this.storedProcedureTerminationToken = ";";
    }

// TODO: can't use these field types: none of them has sizes.
// That results is using defaults, that seem to be 9 for VARCHAR
// and neither FeatureTestModel nor AggregateTestModel would setup because of that.
// Untill this is fixed have to use the field types inherited from SybasePlatform.
/*    protected Hashtable buildFieldTypes() {
        Hashtable<Class, FieldTypeDefinition> fieldTypeMapping = new Hashtable<Class, FieldTypeDefinition>();
        fieldTypeMapping.put(Boolean.class, new FieldTypeDefinition("BIT", false));
        fieldTypeMapping.put(Integer.class, new FieldTypeDefinition("INTEGER", false));
        fieldTypeMapping.put(Long.class, new FieldTypeDefinition("BIGINT", false));
        fieldTypeMapping.put(Float.class, new FieldTypeDefinition("REAL", false));
        fieldTypeMapping.put(Double.class, new FieldTypeDefinition("DOUBLE", false));
        fieldTypeMapping.put(Short.class, new FieldTypeDefinition("SMALLINT", false));
        fieldTypeMapping.put(Byte.class, new FieldTypeDefinition("SMALLINT", false));
        fieldTypeMapping.put(BigInteger.class, new FieldTypeDefinition("BIGINT", false));
        fieldTypeMapping.put(BigDecimal.class, new FieldTypeDefinition("DOUBLE", false));
        fieldTypeMapping.put(Number.class, new FieldTypeDefinition("DOUBLE", false));
        fieldTypeMapping.put(String.class, new FieldTypeDefinition("VARCHAR"));
        fieldTypeMapping.put(Character.class, new FieldTypeDefinition("CHAR"));
        fieldTypeMapping.put(Byte[].class, new FieldTypeDefinition("LONG BINARY", false));
        fieldTypeMapping.put(Character[].class, new FieldTypeDefinition("LONG VARCHAR", false));
        fieldTypeMapping.put(byte[].class, new FieldTypeDefinition("LONG BINARY", false));
        fieldTypeMapping.put(char[].class, new FieldTypeDefinition("LONG VARCHAR", false));
        fieldTypeMapping.put(Blob.class, new FieldTypeDefinition("LONG BINARY", false));
        fieldTypeMapping.put(Clob.class, new FieldTypeDefinition("LONG VARCHAR", false));
        fieldTypeMapping.put(Date.class, new FieldTypeDefinition("DATE", false));
        fieldTypeMapping.put(Time.class, new FieldTypeDefinition("TIME", false));
        fieldTypeMapping.put(Timestamp.class, new FieldTypeDefinition("TIMESTAMP", false));
        return fieldTypeMapping;
    }*/
    @Override
    protected Hashtable buildFieldTypes() {
        Hashtable fieldTypeMapping = super.buildFieldTypes();
        fieldTypeMapping.put(Boolean.class, new FieldTypeDefinition("BIT", false));
        return fieldTypeMapping;
    }

    /**
     * INTERNAL:
     * Build the identity query for native sequencing.
     */
    @Override
    public ValueReadQuery buildSelectQueryForIdentity() {
        ValueReadQuery selectQuery = new ValueReadQuery();
        StringWriter writer = new StringWriter();
        writer.write("SELECT @@IDENTITY");
        selectQuery.setSQLString(writer.toString());
        return selectQuery;
    }

    public static ExpressionOperator createCurrentDateOperator() {
        return ExpressionOperator.simpleFunctionNoParentheses(ExpressionOperator.CurrentDate, "CURRENT DATE");
    }

    public static ExpressionOperator createCurrentTimeOperator() {
        return ExpressionOperator.simpleFunctionNoParentheses(ExpressionOperator.CurrentTime, "CURRENT TIME");
    }

    public static ExpressionOperator createLocate2Operator() {
        return ExpressionOperator.simpleThreeArgumentFunction(ExpressionOperator.Locate2, "LOCATE");
    }

    public static ExpressionOperator createConcatOperator() {
        return ExpressionOperator.simpleLogicalNoParens(ExpressionOperator.Concat, "||");
    }

    public static ExpressionOperator createLocateOperator() {
        return ExpressionOperator.simpleTwoArgumentFunction(ExpressionOperator.Locate, "LOCATE");
    }

    @Override
    protected String getCreateTempTableSqlPrefix() {
        return "DECLARE LOCAL TEMPORARY TABLE ";
    }

    /**
     * Used for stored procedure creation: Prefix for INPUT parameters.
     * Not required on most platforms.
     */
    @Override
    public String getInputProcedureToken() {
        return "IN";
    }

    @Override
    public String getInOutputProcedureToken() {
        return "";
    }

    /* This method is used to print the output parameter token when stored
     * procedures is created
     */
    @Override
    public String getCreationOutputProcedureToken() {
        return "OUT";
    }

    /* This method is used to print the output parameter token when stored
     * procedures are called
     */
    @Override
    public String getOutputProcedureToken() {
        return "";
    }

    @Override
    public int getMaxFieldNameSize() {
        return 128;
    }

    /**
     * Used for sp defs.
     */
    @Override
    public String getProcedureArgumentString() {
        return "";
    }

    @Override
    public String getStoredProcedureParameterPrefix() {
        return "";
    }

    @Override
    public String getProcedureAsString() {
        return "";
    }

    @Override
    public String getProcedureBeginString() {
        return "BEGIN";
    }

    @Override
    public String getProcedureEndString() {
        return "END";
    }

    /**
     * Used for batch writing and sp defs.
     */
    @Override
    public String getBatchBeginString() {
        return "BEGIN ";
    }

    /**
     * Used for batch writing and sp defs.
     */
    @Override
    public String getBatchEndString() {
        return "END;";
    }

    /**
     * Used for batch writing and sp defs.
     */
    @Override
    public String getBatchDelimiterString() {
        return "; ";
    }

    /**
     * Used for sp calls.
     */
    @Override
    public String getProcedureCallHeader() {
        return "CALL ";
    }

    /**
     * Used for sp calls.
     */
    @Override
    public DatabaseTable getTempTableForTable(DatabaseTable table) {
        return new DatabaseTable("$" + table.getName(), table.getTableQualifier(), table.shouldUseDelimiters(), getStartDelimiter(), getEndDelimiter());
    }

    @Override
    protected void initializePlatformOperators() {
        super.initializePlatformOperators();
        this.addOperator(createConcatOperator());
        this.addOperator(createLocateOperator());
        this.addOperator(createLocate2Operator());
//        this.addOperator(createCurrentDateOperator());
//        this.addOperator(createCurrentTimeOperator());
        this.addOperator(ExpressionOperator.charLength());
        this.addOperator(ExpressionOperator.mod());
    }

    @Override
    public boolean isSQLAnywhere() {
        return true;
    }

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

    @Override
    public boolean requiresProcedureBrackets() {
        return true;
    }

    @Override
    public boolean requiresProcedureCallBrackets() {
        return true;
    }

    /**
     * INTERNAL:
     * Indicates whether the version of CallableStatement.registerOutputParameter method
     * that takes type name should be used.
     */
    @Override
    public boolean requiresTypeNameToRegisterOutputParameter() {
        return false;
    }

    @Override
    public void printFieldIdentityClause(Writer writer) throws ValidationException {
        try {
            writer.write(" DEFAULT AUTOINCREMENT");
        } catch (IOException ioException) {
            throw ValidationException.fileError(ioException);
        }
    }

    @Override
    public void printFieldNullClause(Writer writer) throws ValidationException {
        try {
            writer.write(" NULL");
        } catch (IOException ioException) {
            throw ValidationException.fileError(ioException);
        }
    }

    @Override
    public boolean shouldPrintInputTokenAtStart() {
        return true;
    }

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

    @Override
    public boolean shouldPrintOutputTokenAtStart() {
        return true;
    }

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

    //**temp
    @Override
    public boolean shouldPrintStoredProcedureArgumentNameInCall() {
        return false;
    }

    @Override
    public boolean shouldPrintStoredProcedureVariablesAfterBeginString() {
        return true;
    }

    @Override
    public boolean supportsIdentity() {
        return true;
    }

    @Override
    public boolean supportsLocalTempTables() {
        return true;
    }

    @Override
    public boolean supportsStoredFunctions() {
        return true;
    }

    /**
     * SQL Anywhere does support cascade on delete, unlike Sybase.
     */
    @Override
    public boolean supportsDeleteOnCascade() {
        return true;
    }
}
