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

import gnu.io.CommPort;
import gnu.io.CommPortIdentifier;
import gnu.io.SerialPort;
import gnu.io.SerialPortEvent;
import gnu.io.SerialPortEventListener;
import gov.nist.javax.sip.address.SipUri;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.sql.SQLException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

import javax.sip.address.SipURI;

import org.apache.log4j.Logger;

import jp.ac.naka.ec.EventSource;
import jp.ac.naka.ec.Main;
import jp.ac.naka.ec.db.DataObject;
import jp.ac.naka.ec.db.DataObjectImpl;
import jp.ac.naka.ec.db.DatabaseConnector;
import jp.ac.naka.ec.entity.Entity;
import jp.ac.naka.ec.entity.EntityContainer;
import jp.ac.naka.ec.entity.EntityImpl;
import jp.ac.naka.ec.entity.EntityType;
import jp.ac.naka.ec.http.HttpCore;

/**
 * シリアル通信のためのクラス。
 * シリアル入力からパーサによって解析、取得されるSensorDataオブジェクトをSerialListenerに対して配布する。
 * 
 * @author 粕谷
 * 
 */
public class SerialManager extends EventSource implements
		SerialPortEventListener {

	private static DatabaseConnector con;
	private static Logger logger = Logger.getLogger(SerialManager.class);

	private Entity source;
	List<SerialListener> listeners = new ArrayList<SerialListener>();
	private SerialInputParser parser;
	private String commPort = "COM1";
	private int baud_rate = 9600;
	private final int timeout = 20000;

	// Thread th;

	//private OutputStream out;
	BufferedReader comReader;

	public SerialManager() {

	}

	@Override
	public void init(EntityContainer source, Properties prop)
			throws Exception {
		this.source = source;
		if (Main.con != null) 
			SerialManager.con = Main.con;
		// COM port setting
		String port = prop.getProperty("port");
		if (port != null && !port.equals("")) {
			commPort = port;
		}
		// baud rate setting
		String baud = prop.getProperty("baudRate");
		if (baud != null && !baud.equals("")) {
			baud_rate = Integer.valueOf(baud);
		}
		// SerialInputParser setting
		String a = prop.getProperty("SerialInputParser");
		if (a != null && !a.equals("")) {
			// TODO
			try {
				Class clz = Class.forName(a);
				parser = (SerialInputParser) clz
						.newInstance();
				
			} catch (Exception e) {
				logger.warn("Invalid SerialInputParser Class :" + a, e);
			}
		} 
		if (parser == null)
			parser = new TemperatureParser();
		
		initialize();
	}

	
	public void initialize() throws IOException {
		try {
			CommPortIdentifier portIdentifier = CommPortIdentifier
					.getPortIdentifier(commPort);
			if (portIdentifier.isCurrentlyOwned()) {
				logger.error("Error: Port is currently in use");
			} else {
				// 名前とタイムアウトを指定
				CommPort commPort = portIdentifier.open(getClass().getName(),
						timeout);

				if (commPort instanceof SerialPort) {
					SerialPort serialPort = (SerialPort) commPort;
					serialPort.setSerialPortParams(baud_rate,
							SerialPort.DATABITS_8, SerialPort.STOPBITS_1,
							SerialPort.PARITY_NONE);
					serialPort.addEventListener(this);
					serialPort.notifyOnDataAvailable(true);
					InputStream in = serialPort.getInputStream();
					comReader = new BufferedReader(new InputStreamReader(in));

				} else {
					logger.error("Error: Only serial ports are handled by this example.");
				}
			}
		} catch (Exception e) {
			throw new IOException(e.getMessage());
		}
	}

	/**
	 * SerialListenerの追加
	 * 
	 * @param listener
	 */
	public void addSerialListener(SerialListener listener) {
		listeners.add(listener);
	}

	/**
	 * SerialListenerの削除
	 * 
	 * @param listener
	 */
	public void removeSerialListener(SerialListener listener) {
		for (SerialListener l : listeners) {
			if (l == listener) {
				listeners.remove(l);
				break;
			}
		}
	}

	/*
	private void sendData(String str) throws IOException {
		int c = 0;
		this.out.write(c);
	}*/

	void dispatchEvent(SerialEvent evt) {
		// 保存
		if (con != null) {
			try {
				// センサ情報の保存
				// DataObject obj = new DataObjectImpl (EntityType.SENSOR,
				// evt.getSensorData(), source, null, null);
				int num = con.store(EntityType.SENSOR, evt.getSensorData(),
						source);
				evt.getSensorData().setId(num);
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
		for (SerialListener listener : listeners) {
			listener.serialPerformed(evt);
		}
	}

	public void serialEvent(SerialPortEvent event) {
		switch (event.getEventType()) {
		case SerialPortEvent.BI:
		case SerialPortEvent.OE:
		case SerialPortEvent.FE:
		case SerialPortEvent.PE:
		case SerialPortEvent.CD:
		case SerialPortEvent.CTS:
		case SerialPortEvent.DSR:
		case SerialPortEvent.RI:
		case SerialPortEvent.OUTPUT_BUFFER_EMPTY:
			// Data Available 以外のイベントは処理しない
			break;
		case SerialPortEvent.DATA_AVAILABLE:
			// Data Available の処理
			String buffer = null;
			SensorData data = null;
			try {
				while (comReader.ready()) {
					// データの読み込み
					buffer = comReader.readLine();
					// System.out.println("> " + buffer);
					data = parser.parse(buffer);
					if (data != null)
						dispatchEvent(new SerialEvent(this, data));
				}
			} catch (IOException e) {
			}
			break;
		}
	}

	public static void main(String[] args) {
		try {
			SerialManager sc = new SerialManager();
			sc.initialize();
			SerialManager.con = DatabaseConnector.getInstance();
			SerialListener l1 = new SerialListener() {
				public void serialPerformed(SerialEvent e) {
					System.out.println(e.getSensorData().toString());
				}
			};
			SerialListener l2 = new SerialListener() {
				public void serialPerformed(SerialEvent e) {
					System.out.println(e.getSensorData().toString());
				}
			};
			sc.addSerialListener(l1);
			sc.addSerialListener(l2);
		} catch (Exception e) {
			e.printStackTrace();
		}

	}

	/**
	 * シリアルへの文字列の出力。未実装。
	 * 
	 * @param str
	 */
	public void input(String str) {
		// TODO Auto-generated method stub

	}

}
