/**
 * 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.activemq.broker.region.virtual;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.activemq.broker.Broker;
import org.apache.activemq.broker.ConnectionContext;
import org.apache.activemq.broker.region.Destination;
import org.apache.activemq.command.ActiveMQDestination;
import org.apache.activemq.command.ActiveMQQueue;
import org.apache.activemq.command.ActiveMQTopic;
import org.apache.activemq.filter.DestinationFilter;

/**
 * Creates <a href="https://activemq.apache.org/virtual-destinations">Virtual
 * Topics</a> using a prefix and postfix. The virtual destination creates a
 * wildcard that is then used to look up all active queue subscriptions which
 * match.
 *
 * @org.apache.xbean.XBean
 */
public class VirtualTopic implements VirtualDestination {

    private String prefix = "Consumer.*.";
    private String postfix = "";
    private String name = ">";
    private boolean selectorAware = false;
    private boolean local = false;
    private boolean concurrentSend = false;
    private boolean transactedSend = false;
    private boolean dropOnResourceLimit = false;
    private boolean setOriginalDestination = true;

    @Override
    public ActiveMQDestination getVirtualDestination() {
        return new ActiveMQTopic(getName());
    }

    @Override
    public Destination intercept(Destination destination) {
        return selectorAware ? new SelectorAwareVirtualTopicInterceptor(destination, this) :
                new VirtualTopicInterceptor(destination, this);
    }

    @Override
    public ActiveMQDestination getMappedDestinations() {
        return new ActiveMQQueue(prefix + name + postfix);
    }

    @Override
    public Destination interceptMappedDestination(Destination destination) {
        // do a reverse map from destination to get actual virtual destination
        final String physicalName = destination.getActiveMQDestination().getPhysicalName();
        final Pattern pattern = Pattern.compile(getRegex(prefix) + "(.+)" + getRegex(postfix));
        final Matcher matcher = pattern.matcher(physicalName);
        if (matcher.matches()) {
            final String virtualName = matcher.group(1);
            return new MappedQueueFilter(new ActiveMQTopic(virtualName), destination);
        }
        return destination;
    }

    private String getRegex(String part) {
        StringBuilder builder = new StringBuilder();
        for (char c : part.toCharArray()) {
            switch (c) {
                case '.':
                    builder.append("\\.");
                    break;
                case '*':
                    builder.append("[^\\.]*");
                    break;
                default:
                    builder.append(c);
            }
        }
        return builder.toString();
    }

    @Override
    public void create(Broker broker, ConnectionContext context, ActiveMQDestination destination) throws Exception {
        if (destination.isQueue() && destination.isPattern()) {
            DestinationFilter filter = DestinationFilter.parseFilter(new ActiveMQQueue(prefix + DestinationFilter.ANY_DESCENDENT));
            if (filter.matches(destination)) {
                broker.addDestination(context, destination, false);

            }
        }
    }

    @Override
    public void remove(Destination destination) {
    }

    // Properties
    // -------------------------------------------------------------------------

    public String getPostfix() {
        return postfix;
    }

    /**
     * Sets any postix used to identify the queue consumers
     */
    public void setPostfix(String postfix) {
        this.postfix = postfix;
    }

    public String getPrefix() {
        return prefix;
    }

    /**
     * Sets the prefix wildcard used to identify the queue consumers for a given
     * topic
     */
    public void setPrefix(String prefix) {
        this.prefix = prefix;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    /**
     * Indicates whether the selectors of consumers are used to determine
     * dispatch to a virtual destination, when true only messages matching an
     * existing consumer will be dispatched.
     *
     * @param selectorAware
     *            when true take consumer selectors into consideration
     */
    public void setSelectorAware(boolean selectorAware) {
        this.selectorAware = selectorAware;
    }

    public boolean isSelectorAware() {
        return selectorAware;
    }

    public boolean isLocal() {
        return local;
    }

    public void setLocal(boolean local) {
        this.local = local;
    }

    @Override
    public String toString() {
        return new StringBuilder("VirtualTopic:").append(prefix).append(',').append(name).append(',').
                                                  append(postfix).append(',').append(selectorAware).
                                                  append(',').append(local).toString();
    }

    public boolean isConcurrentSend() {
        return concurrentSend;
    }

    /**
     * When true, dispatch to matching destinations in parallel (in multiple threads)
     * @param concurrentSend
     */
    public void setConcurrentSend(boolean concurrentSend) {
        this.concurrentSend = concurrentSend;
    }

    public boolean isTransactedSend() {
        return transactedSend;
    }

    /**
     * When true, dispatch to matching destinations always uses a transaction.
     * @param transactedSend
     */
    public void setTransactedSend(boolean transactedSend) {
        this.transactedSend = transactedSend;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + (concurrentSend ? 1231 : 1237);
        result = prime * result + (local ? 1231 : 1237);
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        result = prime * result + ((postfix == null) ? 0 : postfix.hashCode());
        result = prime * result + ((prefix == null) ? 0 : prefix.hashCode());
        result = prime * result + (selectorAware ? 1231 : 1237);
        result = prime * result + (transactedSend ? 1231 : 1237);
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        VirtualTopic other = (VirtualTopic) obj;
        if (concurrentSend != other.concurrentSend)
            return false;
        if (local != other.local)
            return false;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        if (postfix == null) {
            if (other.postfix != null)
                return false;
        } else if (!postfix.equals(other.postfix))
            return false;
        if (prefix == null) {
            if (other.prefix != null)
                return false;
        } else if (!prefix.equals(other.prefix))
            return false;
        if (selectorAware != other.selectorAware)
            return false;
        if (transactedSend != other.transactedSend)
            return false;
        return true;
    }

    public boolean isDropOnResourceLimit() {
        return dropOnResourceLimit;
    }

    public void setDropOnResourceLimit(boolean dropOnResourceLimit) {
        this.dropOnResourceLimit = dropOnResourceLimit;
    }

    public boolean isSetOriginalDestination() {
        return setOriginalDestination;
    }

    public void setSetOriginalDestination(boolean setOriginalDestination) {
        this.setOriginalDestination = setOriginalDestination;
    }
}
