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

import gov.nist.javax.sip.address.SipUri;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.text.ParseException;
import java.util.List;

import javax.sip.address.SipURI;
import javax.sip.address.URI;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import jp.ac.naka.ec.Location;
import jp.ac.naka.ec.entity.Entity;
import jp.ac.naka.ec.entity.EntityContainer;
import jp.ac.naka.ec.entity.EntityContainerImpl;
import jp.ac.naka.ec.entity.EntityImpl;
import jp.ac.naka.ec.entity.EntityType;
import jp.ac.naka.ec.sip.SipCore;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

/**
 * 
 * @author Takashi Kasuya
 *
 */
public class PIDFParserImpl implements ErrorHandler, PIDFParser {

	private static DocumentBuilder db;
	private static PIDFParserImpl instance = new PIDFParserImpl();

	/**
	 * 
	 *
	 */
	private PIDFParserImpl() {

	}

	/**
	 * 
	 * @return
	 * @throws ParserConfigurationException
	 * @throws SAXException
	 */
	public static PIDFParser getInatance() throws ParserConfigurationException,
			SAXException {
		DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
		// 検証を行う
		// dbf.setValidating(true);
		// ドキュメントビルダーを生成
		db = dbf.newDocumentBuilder();
		db.setErrorHandler(instance);
		return instance;
	}

	/*
	 * (non-Javadoc)
	 * @see jp.ac.naka.ec.sip.pidf.PIDFParser#parse(byte[])
	 */
	public PIDFData parse(byte[] content) throws SAXException, ParseException,
			IOException {
		Document doc = db.parse(new ByteArrayInputStream(content), "UTF-8");
		Element root = doc.getDocumentElement();
		NamedNodeMap oAttrs = root.getAttributes();
		String entity = oAttrs.getNamedItem("entity").getNodeValue();
		if (entity == null || entity.equals("null")) {
			throw new NullPointerException("Entity attribute is null.");
		}

		SipURI uri = (SipURI) SipCore.getInstance().createURI(entity);
		PIDFData data = new PIDFData(uri);
		// tupleタグの処理
		NodeList list = root.getElementsByTagName("tuple");
		procTuple(list, data);

		// noteタグの処理
		Node node = root.getLastChild();

		while (node.getNodeType() == Node.ELEMENT_NODE
				&& node.getNodeName().equals("note")) {
			data.addNote(getNote(node));
			node = node.getPreviousSibling();
		}

		// 他Extensionsの処理
		// locationタグ
		NodeList nodes = root.getElementsByTagName("location");
		node = nodes.item(0);
		if (node != null) {
			Node t =  node.getFirstChild();
			String name = t.getFirstChild().getNodeValue();
			t = t.getNextSibling();
			String lat = t.getFirstChild().getNodeValue();
			t = t.getNextSibling();
			String lon = t.getFirstChild().getNodeValue();
			Location location = new Location(name, lat, lon);
			data.setLocation(location);
			if (nodes.item(1) != null) {
				System.err.println("Invalid Location tag, Ignored.");
			}
		}
		return data;
	}

	/**
	 * 
	 * @param list
	 * @param data
	 * @throws SAXException
	 * @throws ParseException
	 */
	private void procTuple(NodeList list, PIDFData data) throws SAXException,
			ParseException {
		NodeList tList, eList;
		Node element1, element2;

		for (int i = 0; i < list.getLength(); i++) {
			Tuple tuple = new Tuple();
			Element element = (Element) list.item(i);

			// tuple idの取得
			NamedNodeMap oAttrs = element.getAttributes();
			String tupleId = oAttrs.getNamedItem("id").getNodeValue();

			if (tupleId == null) {
				throw new IllegalArgumentException("No tuple id.");
			}

			tuple.id = tupleId;
			tList = element.getChildNodes();
			// tupleタグの処理
			for (int j = 0; j < tList.getLength(); j++) {
				element1 = tList.item(j);
				if (!(element1.getNodeType() == Node.ELEMENT_NODE)) {
					continue;
				}

				// statusタグの処理
				if (element1.getNodeName().equals("status")) {
					eList = element1.getChildNodes();
					for (int k = 0; k < eList.getLength(); k++) {
						element2 = eList.item(k);
						if (!(element2.getNodeType() == Node.ELEMENT_NODE)) {
							continue;
						}
						String nodeName = element2.getNodeName();
						if (nodeName.equals("basic")) {
							String basic = element2.getFirstChild()
									.getNodeValue();
							if (basic.equals("OPEN")) {
								tuple.status.basic = Tuple.Basic.OPEN;
							} else {
								tuple.status.basic = Tuple.Basic.CLOSE;
							}
						}

						// 自己定義タグ
						else {
							// tuple.(nodeName,
							// element2.getFirstChild().getNodeValue());
						}
					}
				}

				// contactタグの処理
				else if (element1.getNodeName().equals("contact")) {
					SipURI uri = new SipUri();
					String temp = element1.getFirstChild().getNodeValue();
					java.util.StringTokenizer stToken = new java.util.StringTokenizer(
							temp, "@:");
					String scheme = stToken.nextToken();
					if (!scheme.equals("sip") && !scheme.equals("pres")) {
						throw new IllegalArgumentException("Bad uri scheme :"
								+ scheme);
					}
					String user = stToken.nextToken();
					String host = stToken.nextToken();
					int port = Integer.parseInt(stToken.nextToken());
					uri.setUser(user);
					uri.setHost(host);
					uri.setPort(port);
					// tuple.name = user;
					tuple.contact = uri;
				}

				// noteの処理
				else if (element1.getNodeName().equals("note")) {
					tuple.addNote(element1.getFirstChild().getNodeValue());
				}
				// timestampの処理
				else if (element1.getNodeName().equals("timestamp")) {
					tuple.timestamp = element1.getFirstChild().getNodeValue();
				}
				// 自己定義タグの処理
				else {
					tuple.putExtension(element1.getNodeName(), element1
							.getFirstChild().getNodeValue());
				}
			}
			data.addTuple(tuple);
		}
	}

	/*
	 * (non-Javadoc)
	 * @see jp.ac.naka.ec.sip.pidf.PIDFParser#makeEntityContainerFromPIDF(jp.ac.naka.ec.sip.pidf.PIDFData)
	 */
	public EntityContainer makeEntityContainerFromPIDF(PIDFData data) {
		// TODO
		SipURI uri = (SipURI) data.getURI();
		String name = uri.getUser();
		EntityContainer con = new EntityContainerImpl(name);
		SipCore core = SipCore.getInstance();
		con.setURI(uri);
		con.setLocal(false);
		if (data.getLocation() != null)
			con.setLocation(data.getLocation());
		// Entityの作成
		List<Tuple> tuples = data.getTuples();
		for (Tuple tuple : tuples) {
			Entity entity = new EntityImpl(con);
			entity.setName((String) tuple.getExtension("name"));
			EntityType type = EntityType.valueOf((String) tuple
					.getExtension("type"));
			entity.setTupleId(tuple.id);
			entity.setEntityType(type);
			entity.setLocal(false);
			String temp = (String) tuple.getExtension("uri");
			if (temp != null) {
				URI e_uri = core.createURI(temp);
				if (e_uri != null)
					entity.setURI(e_uri);
			}
			con.addChild(entity);
		}
		con.setLocal(false);
		return con;
	}

	private String getNote(Node node) {
		return node.getFirstChild().getNodeValue();
	}

	/*
	 * (non-Javadoc)
	 * @see org.xml.sax.ErrorHandler#error(org.xml.sax.SAXParseException)
	 */
	public void error(SAXParseException e) throws SAXException {
		e.printStackTrace();
	}

	/*
	 * (non-Javadoc)
	 * @see org.xml.sax.ErrorHandler#fatalError(org.xml.sax.SAXParseException)
	 */
	public void fatalError(SAXParseException e) throws SAXException {
		e.printStackTrace();
	}

	/*
	 * (non-Javadoc)
	 * @see org.xml.sax.ErrorHandler#warning(org.xml.sax.SAXParseException)
	 */
	public void warning(SAXParseException e) throws SAXException {
		e.printStackTrace();
	}

}
