/*
 * Copyright (c) 1998, 2018 Oracle and/or its affiliates. All rights reserved.
 *
 * 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:
//     Oracle - Dec 2008
package org.eclipse.persistence.internal.helper;

import java.util.ListIterator;
import java.util.List;

import org.eclipse.persistence.exceptions.QueryException;
import org.eclipse.persistence.internal.sessions.AbstractRecord;
import org.eclipse.persistence.platform.database.DatabasePlatform;
import org.eclipse.persistence.platform.database.oracle.plsql.PLSQLStoredProcedureCall;
import org.eclipse.persistence.platform.database.oracle.plsql.PLSQLargument;
import org.eclipse.persistence.queries.StoredProcedureCall;
import org.eclipse.persistence.sessions.DatabaseRecord;
import static org.eclipse.persistence.internal.helper.DatabaseType.DatabaseTypeHelper.databaseTypeHelper;
import static org.eclipse.persistence.internal.helper.Helper.NL;

/**
 * <b>PUBLIC</b>: Abstract class for Complex Database types
 * (e.g. PL/SQL records, PL/SQL collections)
 *
 * @author Mike Norman - michael.norman@oracle.com
 * @since Oracle TopLink 11.x.x
 */
public abstract class ComplexDatabaseType implements DatabaseType, Cloneable {

    protected String typeName;
    protected String compatibleType;

    /**
     * Defines the Java class that the complex type maps to.
     */
    protected Class javaType;
    protected String javaTypeName;

    public boolean isRecord() {
        return false;
    }

    public boolean isCollection() {
        return false;
    }

    public boolean isStruct() {
        return false;
    }

    public boolean isArray() {
        return false;
    }

    /**
     * Indicates if a given subclass represents a PL/SQL cursor.
     *
     * @see org.eclipse.persistence.platform.database.oracle.plsql.PLSQLCursor
     */
    public boolean isCursor() {
        return false;
    }

    public int getConversionCode() {
        return getSqlCode();
    }

    public boolean isComplexDatabaseType() {
        return true;
    }

    public boolean isJDBCType() {
        return false;
    }

    public boolean hasCompatibleType() {
        return this.compatibleType != null;
    }

    public String getCompatibleType() {
        return compatibleType;
    }

    public void setCompatibleType(String compatibleType) {
        this.compatibleType = compatibleType;
    }

    public String getTypeName() {
        return typeName;
    }

    public void setTypeName(String typeName) {
        this.typeName = typeName;
    }

    /**
     * Set the Java class that the complex type maps to.
     * The mapped class for a Record type, and collection class for Collection type.
     */
    public void setJavaType(Class javaType) {
        this.javaType = javaType;
        if (javaType != null) {
            javaTypeName = javaType.getName();
        }
    }

    /**
     * Return the Java class that the complex type maps to.
     */
    public Class getJavaType() {
        return javaType;
    }

    public String getJavaTypeName() {
        if (javaType != null && javaTypeName == null) {
            javaTypeName = javaType.getName();
        }
        return javaTypeName;
    }

    public void setJavaTypeName(String javaTypeName) {
        this.javaTypeName = javaTypeName;
    }

    public ComplexDatabaseType clone() {
        try {
            ComplexDatabaseType clone = (ComplexDatabaseType)super.clone();
            return clone;
        }
        catch (CloneNotSupportedException exception) {
            throw new InternalError(exception.getMessage());
        }
    }

    public void buildInDeclare(StringBuilder sb, PLSQLargument inArg) {
        // Validate.
        if (!hasCompatibleType()) {
            throw QueryException.compatibleTypeNotSet(this);
        }
        if ((getTypeName() == null) || getTypeName().equals("")) {
            throw QueryException.typeNameNotSet(this);
        }
        sb.append("  ");
        sb.append(databaseTypeHelper.buildTarget(inArg));
        sb.append(" ");
        sb.append(getTypeName());
        sb.append(";");
        sb.append(NL);
        sb.append("  ");
        sb.append(databaseTypeHelper.buildCompatible(inArg));
        sb.append(" ");
        sb.append(getCompatibleType());
        sb.append(" := :");
        sb.append(inArg.inIndex);
        sb.append(";");
        sb.append(NL);
    }

    public void buildOutDeclare(StringBuilder sb, PLSQLargument outArg) {// Validate.
        if (!hasCompatibleType()) {
            throw QueryException.compatibleTypeNotSet(this);
        }
        if ((getTypeName() == null) || getTypeName().equals("")) {
            throw QueryException.typeNameNotSet(this);
        }
        sb.append("  ");
        sb.append(databaseTypeHelper.buildTarget(outArg));
        sb.append(" ");
        sb.append(getTypeName());
        sb.append(";");
        sb.append(NL);
    }

    public void buildBeginBlock(StringBuilder sb, PLSQLargument arg, PLSQLStoredProcedureCall call) {
        String sql2PlName = call.getSQL2PlName(this);
        if (sql2PlName == null) {
            // TODO exception
            throw new NullPointerException("no SQL2Pl conversion routine for " + typeName);
        }
        String target = databaseTypeHelper.buildTarget(arg);
        String compat = databaseTypeHelper.buildCompatible(arg);
        sb.append("  ");
        sb.append(target);
        sb.append(" := ");
        sb.append(sql2PlName);
        sb.append("(");
        sb.append(compat);
        sb.append(");");
        sb.append(NL);
    }

    public void buildOutAssignment(StringBuilder sb, PLSQLargument outArg, PLSQLStoredProcedureCall call) {
        String sql2PlName = call.getPl2SQLName(this);
        if (sql2PlName == null) {
            // TODO: Error.
            throw new NullPointerException("no Pl2SQL conversion routine for " + typeName);
        }
        String target = databaseTypeHelper.buildTarget(outArg);
        sb.append("  :");
        sb.append(outArg.outIndex);
        sb.append(" := ");
        sb.append(sql2PlName);
        sb.append("(");
        sb.append(target);
        sb.append(");");
        sb.append(NL);
    }

    public void buildOutputRow(PLSQLargument outArg, AbstractRecord outputRow,
                    DatabaseRecord newOutputRow, List<DatabaseField> outputRowFields, List outputRowValues) {
        databaseTypeHelper.buildOutputRow(outArg, outputRow,
                newOutputRow, outputRowFields, outputRowValues);
    }

    public int computeInIndex(PLSQLargument inArg, int newIndex, ListIterator<PLSQLargument> i) {
        return databaseTypeHelper.computeInIndex(inArg, newIndex);
    }

    public int computeOutIndex(PLSQLargument outArg, int newIndex, ListIterator<PLSQLargument> i) {
        return databaseTypeHelper.computeOutIndex(outArg, newIndex);
    }

    public void logParameter(StringBuilder sb, Integer direction, PLSQLargument arg,
            AbstractRecord translationRow, DatabasePlatform platform) {
        databaseTypeHelper.logParameter(sb, direction, arg, translationRow, platform);
    }

    public void translate(PLSQLargument arg, AbstractRecord translationRow,
            AbstractRecord copyOfTranslationRow, List<DatabaseField> copyOfTranslationFields,
            List<DatabaseField> translationRowFields, List translationRowValues, StoredProcedureCall call) {
        databaseTypeHelper.translate(arg, translationRow, copyOfTranslationRow,
            copyOfTranslationFields, translationRowFields, translationRowValues, call);
    }

    public String toString() {
        return getClass().getSimpleName() + "(" + getTypeName() + ")";
    }
}
