/*
 * Copyright (c) 2011-2015 Pivotal Software Inc., Inc. All Rights Reserved.
 *
 * Licensed 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 reactor.bus;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.bus.registry.Registration;
import reactor.bus.registry.Registries;
import reactor.bus.registry.Registry;
import reactor.bus.selector.Selector;
import reactor.bus.selector.Selectors;
import reactor.fn.Consumer;
import reactor.fn.Function;
import reactor.fn.tuple.Tuple;
import reactor.fn.tuple.Tuple2;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;

import static org.hamcrest.MatcherAssert.assertThat;
import static reactor.bus.selector.JsonPathSelector.J;

/**
 * @author Jon Brisbin
 */
public class SelectorUnitTests {

	static final Logger LOG        = LoggerFactory.getLogger(SelectorUnitTests.class);
	final        int    selectors  = 50;
	final        int    iterations = 10000;

	final ObjectMapper mapper = new ObjectMapper();

	@Test
	public void testSelectionThroughput() throws Exception {
		runTest("ObjectSelector", new Function<Integer, Tuple2<Selector, Object>>() {
			@Override
			public Tuple2<Selector, Object> apply(Integer i) {
				String key = "test" + i;
				return Tuple.<Selector, Object>of(Selectors.$(key), key);
			}
		});
	}

	@Test
	public void testUriTemplateSelectorThroughput() throws Exception {
		runTest("UriPathSelector", new Function<Integer, Tuple2<Selector, Object>>() {
			@Override
			public Tuple2<Selector, Object> apply(Integer i) {
				String key = "/test/" + i;
				return Tuple.<Selector, Object>of(Selectors.U(key), key);
			}
		});
	}

	@Test
	public void testJsonPathSelectorThroughput() {
		final String jsonTmpl = "{\"data\": [{\"run\": %s}]}";
		final String jsonPathTmpl = "$.data[?(@.run == %s)]";

		runTest("JsonPath[String]", new Function<Integer, Tuple2<Selector, Object>>() {
			@Override
			public Tuple2<Selector, Object> apply(Integer i) {
				Selector sel = J(String.format(jsonPathTmpl, i));
				String json = String.format(jsonTmpl, i);
				return Tuple.<Selector, Object>of(sel, json);
			}
		});

		runTest("JsonPath[POJO]", new Function<Integer, Tuple2<Selector, Object>>() {
			@Override
			public Tuple2<Selector, Object> apply(Integer i) {
				Selector sel = J(String.format(jsonPathTmpl, i));
				DataNode node = new DataNode(new DataNode.Run(i));
				return Tuple.<Selector, Object>of(sel, node);
			}
		});

		runTest("JsonPath[Map]", new Function<Integer, Tuple2<Selector, Object>>() {
			@Override
			public Tuple2<Selector, Object> apply(Integer i) {
				Selector sel = J(String.format(jsonPathTmpl, i));
				String json = String.format(jsonTmpl, i);
				Object key;
				try {
					key = mapper.readValue(json, Map.class);
				} catch(IOException e) {
					throw new IllegalStateException(e);
				}
				return Tuple.<Selector, Object>of(sel, key);
			}
		});

		runTest("JsonPath[Tree]", new Function<Integer, Tuple2<Selector, Object>>() {
			@Override
			public Tuple2<Selector, Object> apply(Integer i) {
				Selector sel = J(String.format(jsonPathTmpl, i));
				String json = String.format(jsonTmpl, i);
				Object key;
				try {
					key = mapper.readTree(json);
				} catch(IOException e) {
					throw new IllegalStateException(e);
				}
				return Tuple.<Selector, Object>of(sel, key);
			}
		});
	}

	@SuppressWarnings("unchecked")
	private void runTest(String type, Function<Integer, Tuple2<Selector, Object>> fn) {
		final AtomicLong counter = new AtomicLong(selectors * iterations);
		Registry<Object, Consumer<?>> registry = Registries.create();

		Consumer<?> countDown = new Consumer<Object>() {
			@Override
			public void accept(Object obj) {
				counter.decrementAndGet();
			}
		};

		Selector<Object>[] sels = new Selector[selectors];
		Object[] keys = new Object[selectors];

		for(int i = 0; i < selectors; i++) {
			Tuple2<Selector, Object> tup = fn.apply(i);
			sels[i] = tup.getT1();
			keys[i] = tup.getT2();
			registry.register(sels[i], countDown);
		}

		long start = System.currentTimeMillis();
		for(int i = 0; i < selectors * iterations; i++) {
			int j = i % selectors;
			for(Registration<?, ? extends Consumer<?>> reg : registry.select(keys[j])) {
				reg.getObject().accept(null);
			}
		}
		long end = System.currentTimeMillis();
		double elapsed = (end - start);
		long throughput = Math.round((selectors * iterations) / (elapsed / 1000));
		LOG.info("{} throughput: {}M/s in {}ms", type, throughput, Math.round(elapsed));

		assertThat("All handlers have been found and executed.", counter.get() == 0);
	}

	public static class DataNode {
		private final List<Run> data = new ArrayList<Run>();

		private DataNode(Run data) {
			this.data.add(data);
		}

		public List<Run> getData() {
			return data;
		}

		public static class Run {
			private final int run;

			Run(int run) {
				this.run = run;
			}

			public int getRun() {
				return run;
			}
		}
	}

}
