/*
 * Copyright 2013 the original author or authors.
 *
 * 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 org.springframework.batch.core.jsr.configuration.xml;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.springframework.batch.core.jsr.JsrTestUtils.runJob;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.batch.api.BatchProperty;
import javax.batch.api.Batchlet;
import javax.batch.api.chunk.AbstractItemReader;
import javax.batch.api.chunk.AbstractItemWriter;
import javax.batch.api.partition.PartitionPlan;
import javax.batch.api.partition.PartitionPlanImpl;
import javax.batch.runtime.BatchStatus;
import javax.batch.runtime.JobExecution;
import javax.batch.runtime.context.JobContext;
import javax.batch.runtime.context.StepContext;
import javax.inject.Inject;

import org.junit.Before;
import org.junit.Test;
import org.springframework.util.Assert;

public class PartitionParserTests {
	private Pattern caPattern = Pattern.compile("ca");
	private Pattern asPattern = Pattern.compile("AS");
	private static final long TIMEOUT = 10000l;

	@Before
	public void before() {
		MyBatchlet.processed = 0;
		MyBatchlet.threadNames = Collections.synchronizedSet(new HashSet<String>());
		MyBatchlet.artifactNames = Collections.synchronizedSet(new HashSet<String>());
		PartitionCollector.artifactNames = Collections.synchronizedSet(new HashSet<String>());
	}

	@Test
	public void testBatchletNoProperties() throws Exception {
		BatchStatus curBatchStatus = runJob("partitionParserTestsBatchlet", new Properties(), TIMEOUT).getBatchStatus();

		assertEquals(BatchStatus.COMPLETED, curBatchStatus);
		assertEquals(10, MyBatchlet.processed);
		assertEquals(10, MyBatchlet.threadNames.size());
	}

	@Test
	public void testChunkNoProperties() throws Exception {
		JobExecution execution = runJob("partitionParserTestsChunk", new Properties(), TIMEOUT);

		assertEquals(BatchStatus.COMPLETED, execution.getBatchStatus());
		assertEquals(30, ItemReader.processedItems.size());
		assertEquals(10, ItemReader.threadNames.size());
		assertEquals(30, ItemWriter.processedItems.size());
		assertEquals(10, ItemWriter.threadNames.size());
	}

	@Test
	public void testFullPartitionConfiguration() throws Exception {
		JobExecution execution = runJob("fullPartitionParserTests", new Properties(), TIMEOUT);

		assertEquals(BatchStatus.COMPLETED, execution.getBatchStatus());
		assertTrue(execution.getExitStatus().startsWith("BPS_"));
		assertTrue(execution.getExitStatus().endsWith("BPSC_APSC"));
		assertEquals(3, countMatches(execution.getExitStatus(), caPattern));
		assertEquals(3, countMatches(execution.getExitStatus(), asPattern));
		assertEquals(3, MyBatchlet.processed);
		assertEquals(3, MyBatchlet.threadNames.size());
	}

	@Test
	public void testFullPartitionConfigurationWithProperties() throws Exception {
		JobExecution execution = runJob("fullPartitionParserWithPropertiesTests", new Properties(), TIMEOUT);

		assertEquals(BatchStatus.COMPLETED, execution.getBatchStatus());
		assertTrue(execution.getExitStatus().startsWith("BPS_"));
		assertTrue(execution.getExitStatus().endsWith("BPSC_APSC"));
		assertEquals(3, countMatches(execution.getExitStatus(), caPattern));
		assertEquals(3, countMatches(execution.getExitStatus(), asPattern));
		assertEquals(3, MyBatchlet.processed);
		assertEquals(3, MyBatchlet.threadNames.size());
		assertEquals(MyBatchlet.artifactNames.iterator().next(), "batchlet");
		assertEquals(PartitionMapper.name, "mapper");
		assertEquals(PartitionAnalyzer.name, "analyzer");
		assertEquals(PartitionReducer.name, "reducer");
		assertEquals(PartitionCollector.artifactNames.size(), 1);
		assertTrue(PartitionCollector.artifactNames.contains("collector"));
	}

	@Test
	public void testFullPartitionConfigurationWithMapperSuppliedProperties() throws Exception {
		JobExecution execution = runJob("fullPartitionParserWithMapperPropertiesTests", new Properties(), TIMEOUT);

		assertEquals(BatchStatus.COMPLETED, execution.getBatchStatus());
		assertTrue(execution.getExitStatus().startsWith("BPS_"));
		assertTrue(execution.getExitStatus().endsWith("BPSC_APSC"));
		assertEquals(3, countMatches(execution.getExitStatus(), caPattern));
		assertEquals(3, countMatches(execution.getExitStatus(), asPattern));
		assertEquals(3, MyBatchlet.processed);
		assertEquals(3, MyBatchlet.threadNames.size());

		assertEquals(MyBatchlet.artifactNames.size(), 3);
		assertTrue(MyBatchlet.artifactNames.contains("batchlet0"));
		assertTrue(MyBatchlet.artifactNames.contains("batchlet1"));
		assertTrue(MyBatchlet.artifactNames.contains("batchlet2"));
		assertEquals(PartitionCollector.artifactNames.size(), 3);
		assertTrue(PartitionCollector.artifactNames.contains("collector0"));
		assertTrue(PartitionCollector.artifactNames.contains("collector1"));
		assertTrue(PartitionCollector.artifactNames.contains("collector2"));

		assertEquals(PartitionMapper.name, "mapper");
		assertEquals(PartitionAnalyzer.name, "analyzer");
		assertEquals(PartitionReducer.name, "reducer");
	}

	@Test
	public void testFullPartitionConfigurationWithHardcodedProperties() throws Exception {
		JobExecution execution = runJob("fullPartitionParserWithHardcodedPropertiesTests", new Properties(), TIMEOUT);

		assertEquals(BatchStatus.COMPLETED, execution.getBatchStatus());
		assertTrue(execution.getExitStatus().startsWith("BPS_"));
		assertTrue(execution.getExitStatus().endsWith("BPSC_APSC"));
		assertEquals(3, countMatches(execution.getExitStatus(), caPattern));
		assertEquals(3, countMatches(execution.getExitStatus(), asPattern));
		assertEquals(3, MyBatchlet.processed);
		assertEquals(3, MyBatchlet.threadNames.size());

		assertEquals(MyBatchlet.artifactNames.size(), 3);
		assertTrue(MyBatchlet.artifactNames.contains("batchlet0"));
		assertTrue(MyBatchlet.artifactNames.contains("batchlet1"));
		assertTrue(MyBatchlet.artifactNames.contains("batchlet2"));
		assertEquals(PartitionCollector.artifactNames.size(), 3);
		assertTrue(PartitionCollector.artifactNames.contains("collector0"));
		assertTrue(PartitionCollector.artifactNames.contains("collector1"));
		assertTrue(PartitionCollector.artifactNames.contains("collector2"));

		assertEquals(PartitionMapper.name, "mapper");
		assertEquals(PartitionAnalyzer.name, "analyzer");
		assertEquals(PartitionReducer.name, "reducer");
	}

	private int countMatches(String string, Pattern pattern) {
		Matcher matcher = pattern.matcher(string);

		int count = 0;
		while(matcher.find()) {
			count++;
		}

		return count;
	}

	public static class PartitionReducer implements javax.batch.api.partition.PartitionReducer {

		public static String name;

		@Inject
		@BatchProperty
		String artifactName;

		@Inject
		protected JobContext jobContext;

		@Override
		public void beginPartitionedStep() throws Exception {
			name = artifactName;
			jobContext.setExitStatus("BPS_");
		}

		@Override
		public void beforePartitionedStepCompletion() throws Exception {
			jobContext.setExitStatus(jobContext.getExitStatus() + "BPSC_");
		}

		@Override
		public void rollbackPartitionedStep() throws Exception {
			jobContext.setExitStatus(jobContext.getExitStatus() + "RPS");
		}

		@Override
		public void afterPartitionedStepCompletion(PartitionStatus status)
				throws Exception {
			jobContext.setExitStatus(jobContext.getExitStatus() + "APSC");
		}
	}

	public static class PartitionAnalyzer implements javax.batch.api.partition.PartitionAnalyzer {

		public static String name;

		@Inject
		@BatchProperty
		String artifactName;

		@Inject
		protected JobContext jobContext;

		@Override
		public void analyzeCollectorData(Serializable data) throws Exception {
			name = artifactName;

			Assert.isTrue(data.equals("c"));
			jobContext.setExitStatus(jobContext.getExitStatus() + data + "a");
		}

		@Override
		public void analyzeStatus(BatchStatus batchStatus, String exitStatus)
				throws Exception {
			Assert.isTrue(batchStatus.equals(BatchStatus.COMPLETED));
			jobContext.setExitStatus(jobContext.getExitStatus() + "AS");
		}
	}

	public static class PartitionCollector implements javax.batch.api.partition.PartitionCollector {

		protected static Set<String> artifactNames = Collections.synchronizedSet(new HashSet<String>());

		@Inject
		@BatchProperty
		String artifactName;

		@Override
		public Serializable collectPartitionData() throws Exception {
			artifactNames.add(artifactName);
			return "c";
		}
	}

	public static class PropertyPartitionMapper implements javax.batch.api.partition.PartitionMapper {

		@Override
		public PartitionPlan mapPartitions() throws Exception {
			Properties[] props = new Properties[3];

			for(int i = 0; i < props.length; i++) {
				props[i] = new Properties();
				props[i].put("collectorName", "collector" + i);
				props[i].put("batchletName", "batchlet" + i);
			}

			PartitionPlan plan = new PartitionPlanImpl();
			plan.setPartitions(3);
			plan.setThreads(3);
			plan.setPartitionProperties(props);

			return plan;
		}
	}

	public static class PartitionMapper implements javax.batch.api.partition.PartitionMapper {

		public static String name;

		@Inject
		@BatchProperty
		public String artifactName;

		@Override
		public PartitionPlan mapPartitions() throws Exception {
			name = artifactName;

			PartitionPlan plan = new PartitionPlanImpl();
			plan.setPartitions(3);
			plan.setThreads(3);

			return plan;
		}
	}

	public static class MyBatchlet implements Batchlet {

		protected static int processed = 0;
		protected static Set<String> threadNames = Collections.synchronizedSet(new HashSet<String>());
		protected static Set<String> artifactNames = Collections.synchronizedSet(new HashSet<String>());

		@Inject
		@BatchProperty
		String artifactName;

		@Inject
		StepContext stepContext;

		@Inject
		JobContext jobContext;

		@Override
		public String process() throws Exception {
			artifactNames.add(artifactName);
			threadNames.add(Thread.currentThread().getName());
			processed++;

			stepContext.setExitStatus("bad step exit status");
			jobContext.setExitStatus("bad job exit status");

			return null;
		}

		@Override
		public void stop() throws Exception {
		}
	}

	public static class ItemReader extends AbstractItemReader {

		private List<Integer> items;
		protected static Vector<Integer> processedItems = new Vector<Integer>();
		protected static Set<String> threadNames = Collections.synchronizedSet(new HashSet<String>());

		@Override
		public void open(Serializable checkpoint) throws Exception {
			items = new ArrayList<Integer>();
			items.add(1);
			items.add(2);
			items.add(3);
		}

		@Override
		public Object readItem() throws Exception {
			threadNames.add(Thread.currentThread().getName());
			if(items.size() > 0) {
				Integer curItem = items.remove(0);
				processedItems.add(curItem);
				return curItem;
			} else {
				return null;
			}
		}
	}

	public static class ItemWriter extends AbstractItemWriter {

		protected static Vector<Integer> processedItems = new Vector<Integer>();
		protected static Set<String> threadNames = Collections.synchronizedSet(new HashSet<String>());

		@Override
		public void writeItems(List<Object> items) throws Exception {
			threadNames.add(Thread.currentThread().getName());
			for (Object object : items) {
				processedItems.add((Integer) object);
			}
		}
	}
}
