/*
 * 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.meta.strats;

import java.sql.SQLException;
import java.util.LinkedList;

import org.apache.openjpa.jdbc.identifier.DBIdentifier;
import org.apache.openjpa.jdbc.kernel.JDBCFetchConfiguration;
import org.apache.openjpa.jdbc.kernel.JDBCStore;
import org.apache.openjpa.jdbc.meta.Embeddable;
import org.apache.openjpa.jdbc.meta.FieldMapping;
import org.apache.openjpa.jdbc.meta.RelationId;
import org.apache.openjpa.jdbc.meta.ValueMapping;
import org.apache.openjpa.jdbc.meta.ValueMappingInfo;
import org.apache.openjpa.jdbc.schema.Column;
import org.apache.openjpa.jdbc.schema.ColumnIO;
import org.apache.openjpa.jdbc.sql.DBDictionary;
import org.apache.openjpa.kernel.OpenJPAStateManager;
import org.apache.openjpa.lib.util.Localizer;
import org.apache.openjpa.meta.FieldMetaData;
import org.apache.openjpa.util.InternalException;

/**
 * <p>Handler for embedded objects as elements of a collection or map.  For
 * embedded objects as fields, use the more powerful
 * {@link EmbedFieldStrategy}.</p>
 *
 * @author Abe White
 * @since 0.4.0, 1.1.0
 */
public class ElementEmbedValueHandler
    extends EmbedValueHandler
    implements RelationId {

    
    private static final long serialVersionUID = 1L;

    private static final Localizer _loc = Localizer.forPackage
        (ElementEmbedValueHandler.class);

    private ValueMapping _vm = null;
    private Column[] _cols = null;
    private Object[] _args = null;
    private int _nullIdx = -1;
    private boolean _synthetic = false;

    /**
     * @deprecated
     */
    @Deprecated
    @Override
    public Column[] map(ValueMapping vm, String name, ColumnIO io,
        boolean adapt) {
        DBDictionary dict = vm.getMappingRepository().getDBDictionary();
        DBIdentifier colName = DBIdentifier.newColumn(name, dict != null ? dict.delimitAll() : false);
        return map(vm, colName, io, adapt);
    }

    public Column[] map(ValueMapping vm, DBIdentifier name, ColumnIO io,
        boolean adapt) {
        LinkedList cols = new LinkedList();
        LinkedList args = new LinkedList();
        super.map(vm, name, io, adapt, cols, args);

        ValueMappingInfo vinfo = vm.getValueInfo();
        Column nullInd = vinfo.getNullIndicatorColumn(vm, name,
            vm.getFieldMapping().getTable(), adapt);
        if (nullInd != null)
            vm.setColumns(new Column[]{ nullInd });

        // record index of null indicator column and whether it is synthetic
        if (nullInd != null) {
            _nullIdx = cols.indexOf(nullInd);
            if (_nullIdx == -1) {
                cols.addFirst(nullInd);
                args.addFirst(null);
                _nullIdx = 0;
                _synthetic = true;
            }
        }

        _vm = vm;
        _cols = (Column[]) cols.toArray(new Column[cols.size()]);
        _args = args.toArray();
        return _cols;
    }

    @Override
    public boolean objectValueRequiresLoad(ValueMapping vm) {
        return true;
    }

    @Override
    public Object getResultArgument(ValueMapping vm) {
        return _args;
    }

    @Override
    public Object toDataStoreValue(ValueMapping vm, Object val,
        JDBCStore store) {
        OpenJPAStateManager em = store.getContext().getStateManager(val);
        Object rval = null;
        if (_cols.length > 1)
            rval = new Object[_cols.length];

        // set null indicator column
        int idx = 0;
        if (_synthetic) {
            Object cval = ((EmbeddedClassStrategy) vm.getEmbeddedMapping().
                getStrategy()).getNullIndicatorValue(em);
            if (_cols.length == 1)
                return cval;
            ((Object[]) rval)[idx++] = cval;
        }

        return super.toDataStoreValue(em, vm, store, _cols, rval, idx);
    }

    @Override
    public Object toObjectValue(ValueMapping vm, Object val,
        OpenJPAStateManager sm, JDBCStore store, JDBCFetchConfiguration fetch)
        throws SQLException {
        // check null indicator first
        if (_nullIdx != -1) {
            Object nval;
            if (_cols.length == 1)
                nval = val;
            else
                nval = ((Object[]) val)[_nullIdx];
            if (((EmbeddedClassStrategy) vm.getEmbeddedMapping().
                getStrategy()).indicatesNull(nval))
                return null;
        }

        // create embedded instance
        OpenJPAStateManager em = store.getContext().embed(null, null, sm, vm);
        int idx = (_synthetic) ? 1 : 0;
        super.toObjectValue(em, vm, val, store, fetch, _cols, idx);

        // after loading everything from result, load the rest of the
        // configured fields
        em.load(fetch);
        return em.getManagedInstance();
    }

    /////////////////////////////
    // RelationId implementation
    /////////////////////////////

    @Override
    public Object toRelationDataStoreValue(OpenJPAStateManager sm, Column col) {
        return toRelationDataStoreValue(sm, col, 0);
    }

    /**
     * Recursive helper.
     */
    private Object toRelationDataStoreValue(OpenJPAStateManager sm, Column col,
        int idx) {
        FieldMapping field = findField(col, idx);
        if (field == null)
            throw new InternalException();

        if (field.getHandler() instanceof RelationId)
            return ((RelationId) field.getStrategy()).
                toRelationDataStoreValue(sm, col);
        if (field.getStrategy() instanceof RelationId)
            return ((RelationId) field.getStrategy()).
                toRelationDataStoreValue(sm, col);
        return toRelationDataStoreValue(sm, col, field.getIndex() + 1);
    }

    /**
     * Find the first field mapping that uses the given column starting with
     * the given field index.
     */
    private FieldMapping findField(Column col, int idx) {
        FieldMapping[] fms = _vm.getEmbeddedMapping().getFieldMappings();
        Column[] cols;
        for (int i = idx; i < fms.length; i++) {
            if (fms[i].getManagement() != FieldMetaData.MANAGE_PERSISTENT)
                continue;
            cols = ((Embeddable) fms[i]).getColumns();
            for (Column column : cols)
                if (column == col)
                    return fms[i];
        }
        return null;
	}
}
