package de.hsadmin.core.qserv;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.util.Properties;

import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.ExceptionListener;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.MessageProducer;
import javax.jms.ObjectMessage;
import javax.jms.Queue;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import javax.jms.QueueReceiver;
import javax.jms.QueueSession;
import javax.jms.Session;
import javax.naming.Context;
import javax.naming.InitialContext;

public class QueueServer extends QueueCommons implements
		MessageListener, ExceptionListener {
	
	private static final String VERSION_NO = "1.0";
	
	private int nMessagesProcessed = 0;
	private QueueConnection conn;
	private QueueSession queueSession;
	private String jmsStatusQueue;
	private String jmsPassWord;
	private String jmsUserName;
	private String jmsSystemQueue;
	private String jmsFactory;

	/** Runs the QueueServer, using the arguments as ConnectionFactory 
	 *  and Topic names. 
	 */
	public static void main(String[] args) throws Exception {
		File propFile = new File(System.getProperty("user.dir"),
				"conf/qserv.properties");
		if (args.length == 1)
			propFile = new File(args[0]);
		else if (args.length != 0) {
			throw new Exception(
					"Wrong number of arguments.\n"
							+ "With no arguments '"
							+ propFile
							+ "' will be used as config file.\n"
							+ "Or give a properties file as single argument.\n\n"
							+ "Example config file:\n\n"
							+ "hsadmin.jms.factory=QueueCF\n"
							+ "hsadmin.jms.system-queue=hive-h01\n"
							+ "hsadmin.jms.status-queue=queue/hsadminStatus\n"
							+ "hsadmin.jms.username=hive-h01\n"
							+ "hsadmin.jms.password=geheimeskennwort\n");
		}

		FileInputStream propStream = new FileInputStream(propFile);
		Properties props = new Properties(System.getProperties());
		props.load(propStream);
		propStream.close();
		String stdout = System.getProperty("hsadmin.stdout");
		if (stdout != null && stdout.length() > 0)
			System.setOut(new PrintStream(new FileOutputStream(stdout)));
		String stderr = System.getProperty("hsadmin.stderr");
		if (stderr != null && stderr.length() > 0)
			System.setErr(new PrintStream(new FileOutputStream(stderr)));

		final QueueServer qServ = new QueueServer();
		qServ.setJmsFactory(props.getProperty("hsadmin.jms.factory"));
		qServ.setJmsSystemQueue(props.getProperty("hsadmin.jms.system-queue"));
		qServ.setJmsStatusQueue(props.getProperty("hsadmin.jms.status-queue"));
		qServ.setJmsUserName(props.getProperty("hsadmin.jms.username"));
		qServ.setJmsPassWord(props.getProperty("hsadmin.jms.password"));
		System.out.println("==================================================");
		System.out.println("hsadmin-qserv " + VERSION_NO +
				" started using:"); 
		System.out.println("queue server: "
				+ props.getProperty("hsadmin.jms.factory"));  
		System.out.println("system queue: "
				+ props.getProperty("hsadmin.jms.system-queue"));
		System.out.println("status queue: "
				+ props.getProperty("hsadmin.jms.status-queue"));
		System.out.println("queue user:   "
				+ props.getProperty("hsadmin.jms.username"));
		System.out.println("==================================================");
		Runtime.getRuntime().addShutdownHook(new Thread() {
			@Override
			public void run() {
				qServ.close();
			}
		});
		while (!qServ.connect()) {
			Thread.sleep(10000);
		}
		while (true) {
			Thread.sleep(10000);
		}
	}

	private boolean connect() {
		// create JMS connection and session
		try {
			Context ctx = new InitialContext();
			QueueConnectionFactory connectionFactory = 
				(QueueConnectionFactory) ctx.lookup(jmsFactory);
			conn = connectionFactory.createQueueConnection(jmsUserName, jmsPassWord);
			conn.setExceptionListener(this);
			queueSession = conn.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
			Queue queue = (Queue) ctx.lookup(jmsSystemQueue);
			conn.start();
			QueueReceiver receiver = queueSession.createReceiver(queue);
			receiver.setMessageListener(this);
			return true;
		} catch (Exception e) {
			System.out.println(e.getMessage());
			return false;
		}
	}

	// / Is called when a message arrives; processes Processor within message.
	public synchronized void onMessage(Message jmsMessage) {
		// TODO logging
		System.out.println("\nonMessage(" + jmsMessage);
		QueueTask task = null;
		try {
			// expect ObjectMessage
			ObjectMessage jmsObjectMessage = (ObjectMessage) jmsMessage;

			// get QueueTask
			task = (QueueTask) jmsObjectMessage.getObject();

			// TODO: logging
			System.out.println("processing (" + task.getTitle() + " | started("
					+ task.getStarted() + ") |" + task.getDetails() + "|"
					+ task.getProcessor() + ")");
			System.out.println("with " + task.getProcessor());
			// execute processor within the message
			task.getProcessor().process();
			System.out.println("done");

			// mark done
			sendStatus(task);
		} catch (Exception receiveException) {
			System.err.println("exception " + receiveException); // TODO:
																	// logging
			receiveException.printStackTrace(System.err);
			if (receiveException.getCause() != null) {
				System.err.println("caused by exception "
						+ receiveException.getCause()); // TODO: logging
				receiveException.getCause().printStackTrace(System.err);
			} else
				System.err.println("no further cause available");
			task.setException(receiveException);
			sendStatus(task);
		} catch (Throwable t) {
			System.err.println("severe exception " + t); // TODO: logging
		} finally {
			setNMessagesProcessed(getNMessagesProcessed() + 1);
			notifyAll();
		}
	}

	@Override
	public void onException(JMSException e) {
		System.out.println("Exception: " + e.getMessage());
		close();
		while (!connect()) {
			try {
				Thread.sleep(10000);
			} catch (InterruptedException e1) { }
		}
	}

	public void close() {
		if (queueSession != null) {
			try {
				queueSession.close();
			} catch (JMSException e1) { }
		}
		if (conn != null) {
			try {
				conn.close();
			} catch (JMSException e1) { }
		}
	}

	// / send the status information back to the hsadmin server
	protected void sendStatus(QueueTask queueMessage) {
		MessageProducer producer = null;
		Session statusSession = null;
		Connection statusConnection = null;
		try {
			System.out.println("sendStatus( " + queueMessage + ")");
			Context ctx = new InitialContext();
			System.out.println("Created InitialContext");
			ConnectionFactory connectionFactory = (ConnectionFactory) ctx.lookup(jmsFactory);
			System.out.println("connectionFactory= "
					+ connectionFactory.toString());
			Destination queue = (Destination) ctx.lookup(jmsStatusQueue);
			statusConnection = connectionFactory.createConnection(jmsUserName, jmsPassWord);
			statusSession = statusConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
			producer = statusSession.createProducer(queue);
			ObjectMessage statusMessage = statusSession.createObjectMessage(queueMessage);
			System.out.println("send( " + statusMessage + ")");
			producer.send(statusMessage);
		} catch (Exception statusException) {
			// TODO: log exception
			System.out.println("failed");
			statusException.printStackTrace(System.err);
		} finally {
			// close JMS
			try { producer.close(); } catch (Exception exc) { }
			try { statusSession.close(); } catch (Exception exc) { }
			try { statusConnection.close(); } catch (Exception exc) { }
		}
	
	}

	public void setNMessagesProcessed(int nMessagesProcessed) {
		this.nMessagesProcessed = nMessagesProcessed;
	}

	public int getNMessagesProcessed() {
		return nMessagesProcessed;
	}

	public void setJmsStatusQueue(String property) {
		jmsStatusQueue = property;
	}

	public void setJmsPassWord(String property) {
		jmsPassWord = property;
	}

	public void setJmsUserName(String property) {
		jmsUserName = property;
	}

	public void setJmsSystemQueue(String property) {
		jmsSystemQueue = property;
	}

	public void setJmsFactory(String property) {
		jmsFactory = property;
	}

}
