/*
 * 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.kernel.exps;

import java.util.LinkedList;
import java.util.ListIterator;

import org.apache.openjpa.jdbc.sql.SQLBuffer;
import org.apache.openjpa.jdbc.sql.Select;
import org.apache.openjpa.kernel.Broker;
import org.apache.openjpa.kernel.Filters;
import org.apache.openjpa.kernel.OpenJPAStateManager;
import org.apache.openjpa.kernel.exps.Context;
import org.apache.openjpa.kernel.exps.ExpressionVisitor;
import org.apache.openjpa.meta.ClassMetaData;
import org.apache.openjpa.meta.FieldMetaData;
import org.apache.openjpa.meta.XMLMetaData;
import org.apache.openjpa.util.ImplHelper;
import org.apache.openjpa.util.InternalException;

/**
 * A field traversal starting with a constant filter parameter.
 *
 * @author Abe White
 */
class ConstPath
    extends Const
    implements JDBCPath {

    
    private static final long serialVersionUID = 1L;
    private final Const _constant;
    private final LinkedList _actions = new LinkedList();

    /**
     * Constructor. Supply constant to traverse.
     */
    public ConstPath(Const constant) {
        _constant = constant;
    }

    @Override
    public Class getType() {
        if (_actions.isEmpty()) {
            ClassMetaData meta = getMetaData();
            if (meta == null)
                return Object.class;
            return meta.getDescribedType();
        }

        Object last = _actions.getLast();
        if (last instanceof Class)
            return (Class) last;
        FieldMetaData fmd = (FieldMetaData) last;
        return fmd.getDeclaredType();
    }

    @Override
    public void setImplicitType(Class type) {
        _actions.add(type);
    }

    @Override
    public void get(FieldMetaData field, boolean nullTraversal) {
        _actions.add(field);
    }

    @Override
    public void getKey() {
    }

    @Override
    public FieldMetaData last() {
        ListIterator itr = _actions.listIterator(_actions.size());
        Object prev;
        while (itr.hasPrevious()) {
            prev = itr.previous();
            if (prev instanceof FieldMetaData)
                return (FieldMetaData) prev;
        }
        return null;
    }

    @Override
    public Object getValue(Object[] params) {
        throw new InternalException();
    }

    @Override
    public Object getValue(ExpContext ctx, ExpState state) {
        return ((ConstPathExpState) state).value;
    }

    @Override
    public Object getSQLValue(Select sel, ExpContext ctx, ExpState state) {
        return ((ConstPathExpState) state).sqlValue;
    }

    @Override
    public ExpState initialize(Select sel, ExpContext ctx, int flags) {
        return new ConstPathExpState(_constant.initialize(sel, ctx, 0));
    }

    @Override
    public void calculateValue(Select sel, ExpContext ctx, ExpState state,
        Val other, ExpState otherState) {
        super.calculateValue(sel, ctx, state, other, otherState);
        ConstPathExpState cstate = (ConstPathExpState) state;
        _constant.calculateValue(sel, ctx, cstate.constantState, null, null);
        cstate.value = _constant.getValue(ctx, cstate.constantState);
        boolean failed = false;

        // copied from org.apache.openjpa.query.InMemoryPath
        Object action;
        OpenJPAStateManager sm;
        Broker tmpBroker = null;
        for (Object o : _actions) {
            // fail on null value
            if (cstate.value == null) {
                failed = true;
                break;
            }

            action = o;
            if (action instanceof Class) {
                try {
                    cstate.value = Filters.convert(cstate.value,
                            (Class) action);
                    continue;
                }
                catch (ClassCastException cce) {
                    failed = true;
                    break;
                }
            }

            // make sure we can access the instance; even non-pc vals might
            // be proxyable
            sm = null;
            tmpBroker = null;
            if (ImplHelper.isManageable(cstate.value))
                sm = (OpenJPAStateManager) (ImplHelper.toPersistenceCapable(
                        cstate.value,
                        this.getMetaData().getRepository().getConfiguration())).
                        pcGetStateManager();
            if (sm == null) {
                tmpBroker = ctx.store.getContext().getBroker();
                tmpBroker.transactional(cstate.value, false, null);
                sm = tmpBroker.getStateManager(cstate.value);
            }

            try {
                // get the specified field value and switch candidate
                cstate.value = sm.fetchField(((FieldMetaData) action).
                        getIndex(), true);
            }
            finally {
                // setTransactional does not clear the state, which is
                // important since tmpVal might be also managed by
                // another broker if it's a proxied non-pc instance
                if (tmpBroker != null)
                    tmpBroker.nontransactional(sm.getManagedInstance(), null);
            }
        }

        if (failed)
            cstate.value = null;

        if (other != null) {
            cstate.sqlValue = other.toDataStoreValue(sel, ctx, otherState,
                cstate.value);
            cstate.otherLength = other.length(sel, ctx, otherState);
        } else
            cstate.sqlValue = cstate.value;
    }

    @Override
    public void appendTo(Select sel, ExpContext ctx, ExpState state,
        SQLBuffer sql, int index) {
        ConstPathExpState cstate = (ConstPathExpState) state;
        if (cstate.otherLength > 1)
            sql.appendValue(((Object[]) cstate.sqlValue)[index],
                cstate.getColumn(index));
        else
            sql.appendValue(cstate.sqlValue, cstate.getColumn(index));
    }

    @Override
    public void acceptVisit(ExpressionVisitor visitor) {
        visitor.enter(this);
        _constant.acceptVisit(visitor);
        visitor.exit(this);
    }

    /**
     * Expression state.
     */
    private static class ConstPathExpState
        extends ConstExpState {

        public final ExpState constantState;
        public Object value = null;
        public Object sqlValue = null;
        public int otherLength = 0;

        public ConstPathExpState(ExpState constantState) {
            this.constantState = constantState;
        }
    }

    @Override
    public void get(FieldMetaData fmd, XMLMetaData meta) {
    }

    @Override
    public void get(XMLMetaData meta, String name) {
    }

    @Override
    public XMLMetaData getXmlMapping() {
        return null;
    }

    @Override
    public void setSchemaAlias(String schemaAlias) {
    }

    @Override
    public String getSchemaAlias() {
        return null;
    }

    @Override
    public void setSubqueryContext(Context conext, String correlationVar) {
    }

    @Override
    public String getCorrelationVar() {
        return null;
    }
}
