/**
 * THIS SOFTWARE IS LICENSED UNDER MIT LICENSE.<br>
 * <br>
 * Copyright 2019 Andras Berkes [[email protected]]<br>
 * Based on Moleculer Framework for NodeJS [https://moleculer.services].
 * <br><br>
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:<br>
 * <br>
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.<br>
 * <br>
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package services.moleculer.stream;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

import io.datatree.Promise;
import io.datatree.Tree;
import services.moleculer.ServiceBroker;
import services.moleculer.transporter.Transporter;

public class WrongOrderTransporter extends Transporter implements Runnable {

	protected ScheduledFuture<?> timer;

	protected static HashMap<String, ConcurrentLinkedDeque<Tree>> channels = new HashMap<>();

	@Override
	public void started(ServiceBroker broker) throws Exception {
		super.started(broker);
		timer = broker.getConfig().getScheduler().scheduleAtFixedRate(this, 500, 500, TimeUnit.MILLISECONDS);
	}

	@Override
	public void stopped() {
		super.stopped();
		if (timer != null) {
			timer.cancel(false);
			timer = null;
		}
		synchronized (channels) {
			channels.clear();
		}
		
		// Notify internal listeners
		broadcastTransporterDisconnected();
	}

	@Override
	public void connect() {
		connected();
	}

	@Override
	public void publish(String channel, Tree message) {
		HashSet<ConcurrentLinkedDeque<Tree>> queues = new HashSet<>();
		synchronized (channels) {
			for (Map.Entry<String, ConcurrentLinkedDeque<Tree>> entry : channels.entrySet()) {
				String test = entry.getKey();
				if (test.endsWith(':' + channel)) {
					queues.add(entry.getValue());
				}
			}
		}
		for (ConcurrentLinkedDeque<Tree> queue : queues) {
			queue.addLast(message);
		}
	}

	@Override
	public Promise subscribe(String channel) {
		ConcurrentLinkedDeque<Tree> queue;
		synchronized (channels) {
			queue = channels.get(nodeID + ':' + channel);
			if (queue == null) {
				queue = new ConcurrentLinkedDeque<>();
				channels.put(nodeID + ':' + channel, queue);
			}
		}
		return Promise.resolve();
	}

	public void run() {
		synchronized (channels) {
			for (Map.Entry<String, ConcurrentLinkedDeque<Tree>> entry : channels.entrySet()) {
				String channel = entry.getKey();
				if (!channel.startsWith(nodeID + ':')) {
					continue;
				}
				int i = channel.indexOf(':');
				channel = channel.substring(i + 1);
				ConcurrentLinkedDeque<Tree> queue = entry.getValue();
				while (!queue.isEmpty()) {
					Tree message = queue.removeLast();
					try {
						received(channel, serializer.write(message));
					} catch (Exception cause) {
						logger.error("Unable to serialize message!", cause);
					}
				}
			}
		}
	}

}