package de.hsadmin.core.qserv;

import java.io.File;
import java.io.FileInputStream;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;

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.1.1";
	
	private Logger logger;
	private QueueConnection conn;
	private QueueSession queueSession;
	private String jmsStatusQueue;
	private String jmsPassWord;
	private String jmsUserName;
	private String jmsSystemQueue;
	private String jmsFactory;
	private String serviceEMail;
	private String fromEMail;

	/** 
	 * 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(userHelp(propFile));
			}
		}
		FileInputStream propStream = new FileInputStream(propFile);
		Properties props = new Properties(System.getProperties());
		props.load(propStream);
		propStream.close();
		System.setProperty("java.util.logging.config.file", props.getProperty("hsadmin.log.config", System.getProperty("user.home") + "/.hsadmin.log.properties"));
		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"));
		qServ.setServiceEMail(props.getProperty("hsadmin.log.email"));
		qServ.setFromEMail(props.getProperty("hsadmin.log.from"));
		Logger logger = Logger.getLogger("de.hsadmin.core.qserv");
		logger.log(Level.CONFIG, "hsadmin-qserv " + VERSION_NO + " started using:" 
		 + "\nqueue server: " + props.getProperty("hsadmin.jms.factory")
		 + "\nsystem queue: " + props.getProperty("hsadmin.jms.system-queue")
		 + "\nstatus queue: " + props.getProperty("hsadmin.jms.status-queue")
		 + "\nqueue user:   " + props.getProperty("hsadmin.jms.username"));
		Runtime.getRuntime().addShutdownHook(new Thread() {
			@Override
			public void run() {
				qServ.close();
			}
		});
		while (!qServ.connect()) {
			Thread.sleep(10000);
		}
		while (true) {
			Thread.sleep(10000);
		}
	}

	private static String userHelp(File propFile) {
		return "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";
	}

	public QueueServer() {
		logger = Logger.getLogger("de.hsadmin.core.qserv");
	}
	
	private void setServiceEMail(String emailAddress) {
		if (emailAddress == null || emailAddress.isEmpty()) {
			serviceEMail = "peter@ostwall195.de";
		} else {
			serviceEMail = emailAddress;
		}
	}

	private void setFromEMail(String emailAddress) {
		if (emailAddress == null || emailAddress.isEmpty()) {
			fromEMail = "hsadmin@hostsharing.net";
		} else {
			fromEMail = emailAddress;
		}
	}

	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) {
			logger.log(Level.WARNING, e.getMessage(), e);
			return false;
		}
	}
	
	public synchronized void onMessage(Message jmsMessage) {
		logger.log(Level.INFO, jmsMessage.toString());
		QueueTask task = null;
		try {
			ObjectMessage jmsObjectMessage = (ObjectMessage) jmsMessage;
			task = (QueueTask) jmsObjectMessage.getObject();
			Processor processor = task.getProcessor();
			logger.log(Level.INFO, "processing (" + task.getTitle() + " | started("
					+ task.getStarted() + ") |" + task.getDetails() + "|"
					+ processor + ")");
			try {
				processor.process();
				logger.log(Level.INFO, "done");
			} catch (ProcessorException e) {
				logException(e);
				task.setException(e);
				SmtpHelper.send(fromEMail, serviceEMail, jmsSystemQueue, processor.logInfo());
			}
		} catch (Throwable throwable) {
			logException(throwable);
			task.setException(throwable);
		} finally {
			sendStatus(task);
			notifyAll();
		}
	}

	private void logException(Throwable t) {
		logger.log(Level.SEVERE, t.getMessage(), t);
		Throwable cause = t.getCause();
		if (cause != null) {
			logger.log(Level.SEVERE, "cause: " + t.getMessage(), t);
		} 
	}

	@Override
	public void onException(JMSException e) {
		logger.log(Level.WARNING, e.getMessage(), e);
		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) { }
		}
	}

	protected void sendStatus(QueueTask queueMessage) {
		MessageProducer producer = null;
		Session statusSession = null;
		Connection statusConnection = null;
		try {
			logger.log(Level.INFO, "sendStatus(" + queueMessage + ")");
			Context ctx = new InitialContext();
			ConnectionFactory connectionFactory = (ConnectionFactory) ctx.lookup(jmsFactory);
			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);
			logger.log(Level.INFO, "send(" + statusMessage + ")");
			producer.send(statusMessage);
		} catch (Exception statusException) {
			logger.log(Level.SEVERE, statusException.getMessage(), statusException);
		} finally {
			// close JMS
			try { producer.close(); } catch (Exception exc) { }
			try { statusSession.close(); } catch (Exception exc) { }
			try { statusConnection.close(); } catch (Exception exc) { }
		}
	
	}

	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;
	}

}
