/*
 * 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.openjpa.jdbc.sql;

import java.sql.SQLException;

import org.apache.openjpa.jdbc.meta.ClassMapping;
import org.apache.openjpa.jdbc.meta.RelationId;
import org.apache.openjpa.jdbc.schema.Column;
import org.apache.openjpa.jdbc.schema.ColumnIO;
import org.apache.openjpa.jdbc.schema.ForeignKey;
import org.apache.openjpa.jdbc.schema.Table;
import org.apache.openjpa.kernel.OpenJPAStateManager;

/**
 * Secondary table row that tracks foreign keys to auto-inc columns.
 *
 * @author Abe White
 */
public class SecondaryRow
    extends RowImpl {

    private OpenJPAStateManager[] _fks = null;
    private ColumnIO[] _fkIO = null;
    private OpenJPAStateManager[] _rels = null;
    private RelationId[] _callbacks = null;

    /**
     * Constructor; supply table and action.
     */
    public SecondaryRow(Table table, int action) {
        this(table.getColumns(), action);
    }

    protected SecondaryRow(Column[] cols, int action) {
        super(cols, action);
    }

    @Override
    public void setForeignKey(ForeignKey fk, OpenJPAStateManager sm)
        throws SQLException {
        setForeignKey(fk, null, sm);
    }

    @Override
    public void setForeignKey(ForeignKey fk, ColumnIO io,
        OpenJPAStateManager sm)
        throws SQLException {
        if (!delayForeignKey(fk, sm)) {
            super.setForeignKey(fk, io, sm);
            return;
        }

        // force valid
        if (canSetAny(io, fk.getColumns().length
            + fk.getConstantColumns().length, false))
            setValid(true);

        // record foreig key for delayed flush
        if (_fks == null)
            _fks = new OpenJPAStateManager[getTable().getForeignKeys().length];
        _fks[fk.getIndex()] = sm;

        if (_fkIO != null)
            _fkIO[fk.getIndex()] = io;
        else if (io != null
            && ((getAction() == ACTION_INSERT
            && !io.isAllInsertable(fk, false))
            || (getAction() != ACTION_INSERT
            && !io.isAllUpdatable(fk, false)))) {
            _fkIO = new ColumnIO[_fks.length];
            _fkIO[fk.getIndex()] = io;
        }
    }

    /**
     * Record foreign keys to new auto-inc instances; flush them only when
     * we have to generate our SQL to give the instance a chance to finalize
     * its values.
     */
    private boolean delayForeignKey(ForeignKey fk, OpenJPAStateManager sm) {
        return fk.isPrimaryKeyAutoAssigned() && getAction() != ACTION_DELETE
            && sm != null && sm.isNew() && !sm.isFlushed();
    }

    @Override
    public void setRelationId(Column col, OpenJPAStateManager sm,
        RelationId rel)
        throws SQLException {
        if (sm == null || sm.getObjectId() != null || !sm.isNew()
            || sm.isFlushed() || !isPrimaryKeyAutoAssigned(sm))
            super.setRelationId(col, sm, rel);
        else {
            if (_rels == null) {
                Column[] cols = getTable().getRelationIdColumns();
                _rels = new OpenJPAStateManager[cols.length];
                _callbacks = new RelationId[cols.length];
            }
            int idx = getRelationIdIndex(col);
            _rels[idx] = sm;
            _callbacks[idx] = rel;
        }
    }

    /**
     * Return the index into our relation id array of the value for the
     * given column.
     */
    private int getRelationIdIndex(Column col) {
        Column[] cols = getTable().getRelationIdColumns();
        for (int i = 0; i < cols.length; i++)
            if (cols[i] == col)
                return i;
        return -1;
    }

    /**
     * Return true if any primary key columns of the given instance are
     * auto-assigned.
     */
    private static boolean isPrimaryKeyAutoAssigned(OpenJPAStateManager sm) {
        ClassMapping cls = (ClassMapping) sm.getMetaData();
        while (cls.getJoinablePCSuperclassMapping() != null)
            cls = cls.getJoinablePCSuperclassMapping();
        Column[] cols = cls.getPrimaryKeyColumns();
        for (Column col : cols)
            if (col.isAutoAssigned())
                return true;
        return false;
    }

    @Override
    protected String generateSQL(DBDictionary dict) {
        try {
            if (_fks != null) {
                ForeignKey[] fks = getTable().getForeignKeys();
                ColumnIO io;
                for (int i = 0; i < _fks.length; i++) {
                    if (_fks[i] != null) {
                        io = (_fkIO == null) ? null : _fkIO[i];
                        super.setForeignKey(fks[i], io, _fks[i]);
                    }
                }
            }
            if (_rels != null) {
                Column[] cols = getTable().getRelationIdColumns();
                for (int i = 0; i < _rels.length; i++)
                    if (_rels[i] != null)
                        super.setRelationId(cols[i], _rels[i], _callbacks[i]);
            }
        }
        catch (SQLException se) {
            throw SQLExceptions.getStore(se, dict);
        }
        return super.generateSQL(dict);
    }

    @Override
    protected RowImpl newInstance(Column[] cols, int action) {
        return new SecondaryRow(cols, action);
    }

    @Override
    public void copyInto(RowImpl row, boolean whereOnly) {
        super.copyInto(row, whereOnly);
        if (_fks == null || whereOnly || row.getAction() == ACTION_DELETE
            || !(row instanceof SecondaryRow))
            return;

        SecondaryRow srow = (SecondaryRow) row;
        if (srow._fks == null)
            srow._fks = new OpenJPAStateManager[_fks.length];
        System.arraycopy(_fks, 0, srow._fks, 0, _fks.length);
        if (_fkIO != null) {
            if (srow._fkIO == null)
                srow._fkIO = new ColumnIO[_fkIO.length];
            System.arraycopy(_fkIO, 0, srow._fkIO, 0, _fkIO.length);
        }
    }
}
