/*
 *  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.URI;

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.manager.ECManager;
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 static boolean use_db = false;
	public static DatabaseConnector con;
	private static int manager_port = 8080;
	private static int sip_port = 5060;

	private static String parent_uri = "";
	private static Location location ;

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		try {
			init();
			Properties prop = new Properties();
			procProperty(prop);

			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();
			SipCore.initialize(hostname, ipAddr, sip_port,
					sip_config);
			URI 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) {
				URI 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("")) {
				URI 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);
			
		} catch (Exception e) {
			e.printStackTrace();
			System.exit(-1);
		}
	}


	private static List<EntityListener> invokePlugin(EntityContainer container) {
		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);
				fis.close();
			
			String classname = prop.getProperty("className");
			String pluginname = prop.getProperty("pluginName");
//			String providername = prop.getProperty("providerName");
			Class clazz = Class.forName(classname);
			EntityListener instance = (EntityListener)clazz.newInstance();
			if (instance instanceof EventSource) {
				EventSource source = (EventSource)instance;
				source.init(container, prop);
			}
			dispatcher.addEntityListener(instance);
			logger.info("Plugin loaded successfully :" + pluginname);
			loadedPlugins.add(instance);
			} catch (Exception e) {
				logger.error("Error while processing plugin :" + plugin.getName(), e);
				break;
			} finally {
				if (fis != null)
					try {
						fis.close();
					} catch (IOException e) {
					}
			}
		}
		
		return loadedPlugins;
	}


	private static 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 static 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");

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

}
