/*
 *  Copyright (C) 2006  Takashi Kasuya <kasuya@sfc.keio.ac.jp>
 *
 * 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 jp.ac.naka.ec;

import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

import javax.sip.address.SipURI;

import jp.ac.naka.ec.db.DatabaseConnector;
import jp.ac.naka.ec.db.TupleSpaceAdapter;
import jp.ac.naka.ec.entity.Entity;
import jp.ac.naka.ec.entity.EntityConfigParser;
import jp.ac.naka.ec.entity.EntityContainer;
import jp.ac.naka.ec.entity.EntityContainerImpl;
import jp.ac.naka.ec.entity.EntityEventDispatcher;
import jp.ac.naka.ec.entity.EntityListener;
import jp.ac.naka.ec.media.MediaTransmitter;
import jp.ac.naka.ec.sip.SipCore;

import org.apache.log4j.PropertyConfigurator;

/**
 * @author kasuya
 * 
 */
public class Main {

	private final static String config_file = "./config/container.properties";
	private final static String sip_config = "./config/sip.properties";
	public final static String entity_config = "./config/entities.xml";
	private final static String jmf_config_file = "./config/jmf.properties";
	public static String log4j_properties = "./config/log4j.properties";

	public static org.apache.log4j.Logger logger = org.apache.log4j.Logger
			.getLogger(Main.class);

	private boolean use_db = false;
	public static DatabaseConnector con;
	private int manager_port = 8080;
	private int sip_port = 5060;
	private String parent_uri = "";
	private Location location;

	public Main()throws Exception{
		init();
		Properties prop = new Properties();
		procProperty(prop);

		// TODO
		//ECManager.getInstance().setPort(manager_port);

		// Container
		String hostname = getHostName();
		String ipAddr = getIpAddress(hostname);

		EntityContainer parent = null;
		TupleSpaceAdapter dispatcher = TupleSpaceAdapter.getInstance();

		EntityContainer container = new EntityContainerImpl();
		container.setName(hostname);
		container.setLocal(true);

		if (location != null) {
			container.setLocation(location);
		}
		dispatcher.addEntityListener(container);

		SipCore core = SipCore.getInstance();
		core.initialize(hostname, ipAddr, sip_port, sip_config);
		SipURI uri = core.createURI("sip:" + hostname + "@" + ipAddr + ":"
				+ sip_port);
		container.setURI(uri);

		core.setEntityContainer(container);
		// Entity
		Entity[] entities = EntityConfigParser.parse(entity_config);
		for (Entity e : entities) {
			SipURI e_uri = core.createURI("sip:" + e.getName() + "@" + ipAddr
					+ ":" + sip_port);
			e.setParent(container);
			e.setURI(e_uri);
			// 初期位置はコンテナの位置
			if (location != null) {
				e.setLocation(location);
			}
			container.addEntity(e);
		}

		if (use_db) {
			con = DatabaseConnector.getInstance();
			if (!con.hasTables()) {
				con.createNewTable();
			}
			dispatcher.setDatabaseConnector(con);
		}

		if (parent_uri != null && !parent_uri.equals("")) {
			SipURI p_uri = core.createURI(parent_uri);
			parent = new EntityContainerImpl();
			parent.setLocal(false);
			parent.setURI(p_uri);
			container.setParent(parent);
		}

		// invoke plugins
		List<EntityListener> plugins = invokePlugin(container);

	}
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		try {
			new Main();
		} catch (Exception e) {
			e.printStackTrace();
			System.exit(-1);
		}
	}

	private static List<EntityListener> invokePlugin(EntityContainer container) {
		PluginClassLoader loader = PluginClassLoader.getClassLoader();
		List<EntityListener> loadedPlugins = new ArrayList<EntityListener>();
		// plugin以下のフォルダの読み込み
		File folder = new File("plugin");
		if (!folder.exists() || !folder.isDirectory()) {
			folder.mkdir();
		}
		File[] plugins = folder.listFiles(new FileFilter() {
			public boolean accept(File pathname) {
				// CVSは除く
				if (pathname.getName().equals("CVS"))
					return false;
				return pathname.isDirectory();
			}
		});
		EventDispatcher dispatcher = EntityEventDispatcher.getInstance();
		for (File plugin : plugins) {
			File[] files = plugin.listFiles(new FilenameFilter() {
				public boolean accept(File path, String name) {
					if (name.equals("plugin.properties"))
						return true;
					return false;
				}
			});
			if (files.length == 0) {
				logger.warn("In " + plugin.getName()
						+ ", There is no plugin.properties file.");
				break;
			}
			File propfile = files[0];
			Properties prop = new Properties();
			FileInputStream fis = null;
			try {
				fis = new FileInputStream(propfile);
				prop.load(fis);
				String enable = prop.getProperty("enable");
				// プラグインが有効か？
				if (enable == null || !enable.equals("YES")) {
					continue;
				}
				// jarfileがあった場合はクラスパスに追加
				String jar = prop.getProperty("jarfile");
				if (jar != null && !jar.equals("")) {
					String[] jarfiles = jar.split(",");
					for (String jarfile : jarfiles) {
						jarfile = plugin.getAbsolutePath() + "/" + jarfile;
						PluginClassLoader.addJarFile(jarfile);
					}
				}
				String classname = prop.getProperty("className");
				String pluginname = prop.getProperty("pluginName");
				// String providername = prop.getProperty("providerName");
				//Class clazz = Class.forName(classname);
				Class clazz = loader.loadClass(classname);
				EntityListener instance = (EntityListener) clazz.newInstance();
				if (instance instanceof EventSource) {
					EventSource source = (EventSource) instance;
					source.init(container, prop);
				}
				dispatcher.addEntityListener(instance);
				
				loadedPlugins.add(instance);
				logger.info("Plugin loaded successfully :" + pluginname);
			} catch (Exception e) {
				logger.error("Error while processing plugin :"
						+ plugin.getName(), e);
			} finally {
				if (fis != null) {
					try {
						fis.close();
					} catch (IOException e) {
					}
				}
			}
		}

		return loadedPlugins;
	}

	private void init() throws IOException {
		File config = new File(config_file);
		File entities = new File(entity_config);
		if (!config.exists()) {
			// Config
			if (config.createNewFile())
				logger.info("Create: container.properties");
		}
		if (!entities.exists()) {
			// entities.xml
			if (entities.createNewFile())
				logger.info("Create: entities.xml");
		}
	}

	/**
	 * 
	 * @param prop
	 */
	private void procProperty(Properties prop) throws IOException {
		FileInputStream fis = null;
		try {
			fis = new FileInputStream(config_file);
			prop.load(fis);
			fis.close();
		} catch (IOException e) {
			throw e;
		} finally {
			if (fis != null)
				fis.close();
		}

		// SIP settings
		String port = prop.getProperty("port");
		if (!port.equals("")) {
			sip_port = Integer.valueOf(port);
		} else {
			sip_port = 5060;
		}
		parent_uri = prop.getProperty("parent_uri");
		
		String transport = prop.getProperty("transport");
		if (transport != null && !transport.equals(""))
			SipCore.TRANSPORT = transport;
		
		// TODO Logger
		PropertyConfigurator.configure(log4j_properties);

		// Database settings
		use_db = initDatabase(prop);

		/*
		 * port = prop.getProperty("manager_port"); if (!port.equals("")) { port =
		 * manager_port + ""; logger.info("Manager tool port: " + port); } else {
		 * prop.setProperty("manager_port", value); }
		 * prop.setProperty("manager_port", port);
		 */

		String a = prop.getProperty("location");
		if (a != null && !a.equals("")) {
			String lat, lon;
			lat = prop.getProperty("latitude");
			lon = prop.getProperty("longitude");
			location = new Location(a, lat, lon);
		}

		// JMF settings
		try {
			fis = new FileInputStream(jmf_config_file);
			Properties jmf_setting = new Properties();
			jmf_setting.load(fis);
			String temp = jmf_setting.getProperty("video_device_name");
			if (temp != null || !temp.equals(""))
				MediaTransmitter.defaultVideoDeviceName = temp;
			temp = jmf_setting.getProperty("audio_device_name");
			if (temp != null || !temp.equals(""))
				MediaTransmitter.defaultAudioDeviceName = temp;
			temp = jmf_setting.getProperty("video_capture_format");
			if (temp != null || !temp.equals(""))
				MediaTransmitter.defaultVideoFormatString = temp;
			temp = jmf_setting.getProperty("audio_capture_format");
			if (temp != null || !temp.equals(""))
				MediaTransmitter.defaultAudioFormatString = temp;

		} catch (IOException e) {
			// Default
			;
		} finally {
			if (fis != null)
				fis.close();
		}

		/*
		 * try { fos = new FileOutputStream(config_file); prop.store(fos, "Auto
		 * Generated by E.C."); } catch (IOException e) { throw e; } finally {
		 * if (fos != null) { fos.close(); } }
		 */

	}

	private static String getIpAddress(String name) throws IOException {
		String address = "";

		java.util.Enumeration enuIfs = NetworkInterface.getNetworkInterfaces();
		if (null != enuIfs) {
			while (enuIfs.hasMoreElements()) {
				NetworkInterface ni = (NetworkInterface) enuIfs.nextElement();
				java.util.Enumeration enuAddrs = ni.getInetAddresses();
				boolean flag = false;
				while (enuAddrs.hasMoreElements()) {
					InetAddress in4 = (InetAddress) enuAddrs.nextElement();
					// localhost
					if (ni.getName().equals("lo")) {
						continue;
					}
					if (in4 instanceof Inet4Address) {
						address = in4.getHostAddress();
						flag = true;

						break;
					}
					if (flag)
						break;
				}
				if (flag)
					break;
			}
		}
		// System.out.println("Use Interface :" + name + ", " + address);
		return address;
	}

	/**
	 * jdbc_driver_class, jdbc_address, db_catalog_name, db_user, db_password
	 * 
	 * @param prop
	 * @return
	 */
	private static boolean initDatabase(Properties prop) {
		String driver_class, jdbc_address, catalog_name, user, pwd;
		driver_class = prop.getProperty("jdbc_driver_class");
		jdbc_address = prop.getProperty("jdbc_address");
		catalog_name = prop.getProperty("db_catalog_name");
		user = prop.getProperty("db_user");
		pwd = prop.getProperty("db_password");
		if (driver_class == null || driver_class.equals("")
				|| jdbc_address == null || jdbc_address.equals("")) {
			return false;
		} else {
			DatabaseConnector.driver_name = driver_class;
			DatabaseConnector.db_location = jdbc_address;
			DatabaseConnector.catalog = catalog_name;
			DatabaseConnector.user = user;
			DatabaseConnector.pass = pwd;
		}
		String a = prop.getProperty("use_database");
		if (a.equals("YES") || a.equals("true"))
			return true;
		else
			prop.setProperty("use_database", "NO");
		return false;
	}

	private static String getHostName() throws IOException {
		String name = "";
		java.util.Enumeration enuIfs = NetworkInterface.getNetworkInterfaces();
		if (null != enuIfs) {
			while (enuIfs.hasMoreElements()) {
				NetworkInterface ni = (NetworkInterface) enuIfs.nextElement();
				if (ni.getName().equals("lo")) {
					continue;
				}
				java.util.Enumeration enuAddrs = ni.getInetAddresses();
				while (enuAddrs.hasMoreElements()) {
					InetAddress in4 = (InetAddress) enuAddrs.nextElement();
					if (in4 instanceof Inet4Address) {
						name = in4.getHostName();
						break;
					}
				}
			}
		}
		return name;
	}

}
