﻿package org.smslib.smsserver;

import java.io.FileInputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Properties;
import java.util.StringTokenizer;
import org.smslib.InboundMessage;
import org.smslib.Library;
import org.smslib.OutboundMessage;
import org.smslib.Service;
import org.smslib.InboundMessage.MessageClasses;
import org.smslib.Message.MessageTypes;
import org.smslib.smsserver.gateways.AGateway;
import org.smslib.smsserver.interfaces.Interface;
import org.smslib.smsserver.interfaces.Interface.InterfaceTypes;


public class SMS extends javax.swing.JFrame {

 	Service srv;

	Properties props;

	boolean shutdown = false;

	List<Interface<? extends Object>> infList;

	InboundNotification inboundNotification;

	OutboundNotification outboundNotification;

	CallNotification callNotification;

	QueueSendingNotification queueSendingNotification;

	InboundPollingThread inboundPollingThread;

	OutboundPollingThread outboundPollingThread;

	boolean optRunOnce = false;

	public SMSServer()
	{
            initComponents();            
	}

	public Service getService()
	{
		return this.srv;
	}

	public List<Interface<? extends Object>> getInfList()
	{
		return this.infList;
	}

	public Properties getProperties()
	{
		return this.props;
	}

	class Shutdown extends Thread
	{
		@Override
		public void run()
		{
			System.out.println("SMSServer shutting down, please wait...");
			SMSServer.this.shutdown = true;
			try
			{
				stopInterfaces();
				getService().stopService();
				if (SMSServer.this.inboundPollingThread != null)
				{
					SMSServer.this.inboundPollingThread.interrupt();
					SMSServer.this.inboundPollingThread.join();
				}
				if (SMSServer.this.outboundPollingThread != null)
				{
					SMSServer.this.outboundPollingThread.interrupt();
					SMSServer.this.outboundPollingThread.join();
				}
			}
			catch (Exception e)
			{
				System.out.println("Shutdown hook error.");
				e.printStackTrace();
			}
		}
	}

	@SuppressWarnings("unchecked")
	private void loadConfiguration() throws Exception
	{
            try{
		FileInputStream f;
		this.props = new Properties();
		if (System.getProperty("smsserver.configdir") != null)
                {
                    f = new FileInputStream(System.getProperty("smsserver.configdir") + "SMSServer.conf");
                }

		else if (System.getProperty("smsserver.configfile") != null)
                {
                    f = new FileInputStream(System.getProperty("smsserver.configfile"));
                }
		else
                {
                    f = new FileInputStream("c:\\smslib\\src\\org\\smslib\\smsserver\\SMSServer.conf");
                }
                getProperties().load(f);
		f.close();
            }
            catch(Exception ex)
            {
                System.out.println(ex);
            }

		if (getProperties().getProperty("smsserver.balancer", "").length() > 0)
		{
			try
			{
				Object[] args = new Object[] { getService() };
				Class<?>[] argsClass = new Class[] { Service.class };
				Class<?> c = Class.forName((getProperties().getProperty("smsserver.balancer", "").indexOf('.') == -1 ? "org.smslib.balancing." : "") + getProperties().getProperty("smsserver.balancer", ""));
				Constructor<?> constructor = c.getConstructor(argsClass);
				org.smslib.balancing.LoadBalancer balancer = (org.smslib.balancing.LoadBalancer) constructor.newInstance(args);
				getService().setLoadBalancer(balancer);
				System.out.println("SMSServer: set balancer to: " + getProperties().getProperty("smsserver.balancer", ""));
			}
			catch (Exception e)
			{
				e.printStackTrace();
				System.out.println("SMSServer: error setting custom balancer!");
			}
		}
		if (getProperties().getProperty("smsserver.router", "").length() > 0)
		{
			try
			{
				Object[] args = new Object[] { getService() };
				Class<?>[] argsClass = new Class[] { Service.class };
				Class<?> c = Class.forName((getProperties().getProperty("smsserver.router", "").indexOf('.') == -1 ? "org.smslib.routing." : "") + getProperties().getProperty("smsserver.router", ""));
				Constructor<?> constructor = c.getConstructor(argsClass);
				org.smslib.routing.Router router = (org.smslib.routing.Router) constructor.newInstance(args);
				getService().setRouter(router);
				System.out.println("SMSServer: set router to: " + getProperties().getProperty("smsserver.router", ""));
			}
			catch (Exception e)
			{
				System.out.println("SMSServer: error setting custom balancer!");
			}
		}
		for (int i = 0; i < Integer.MAX_VALUE; i++)
		{
			try
			{
				String propName = "gateway." + i;
				String propValue = getProperties().getProperty(propName, "");
				if (propValue.length() == 0) break;
				StringTokenizer tokens = new StringTokenizer(propValue, ",");
				String gtwId = tokens.nextToken().trim();
				String gtwClass = tokens.nextToken().trim();
				Object[] args = new Object[] { gtwId, getProperties(), this };
				Class<?>[] argsClass = new Class[] { String.class, Properties.class, SMSServer.class };
				Class<?> c = Class.forName((gtwClass.indexOf('.') == -1 ? "org.smslib.smsserver.gateways." : "") + gtwClass);
				Constructor<?> constructor = c.getConstructor(argsClass);
				AGateway gtw = (AGateway) constructor.newInstance(args);
				gtw.create();
				getService().addGateway(gtw.getGateway());
				System.out.println("SMSServer: added gateway " + gtwId + " / " + gtw.getDescription());
			}
			catch (Exception e)
			{
				System.out.println("SMSServer: Unknown Gateway in configuration file!");
			}
		}
		for (int i = 0; i < Integer.MAX_VALUE; i++)
		{
			try
			{
				String propName = "interface." + i;
				String propValue = getProperties().getProperty(propName, "");
				if (propValue.length() == 0) break;
				StringTokenizer tokens = new StringTokenizer(propValue, ",");
				String infId = tokens.nextToken().trim();
				String infClass = tokens.nextToken().trim();
				InterfaceTypes infType = null;
				String sinfType = tokens.hasMoreTokens() ? tokens.nextToken() : "inoutbound";
				sinfType = sinfType.trim();
				if ("inbound".equalsIgnoreCase(sinfType))
				{
					infType = InterfaceTypes.INBOUND;
				}
				else if ("outbound".equalsIgnoreCase(sinfType))
				{
					infType = InterfaceTypes.OUTBOUND;
				}
				else
				{ // NULL or other crap
					infType = InterfaceTypes.INOUTBOUND;
				}
				Object[] args = new Object[] { infId, getProperties(), this, infType };
				Class<?>[] argsClass = new Class[] { String.class, Properties.class, SMSServer.class, InterfaceTypes.class };
				Class<?> c = Class.forName((infClass.indexOf('.') == -1 ? "org.smslib.smsserver.interfaces." : "") + infClass);
				Constructor<?> constructor = c.getConstructor(argsClass);
				Interface<? extends Object> inf = (Interface<? extends Object>) constructor.newInstance(args);
				getInfList().add(inf);
				System.out.println("SMSServer: added interface " + infId + " / " + inf.getDescription() + " / " + inf.getType());
			}
			/* Check for exceptions thrown by the constructor itself */
			catch (InvocationTargetException e)
			{
				System.out.println("SMSServer: Illegal Interface configuration: " + e.getCause().getMessage());
			}
			catch (Exception e)
			{
				System.out.println("SMSServer: Unknown Interface in configuration file!");
			}
		}
	}

	class InboundPollingThread extends Thread
	{
		@Override
		public void run()
		{
			try
			{
				while (!SMSServer.this.shutdown)
				{
					System.out.println("InboundPollingThread() run.");
					readMessages();
					if (SMSServer.this.optRunOnce) break;
					Thread.sleep(Integer.parseInt(getProperties().getProperty("settings.inbound_interval", "60")) * 1000);
				}
			}
			catch (InterruptedException e)
			{
				System.out.println("InboundPollingThread() interrupted.");
			}
			catch (Exception e)
			{
				System.out.println("Error in InboundPollingThread()");
			}
		}
	}

	class OutboundPollingThread extends Thread
	{
		@Override
		public void run()
		{
			try
			{
				while (!SMSServer.this.shutdown)
				{
					System.out.println("OutboundPollingThread() run.");
					sendMessages();
					if (SMS.this.optRunOnce) break;
					Thread.sleep(Integer.parseInt(getProperties().getProperty("settings.outbound_interval", "60")) * 1000);
				}
			}
			catch (InterruptedException e)
			{
				System.out.println("OutboundPollingThread() interrupted.");
			}
			catch (Exception e)
			{
				System.out.println("Error in OutboundPollingThread()");
			}
		}
	}

	private void process() throws Exception
	{
		this.inboundPollingThread = new InboundPollingThread();
		this.inboundPollingThread.setName("SMSServer - InboundPollingThread");
		this.inboundPollingThread.start();
		this.outboundPollingThread = new OutboundPollingThread();
		this.outboundPollingThread.setName("SMSServer - OutboundPollingThread");
		this.outboundPollingThread.start();
		while (!this.shutdown)
			Thread.sleep(1000);
	}

	void startInterfaces() throws Exception
	{
		for (Interface<? extends Object> inf : getInfList())
			inf.start();
	}

	void stopInterfaces() throws Exception
	{
		for (Interface<? extends Object> inf : getInfList())
			inf.stop();
	}

	private void run() throws Exception
	{
		loadConfiguration();
		try
		{
			startInterfaces();
			getService().startService();
			process();
		}
		catch (Exception e)
		{
			stopInterfaces();
			getService().stopService();
			if (this.inboundPollingThread != null)
			{
				this.inboundPollingThread.interrupt();
				this.inboundPollingThread.join();
			}
			if (this.outboundPollingThread != null)
			{
				this.outboundPollingThread.interrupt();
				this.outboundPollingThread.join();
			}
		}
	}

	void readMessages() throws Exception
	{
		List<InboundMessage> msgList = new ArrayList<InboundMessage>();
		getService().readMessages(msgList, MessageClasses.ALL);
		if (msgList.size() > 0)
		{
			for (Interface<? extends Object> inf : getInfList())
				if (inf.isInbound()) inf.MessagesReceived(msgList);
			if (getProperties().getProperty("settings.delete_after_processing", "no").equalsIgnoreCase("yes")) for (InboundMessage msg : msgList)
				getService().deleteMessage(msg);
		}
	}

	void sendMessages() throws Exception
	{
		boolean foundOutboundGateway = false;
		for (org.smslib.AGateway gtw : getService().getGateways())
			if (gtw.isOutbound())
			{
				foundOutboundGateway = true;
				break;
			}
		if (foundOutboundGateway)
		{
			List<OutboundMessage> msgList = new ArrayList<OutboundMessage>();
			for (Interface<? extends Object> inf : getInfList())
				if (inf.isOutbound()) msgList.addAll(inf.getMessagesToSend());
			if (getProperties().getProperty("settings.send_mode", "sync").equalsIgnoreCase(("sync")))
			{
				System.out.println("SMSServer: sending synchronously...");
				getService().sendMessages(msgList);
				for (Interface<? extends Object> inf : getInfList())
					if (inf.isOutbound()) inf.markMessages(msgList);
			}
			else
			{
				System.out.println("SMSServer: sending asynchronously...");
				getService().queueMessages(msgList);
			}
		}
	}

	class InboundNotification implements org.smslib.IInboundMessageNotification
	{
		public void process(String gtwId, MessageTypes msgType, InboundMessage msg)
		{
			List<InboundMessage> msgList = new ArrayList<InboundMessage>();
			msgList.add(msg);
			for (Interface<? extends Object> inf : getInfList())
				if (inf.isInbound())
				{
					try
					{
						inf.MessagesReceived(msgList);
					}
					catch (Exception e)
					{
						System.out.println("Error receiving message!");
					}
				}
			if (getProperties().getProperty("settings.delete_after_processing", "no").equalsIgnoreCase("yes"))
			{
				try
				{
					getService().deleteMessage(msg);
				}
				catch (Exception e)
				{
					System.out.println("Error deleting received message!");
				}
			}
			msgList.clear();
		}
	}

	class OutboundNotification implements org.smslib.IOutboundMessageNotification
	{
		public void process(String gtwId, org.smslib.OutboundMessage msg)
		{
			try
			{
				for (Interface<? extends Object> inf : getInfList())
					if (inf.isOutbound()) inf.markMessage(msg);
			}
			catch (Exception e)
			{
				System.out.println("IOutboundMessageNotification error.");
			}
		}
	}

	class CallNotification implements org.smslib.ICallNotification
	{
		public void process(String gtwId, String callerId)
		{
			try
			{
				for (Interface<? extends Object> inf : getInfList())
					inf.CallReceived(gtwId, callerId);
			}
			catch (Exception e)
			{
				System.out.println("ICallNotification error.");
			}
		}
	}

	class QueueSendingNotification implements org.smslib.IQueueSendingNotification
	{
		public void process(String gtwId, OutboundMessage msg)
		{
			System.out.println("**** >>>> Now Sending: " + msg.getRecipient()+ gtwId);
		}
	}

	public boolean checkPriorityTimeFrame(int priority)
	{
		String timeFrame;
		String from, to, current;
		Calendar cal = Calendar.getInstance();
		if (priority < 0) timeFrame = getProperties().getProperty("settings.timeframe.low", "0000-2359");
		else if (priority == 0) timeFrame = getProperties().getProperty("settings.timeframe.normal", "0000-2359");
		else if (priority >= 0) timeFrame = getProperties().getProperty("settings.timeframe.high", "0000-2359");
		else timeFrame = "0000-2359";
		from = timeFrame.substring(0, 4);
		to = timeFrame.substring(5, 9);
		cal.setTime(new java.util.Date());
		current = cal.get(Calendar.HOUR_OF_DAY) < 10 ? "0" + cal.get(Calendar.HOUR_OF_DAY) : "" + cal.get(Calendar.HOUR_OF_DAY);
		current += cal.get(Calendar.MINUTE) < 10 ? "0" + cal.get(Calendar.MINUTE) : "" + cal.get(Calendar.MINUTE);
		if ((Integer.parseInt(current) >= Integer.parseInt(from)) && (Integer.parseInt(current) < Integer.parseInt(to))) return true;
		return false;
	}

	void main1()
	{			
                System.out.println(Library.getLibraryDescription());
		System.out.println("\nAPI version: " + Library.getLibraryVersion());
		System.out.println("SMSServer version: " + Library.getLibraryVersion());

                this.srv = new Service();
		this.infList = new ArrayList<Interface<? extends Object>>();
		Runtime.getRuntime().addShutdownHook(new Shutdown());
		this.inboundNotification = new InboundNotification();
		this.outboundNotification = new OutboundNotification();
		this.callNotification = new CallNotification();
		this.queueSendingNotification = new QueueSendingNotification();
		this.inboundPollingThread = null;
		this.outboundPollingThread = null;
		getService().setInboundNotification(this.inboundNotification);
		getService().setOutboundNotification(this.outboundNotification);
		getService().setCallNotification(this.callNotification);
		getService().setQueueSendingNotification(this.queueSendingNotification);

//		SMSServer app = new SMSServer();
//		for (int i = 0; i < args.length; i++)
//		{
//			if (args[i].equalsIgnoreCase("-runonce")) app.optRunOnce = true;
//			else System.out.println("Invalid argument: " + args[i]);
//		}
		try
		{
                        optRunOnce=true;
			run();
			System.out.println("SMSServer exiting normally.");
		}
		catch (Exception e)
		{
			System.out.println("SMSServer Error: ");
			try
			{
				srv.stopService();
			}
			catch (Exception e1)
			{
				// Swallow this...
			}
		}
        }

    
    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">                          
    private void initComponents() {

        jButton1 = new javax.swing.JButton();

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
        setName("Form"); // NOI18N

        org.jdesktop.application.ResourceMap resourceMap = org.jdesktop.application.Application.getInstance(gsmtester.GSMTesterApp.class).getContext().getResourceMap(SMSServer.class);
        jButton1.setText(resourceMap.getString("jButton1.text")); // NOI18N
        jButton1.setName("jButton1"); // NOI18N
        jButton1.addMouseListener(new java.awt.event.MouseAdapter() {
            public void mouseClicked(java.awt.event.MouseEvent evt) {
                jButton1MouseClicked(evt);
            }
        });

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                .addContainerGap(240, Short.MAX_VALUE)
                .addComponent(jButton1, javax.swing.GroupLayout.PREFERRED_SIZE, 108, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addGap(52, 52, 52))
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addGap(82, 82, 82)
                .addComponent(jButton1, javax.swing.GroupLayout.PREFERRED_SIZE, 60, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addContainerGap(158, Short.MAX_VALUE))
        );

        pack();
    }// </editor-fold>                        

    private void jButton1MouseClicked(java.awt.event.MouseEvent evt) {                                      
        // TODO add your handling code here:
        main1();
    }                                     

    /**
    * @param args the command line arguments
    */
    public static void main(String args[]) {
        java.awt.EventQueue.invokeLater(new Runnable() {
            public void run() {
                new SMSServer().setVisible(true);
            }
        });
    }

    // Variables declaration - do not modify                     
    private javax.swing.JButton jButton1;
    // End of variables declaration                   

}

