/**
 * 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.camel.model;

import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;

import org.apache.camel.Predicate;
import org.apache.camel.Processor;
import org.apache.camel.builder.PredicateBuilder;
import org.apache.camel.processor.Interceptor;
import org.apache.camel.spi.RouteContext;


/**
 * Represents an XML &lt;intercept/&gt; element
 *
 * @version $Revision: 69900 $
 */
@XmlRootElement(name = "intercept")
@XmlAccessorType(XmlAccessType.FIELD)
public class InterceptType extends OutputType<ProcessorType> {
  
    @XmlTransient
    private ProceedType proceed = new ProceedType();
    @XmlTransient
    private Boolean stopIntercept = Boolean.FALSE;
    @XmlTransient
    private Boolean usePredicate = Boolean.FALSE;

    @Override
    public String toString() {
        return "Intercept[" + getOutputs() + "]";
    }

    @Override
    public String getShortName() {
        return "intercept";
    }

    @Override
    public String getLabel() {
        return "intercept";
    }

    @Override
    public Processor createProcessor(RouteContext routeContext) throws Exception {
        Interceptor interceptor = new Interceptor();
        routeContext.intercept(interceptor);

        final Processor interceptRoute = createOutputsProcessor(routeContext);
        interceptor.setInterceptorLogic(interceptRoute);

        return interceptor;
    }

    /**
     * Applies this interceptor only if the given predicate is true
     *
     * @param predicate  the predicate
     * @return the builder
     */
    public ChoiceType when(Predicate predicate) {
        usePredicate = Boolean.TRUE;
        ChoiceType choice = choice().when(PredicateBuilder.not(predicate));
        choice.addOutput(proceed);
        return choice.otherwise();
    }

    public ProceedType getProceed() {
        return proceed;
    }

    public void stopIntercept() {
        setStopIntercept(Boolean.TRUE);
    }

    /**
     * This method is <b>only</b> for handling some post configuration
     * that is needed from the Spring DSL side as JAXB does not invoke the fluent
     * builders, so we need to manually handle this afterwards, and since this is
     * an interceptor it has to do a bit of magic logic to fixup to handle predicates
     * with or without proceed/stop set as well.
     */
    public void afterPropertiesSet() {
        List<ProcessorType<?>> list = new ArrayList<ProcessorType<?>>();
        for (ProcessorType<?> out : outputs) {
            if (out instanceof WhenType) {
                // JAXB does not invoke the when() fluent builder so we need to wrap the when in
                // a choice with the proceed as the when for the Java DSL does
                WhenType when = (WhenType) out;
                usePredicate = Boolean.TRUE;
                ChoiceType choice = new ChoiceType();
                choice.when(PredicateBuilder.not(when.getExpression()));
                choice.addOutput(proceed);
                list.add(choice);

                ChoiceType otherwise = choice.otherwise();
                // add the children to the otherwise
                for (ProcessorType child : when.getOutputs()) {
                    if (child instanceof StopType) {
                        // notify we should stop
                        stopIntercept();
                    } else {
                        otherwise.addOutput(child);
                    }
                }
            } else if (out instanceof StopType) {
                // notify we shuld stop
                stopIntercept();
            } else {
                list.add(out);
            }
        }

        // replace old output with this redone output list
        outputs.clear();
        for (ProcessorType<?> out : list) {
            addOutput(out);
        }
    }

    public InterceptType createProxy() {
        InterceptType answer = new InterceptType();
        answer.getOutputs().addAll(this.getOutputs());
        
        answer.setStopIntercept(getStopIntercept());

        // hack: now we need to replace the proceed of the proxy with its own
        // a bit ugly, operating based on the assumption that the proceed is
        // in its outputs (if proceed() was called) and/or in the
        // outputs of the otherwise or last when clause for the predicated version.
        if (answer.getOutputs().size() > 0) {
            // this is for the predicate version or if a choice() is present
            ChoiceType choice = null;
            for (ProcessorType processor : answer.getOutputs()) {
                if (processor instanceof ChoiceType) {
                    // special cases for predicates (choices)
                    choice = (ChoiceType) processor;

                    // for the predicated version we add the proceed() to otherwise()
                    // before knowing if stop() will follow, so let's make a small adjustment
                    if (usePredicate && getStopIntercept()) {
                        WhenType when = choice.getWhenClauses().get(0);
                        when.getOutputs().remove(this.getProceed());
                    }

                    // add proceed to the when clause
                    addProceedProxy(this.getProceed(), answer.getProceed(),
                        choice.getWhenClauses().get(choice.getWhenClauses().size() - 1), usePredicate && !getStopIntercept());

                    // force adding a proceed at the end (otherwise) if its not a stop type
                    addProceedProxy(this.getProceed(), answer.getProceed(), choice.getOtherwise(), !getStopIntercept());

                    if (getStopIntercept()) {
                        // must add proceed to when clause if stop is explictiy declared, otherwise when the
                        // predicate test fails then there is no proceed
                        // See example: InterceptorSimpleRouteTest (City Paris is never proceeded)  
                        addProceedProxy(this.getProceed(), answer.getProceed(),
                            choice.getWhenClauses().get(choice.getWhenClauses().size() - 1), usePredicate);
                    }

                    break;
                }
            }
            if (choice == null) {
                // force adding a proceed at the end if its not a stop type
                addProceedProxy(this.getProceed(), answer.getProceed(), answer, !getStopIntercept());
            }
        }

        return answer;
    }

    private void addProceedProxy(ProceedType orig, ProceedType proxy, ProcessorType<?> processor, boolean force) {
        int index = processor.getOutputs().indexOf(orig);
        if (index >= 0) {
            processor.addOutput(proxy);
            // replace original proceed with proxy
            List<ProcessorType<?>> outs = processor.getOutputs();
            outs.remove(proxy);
            outs.set(index, proxy);
        } else if (force) {
            processor.addOutput(proxy);
        }
    }

    public void setStopIntercept(Boolean stop) {
        this.stopIntercept = stop;
    }

    public Boolean getStopIntercept() {
        return stopIntercept;
    }

}
