/**
 * 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.openejb.server.discovery;

import org.apache.openejb.loader.Options;
import org.apache.openejb.monitoring.Event;
import org.apache.openejb.monitoring.Managed;
import org.apache.openejb.server.DiscoveryAgent;
import org.apache.openejb.server.DiscoveryListener;
import org.apache.openejb.server.SelfManaging;
import org.apache.openejb.server.ServerService;
import org.apache.openejb.server.ServiceException;
import org.apache.openejb.util.Duration;
import org.apache.openejb.util.LogCategory;
import org.apache.openejb.util.Logger;
import org.apache.openejb.util.OptionsLog;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.URI;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * @version $Rev$ $Date$
 */
public class MultipointDiscoveryAgent implements DiscoveryAgent, ServerService, SelfManaging {

    private static final Logger LOGGER = Logger.getInstance(LogCategory.OPENEJB_SERVER.createChild("discovery").createChild("multipoint"), MultipointDiscoveryAgent.class);

    private final AtomicBoolean running = new AtomicBoolean(false);

    @Managed
    private String host = "127.0.0.1";

    @Managed
    private int port = 4212;

    private String initialServers = "";

    private long heartRate = 500;

    @Managed(append = false)
    private Tracker tracker;

    private MultipointServer multipointServer;

    private boolean debug = true;
    private String name;
    private String discoveryHost;
    private Set<URI> roots;
    private Duration reconnectDelay;

    @Managed
    private final Event restarts = new Event();

    public MultipointDiscoveryAgent() {
    }

    public MultipointDiscoveryAgent(final boolean debug, final String name) {
        this.debug = debug;
        this.name = name;
    }

    @Override
    public void init(final Properties props) {

        final Options options = new Options(props);
        options.setLogger(new OptionsLog(LOGGER));

        host = props.getProperty("bind", host);
        port = options.get("port", port);
        initialServers = options.get("initialServers", initialServers);
        heartRate = options.get("heart_rate", heartRate);
        discoveryHost = options.get("discoveryHost", host);
        name = name != null ? name : options.get("discoveryName", MultipointServer.randomColor());
        reconnectDelay = options.get("reconnectDelay", new Duration("30 seconds"));

        final Set<URI> uris = new LinkedHashSet<>();

        // Connect the initial set of peer servers
        final StringTokenizer st = new StringTokenizer(initialServers, ",");
        while (st.hasMoreTokens()) {
            final String string = st.nextToken().trim();
            if (string.startsWith("conn://")) {
                final URI uri = URI.create(string);
                uris.add(uri);
            } else {
                final URI uri = URI.create("conn://" + string);
                uris.add(uri);
            }
        }

        roots = uris;

        final Tracker.Builder builder = new Tracker.Builder();
        builder.setHeartRate(heartRate);
        builder.setGroup(props.getProperty("group", builder.getGroup()));
        builder.setMaxMissedHeartbeats(options.get("max_missed_heartbeats", builder.getMaxMissedHeartbeats()));
        builder.setMaxReconnectDelay(options.get("max_reconnect_delay", builder.getMaxReconnectDelay()));
        builder.setReconnectDelay(options.get("reconnect_delay", builder.getReconnectDelay()));
        builder.setExponentialBackoff(options.get("exponential_backoff", builder.getExponentialBackoff()));
        builder.setMaxReconnectAttempts(options.get("max_reconnect_attempts", builder.getMaxReconnectAttempts()));
        builder.setDebug(debug);

        tracker = builder.build();
    }

    @Override
    public String getIP() {
        return host;
    }

    @Override
    public String getName() {
        return "multipoint";
    }

    @Override
    public int getPort() {
        return port;
    }

    public String getInitialServers() {
        return initialServers;
    }

    @Override
    public void setDiscoveryListener(final DiscoveryListener listener) {
        this.tracker.setDiscoveryListener(listener);
    }

    @Override
    public void registerService(final URI serviceUri) throws IOException {
        tracker.registerService(serviceUri);
    }

    @Override
    public void unregisterService(final URI serviceUri) throws IOException {
        tracker.unregisterService(serviceUri);
    }

    @Override
    public void reportFailed(final URI serviceUri) {
        tracker.reportFailed(serviceUri);
    }

    public static void main(final String[] args) throws Exception {
    }

    /**
     * start the discovery agent
     *
     * @throws ServiceException On error
     */
    @Override
    @Managed
    public void start() throws ServiceException {
        try {
            if (running.compareAndSet(false, true)) {
                LOGGER.info("MultipointDiscoveryAgent Starting");
                multipointServer = new MultipointServer(host, discoveryHost, port, tracker, name, debug, roots, reconnectDelay).start();
                LOGGER.info("MultipointDiscoveryAgent Started");

                this.port = multipointServer.getPort();

            }
        } catch (Exception e) {
            throw new ServiceException(port + "", e);
        }
    }

    @Managed
    public void restart() throws ServiceException {
        stop();
        start();
        restarts.record();
    }

    /**
     * stop the channel
     *
     * @throws ServiceException On error
     */
    @Override
    @Managed
    public void stop() throws ServiceException {
        if (running.compareAndSet(true, false)) {
            LOGGER.info("MultipointDiscoveryAgent Stopping");
            multipointServer.stop();
        }
    }

    @Override
    public void service(final InputStream in, final OutputStream out) throws ServiceException, IOException {
    }

    @Override
    public void service(final Socket socket) throws ServiceException, IOException {
    }

    public String getHost() {
        return host;
    }

    public void setHost(final String host) {
        this.host = host;
    }

    @Managed
    public URI getURI() {
        return multipointServer.getMe();
    }

    @Managed
    public Set<URI> getRoots() {
        return multipointServer.getRoots();
    }

    @Managed
    public long getRuns() {
        return multipointServer.getRuns().get();
    }

    @Managed
    public String getRunsLatest() {
        return multipointServer.getRuns().getLatest();
    }

    @Managed
    public long getRunsLatestTime() {
        return multipointServer.getRuns().getLatestTime();
    }

    @Managed
    public long getHeartbeats() {
        return multipointServer.getHeartbeats().get();
    }

    @Managed
    public String getHeartbeatsLatest() {
        return multipointServer.getHeartbeats().getLatest();
    }

    @Managed
    public long getHeartbeatsLatestTime() {
        return multipointServer.getHeartbeats().getLatestTime();
    }

    @Managed
    public long getSessionsCreated() {
        return multipointServer.getSessionsCreated().get();
    }

    @Managed
    public String getSessionsCreatedLatest() {
        return multipointServer.getSessionsCreated().getLatest();
    }

    @Managed
    public long getSessionsCreatedLatestTime() {
        return multipointServer.getSessionsCreated().getLatestTime();
    }

    @Managed
    public long getReconnects() {
        return multipointServer.getReconnects().get();
    }

    @Managed
    public String getReconnectsLatest() {
        return multipointServer.getReconnects().getLatest();
    }

    @Managed
    public long getReconnectsLatestTime() {
        return multipointServer.getReconnects().getLatestTime();
    }

    @Managed
    public long getJoined() {
        return multipointServer.getJoined();
    }

    @Managed
    public List<URI> getSessions() {
        return multipointServer.getSessions();
    }

    @Managed
    public List<URI> getConnectionsQueued() {
        return multipointServer.getConnectionsQueued();
    }

    @Managed
    public long getReconnectDelay() {
        return multipointServer.getReconnectDelay();
    }

}