/*
 * jBgJob (Java Background Job) lets you schedule Java jobs to be ran in the background.  They can run in any
 * combination of other threads in the same JVM, other JVMs, or multiple other JVMs, even on different machines.
 * Copyright (C) 2013 Travis Burtrum (moparisthebest)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

package com.moparisthebest.jbgjob.processor;

import com.moparisthebest.jbgjob.AbstractScheduler;
import com.moparisthebest.jbgjob.RedisScheduler;
import com.moparisthebest.jbgjob.ScheduledItem;
import com.moparisthebest.jbgjob.ScheduledItemExecutor;
import com.moparisthebest.jbgjob.result.ExecutionResult;
import com.moparisthebest.jbgjob.result.PrintStackTraceExecutionResult;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

import java.util.Arrays;
import java.util.List;

/**
 * Simply reads from the specified queue with BRPOP and processes the job, no errors are recorded and there is no visibility into which jobs are currently being processed.
 */
public class RedisThread extends RedisScheduler implements Runnable {

	public static final int defaultTimeout;
	public static final int maxTimeoutsBeforeClose;

	public static int getIntSystemProperty(final String property, final int timeout) {
		try {
			return Integer.parseInt(System.getProperty(property, timeout + ""));
		} catch (Throwable e) {
			e.printStackTrace();
			return timeout;
		}
	}

	static {
		defaultTimeout = getIntSystemProperty("redis.timeout", 5);// 5 seconds by default
		maxTimeoutsBeforeClose = getIntSystemProperty("redis.maxTimeoutsBeforeClose", 0);// 0 by default, never close
	}

	private int timeoutCounter = 0;

	protected final String queue;
	protected final String shutdownKey;
	protected final ScheduledItemExecutor executor = new ScheduledItemExecutor();

	public RedisThread() {
		this(AbstractScheduler.defaultQueue);
	}

	public RedisThread(String queue) {
		this(null, queue);
	}

	public RedisThread(JedisPool pool, String queue) {
		this(defaultQueuePrefix, pool, queue);
	}

	public RedisThread(String queuePrefix, JedisPool pool, String queue) {
		super(queuePrefix, pool);
		this.queue = this.queuePrefix + queue;
		this.shutdownKey = this.queuePrefix + "shutdown";
	}

	protected String pollRedis(final Jedis jedis, final int timeout) {
		if (debug) System.out.printf("redis>  BRPOP %s %d\n", queue, timeout);
		final List<String> items = jedis.brpop(timeout, queue);
		//System.out.println("items: " + items);
		return (items == null || items.size() < 2) ? null : items.get(1);
	}

	private static final ExecutionResult noop = new PrintStackTraceExecutionResult();

	protected ExecutionResult getExecutionResult(final String scheduledItemString) {
		return noop;
	}

	@Override
	public final void run() {
		Jedis jedis = null;
		outer:
		while (true)
			try {
				jedis = pool.getResource();
				while (true) {
					if (debug && maxTimeoutsBeforeClose > 0) System.out.printf("maxTimeoutsBeforeClose: %d timeoutCounter: %d\n", maxTimeoutsBeforeClose, timeoutCounter);
					// check to see if we should shutdown
					if ("shutdown".equals(jedis.get(this.shutdownKey)))
						break outer;
					// grab an item, if it's null (probably timed out) try again
					final String scheduledItemString = pollRedis(jedis, defaultTimeout);
					if (scheduledItemString == null) {
						// timed out
						if (maxTimeoutsBeforeClose > 0 && ++timeoutCounter >= maxTimeoutsBeforeClose)
							break outer;
						continue;
					}
					timeoutCounter = 0;
					if (debug) System.out.println("scheduledItemString: " + scheduledItemString);
					final ScheduledItem scheduledItem = om.readValue(scheduledItemString, ScheduledItem.class);
					if (debug) System.out.println("scheduledItem object: " + scheduledItem);
					executor.execute(scheduledItem, getExecutionResult(scheduledItemString));
				}
			} catch (Throwable e) {
				e.printStackTrace();
			} finally {
				tryReturn(jedis);
			}
		this.close();
	}

	public final void tryReturn(final Jedis jedis) {
		if (jedis != null)
			try {
				pool.returnResource(jedis);
			} catch (Throwable e) {
				e.printStackTrace();
			}
	}

	@Override
	public void close() {
		super.close();
		executor.close();
	}

	protected void deleteQueue(final String... queue) {
		Jedis jedis = null;
		try {
			jedis = pool.getResource();
			if (debug) System.out.printf("redis>  DEL %s\n", Arrays.toString(queue));
			jedis.del(queue);
		} catch (Throwable e) {
			e.printStackTrace();
		} finally {
			tryReturn(jedis);
		}
	}

	/**
	 * Only meant to be used by testing frameworks like JUnit
	 */
	void deleteQueue() {
		deleteQueue(queue);
	}

	public static void main(String[] args) {
		// set all needed arguments with system properties
		new RedisThread().run();
	}
}

@@ -4274,16 +4274,113 @@
 tring);%0A
+%09%09%09%09%09final ExecutionResult executionResult = getExecutionResult(scheduledItemString);%0A%09%09%09%09%09try%7B%0A%09
 %09%09%09%09%09fin
@@ -4469,24 +4469,25 @@
 lass);%0A%09%09%09%09%09
+%09
 if (debug) S
@@ -4547,16 +4547,17 @@
 dItem);%0A
+%09
 %09%09%09%09%09exe
@@ -4585,20 +4585,17 @@
 edItem, 
-getE
+e
 xecution
@@ -4604,31 +4604,110 @@
 sult
-(scheduledItemString));
+);%0A%09%09%09%09%09%7Dcatch(Throwable e)%7B%0A%09%09%09%09%09%09if(executionResult != null)%0A%09%09%09%09%09%09%09executionResult.error(e);%0A%09%09%09%09%09%7D
 %0A%09%09%09

Make RedisThread throw the error to an ExecutionResult in case RedisErrorQueueThread wants to do something with it