/**
 * 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.component.routebox;

import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.camel.CamelContext;
import org.apache.camel.ProducerTemplate;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.routebox.strategy.RouteboxDispatchStrategy;
import org.apache.camel.impl.DefaultCamelContext;
import org.apache.camel.spi.Metadata;
import org.apache.camel.spi.Registry;
import org.apache.camel.spi.UriParam;
import org.apache.camel.spi.UriParams;
import org.apache.camel.spi.UriPath;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@UriParams
public class RouteboxConfiguration {
    private static final Logger LOG = LoggerFactory.getLogger(RouteboxConfiguration.class);

    private URI uri;
    private String authority;

    @UriPath @Metadata(required = "true")
    private String routeboxName;
    @UriParam
    private RouteboxDispatchStrategy dispatchStrategy;
    @UriParam
    private Map<String, String> dispatchMap;
    @UriParam(defaultValue = "true")
    private boolean forkContext = true;
    @UriParam(label = "producer", defaultValue = "20000")
    private long connectionTimeout = 20000;
    @UriParam(label = "consumer", defaultValue = "1000")
    private long pollInterval = 1000;
    @UriParam(defaultValue = "direct", enums = "direct,seda")
    private String innerProtocol = "direct";
    @UriParam(label = "consumer", defaultValue = "20")
    private int threads = 20;
    @UriParam
    private int queueSize;
    @UriParam(label = "producer", defaultValue = "true")
    private boolean sendToConsumer = true;
    @UriParam(label = "advanced")
    private CamelContext innerContext;
    @UriParam(label = "advanced")
    private Registry innerRegistry;
    @UriParam(label = "advanced")
    private ProducerTemplate innerProducerTemplate;
    @UriParam(label = "advanced", javaType = "java.lang.String")
    private List<RouteBuilder> routeBuilders = new ArrayList<>();

    public RouteboxConfiguration() {
    }

    public RouteboxConfiguration(URI uri) {
        this();
        this.uri = uri;
    }

    @SuppressWarnings("unchecked")
    public void parseURI(URI uri, Map<String, Object> parameters, RouteboxComponent component) throws Exception {
        String protocol = uri.getScheme();
        
        if (!protocol.equalsIgnoreCase("routebox")) {
            throw new IllegalArgumentException("Unrecognized protocol: " + protocol + " for uri: " + uri);
        }
        
        setUri(uri);
        setAuthority(uri.getAuthority());
        if (LOG.isTraceEnabled()) {
            LOG.trace("Authority: {}", uri.getAuthority());
        }
        
        setRouteboxName(getAuthority());
        
        if (parameters.containsKey("threads")) {
            setThreads(Integer.valueOf((String) parameters.get("threads")));
        }
        
        if (parameters.containsKey("forkContext")) {
            if (!(Boolean.valueOf((String) parameters.get("forkContext")))) {
                setForkContext(false);
            }
        }
        
        if (parameters.containsKey("innerProtocol")) {
            setInnerProtocol((String) parameters.get("innerProtocol"));
            if ((!innerProtocol.equalsIgnoreCase("seda")) && (!innerProtocol.equalsIgnoreCase("direct"))) {
                throw new IllegalArgumentException("Unrecognized inner protocol: " + innerProtocol + " for uri: " + uri);
            }
        } else {
            setInnerProtocol("direct");
        }
        
        if (parameters.containsKey("sendToConsumer")) {
            if (!Boolean.valueOf((String) parameters.get("sendToConsumer"))) {
                setSendToConsumer(false);
            }
        }
        
        if (parameters.containsKey("connectionTimeout")) {
            setConnectionTimeout(Long.parseLong((String) parameters.get("connectionTimeout")));
        }
        
        if (parameters.containsKey("pollInterval")) {
            setConnectionTimeout(Long.parseLong((String) parameters.get("pollInterval")));
        }
        
        if (parameters.containsKey("routeBuilders")) {
            routeBuilders = component.resolveAndRemoveReferenceParameter(parameters, "routeBuilders", List.class);
        }
        
        if (parameters.containsKey("innerRegistry")) {
            innerRegistry = component.resolveAndRemoveReferenceParameter(parameters, "innerRegistry", Registry.class);
        }
        
        if (isForkContext()) {
            if (innerRegistry != null) {
                innerContext = component.resolveAndRemoveReferenceParameter(parameters, "innerContext", CamelContext.class, new DefaultCamelContext(innerRegistry));
            } else {
                innerContext = component.resolveAndRemoveReferenceParameter(parameters, "innerContext", CamelContext.class, new DefaultCamelContext());
            }
        } else {
            innerContext = component.getCamelContext();
        }
        
        innerProducerTemplate = innerContext.createProducerTemplate();
        setQueueSize(component.getAndRemoveParameter(parameters, "size", Integer.class, 0));

        dispatchStrategy = component.resolveAndRemoveReferenceParameter(parameters, "dispatchStrategy", RouteboxDispatchStrategy.class, null);
        dispatchMap = component.resolveAndRemoveReferenceParameter(parameters, "dispatchMap", HashMap.class, new HashMap<String, String>());
        if (dispatchStrategy == null && dispatchMap == null) {
            LOG.warn("No Routebox Dispatch Map or Strategy has been set. Routebox may not have more than one inner route.");
        }        
    }

    public URI getUri() {
        return uri;
    }

    public void setUri(URI uri) {
        this.uri = uri;
    }

    public String getAuthority() {
        return authority;
    }

    public void setAuthority(String authority) {
        this.authority = authority;
    }

    public CamelContext getInnerContext() {
        return innerContext;
    }

    /**
     * A string representing a key in the Camel Registry matching an object value of the type org.apache.camel.CamelContext.
     * If a CamelContext is not provided by the user a CamelContext is automatically created for deployment of inner routes.
     */
    public void setInnerContext(CamelContext innerContext) {
        this.innerContext = innerContext;
    }

    /**
     * A string representing a key in the Camel Registry matching an object value of the type List<org.apache.camel.builder.RouteBuilder>.
     * If the user does not supply an innerContext pre-primed with inner routes, the routeBuilders option must be provided as a non-empty
     * list of RouteBuilders containing inner routes
     */
    public void setRouteBuilders(List<RouteBuilder> routeBuilders) {
        this.routeBuilders = routeBuilders;
    }

    public List<RouteBuilder> getRouteBuilders() {
        return routeBuilders;
    }

    /**
     * Whether to fork and create a new inner CamelContext instead of reusing the same CamelContext.
     */
    public void setForkContext(boolean forkContext) {
        this.forkContext = forkContext;
    }
    
    public boolean isForkContext() {
        return forkContext;
    }

    /**
     * Number of threads to be used by the routebox to receive requests.
     */
    public void setThreads(int threads) {
        this.threads = threads;
    }

    public int getThreads() {
        return threads;
    }

    /**
     * Logical name for the routebox (eg like a queue name)
     */
    public void setRouteboxName(String routeboxName) {
        this.routeboxName = routeboxName;
    }

    public String getRouteboxName() {
        return routeboxName;
    }

    /**
     * To use a custom RouteboxDispatchStrategy which allows to use custom dispatching instead of the default.
     */
    public void setDispatchStrategy(RouteboxDispatchStrategy dispatchStrategy) {
        this.dispatchStrategy = dispatchStrategy;
    }

    public RouteboxDispatchStrategy getDispatchStrategy() {
        return dispatchStrategy;
    }

    /**
     * Timeout in millis used by the producer when sending a message.
     */
    public void setConnectionTimeout(long connectionTimeout) {
        this.connectionTimeout = connectionTimeout;
    }

    public long getConnectionTimeout() {
        return connectionTimeout;
    }

    public long getPollInterval() {
        return pollInterval;
    }

    /**
     * The timeout used when polling from seda.
     * When a timeout occurs, the consumer can check whether it is allowed to continue running.
     * Setting a lower value allows the consumer to react more quickly upon shutdown.
     */
    public void setPollInterval(long pollInterval) {
        this.pollInterval = pollInterval;
    }

    /**
     * Create a fixed size queue to receive requests.
     */
    public void setQueueSize(int queueSize) {
        this.queueSize = queueSize;
    }

    public int getQueueSize() {
        return queueSize;
    }

    public void setInnerProducerTemplate(ProducerTemplate innerProducerTemplate) {
        this.innerProducerTemplate = innerProducerTemplate;
    }

    /**
     * The ProducerTemplate to use by the internal embeded CamelContext
     */
    public ProducerTemplate getInnerProducerTemplate() {
        return innerProducerTemplate;
    }

    /**
     * The Protocol used internally by the Routebox component. Can be Direct or SEDA. The Routebox component currently offers protocols that are JVM bound.
     */
    public void setInnerProtocol(String innerProtocol) {
        this.innerProtocol = innerProtocol;
    }

    public String getInnerProtocol() {
        return innerProtocol;
    }

    /**
     * To use a custom registry for the internal embedded CamelContext.
     */
    public void setInnerRegistry(Registry innerRegistry) {
        this.innerRegistry = innerRegistry;
    }

    public Registry getInnerRegistry() {
        return innerRegistry;
    }

    /**
     * Dictates whether a Producer endpoint sends a request to an external routebox consumer.
     * If the setting is false, the Producer creates an embedded inner context and processes requests internally.
     */
    public void setSendToConsumer(boolean sendToConsumer) {
        this.sendToConsumer = sendToConsumer;
    }

    public boolean isSendToConsumer() {
        return sendToConsumer;
    }

    /**
     * A string representing a key in the Camel Registry matching an object value of the type HashMap<String, String>.
     * The HashMap key should contain strings that can be matched against the value set for the exchange header ROUTE_DISPATCH_KEY.
     * The HashMap value should contain inner route consumer URI's to which requests should be directed.
     */
    public void setDispatchMap(Map<String, String> dispatchMap) {
        this.dispatchMap = dispatchMap;
    }

    public Map<String, String> getDispatchMap() {
        return dispatchMap;
    }

}
