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

import java.text.ParseException;
import java.util.Arrays;
import java.util.List;

import javax.sip.DialogTerminatedEvent;
import javax.sip.IOExceptionEvent;
import javax.sip.InvalidArgumentException;
import javax.sip.RequestEvent;
import javax.sip.ResponseEvent;
import javax.sip.SipException;
import javax.sip.SipListener;
import javax.sip.TimeoutEvent;
import javax.sip.Transaction;
import javax.sip.TransactionTerminatedEvent;
import javax.sip.address.SipURI;
import javax.sip.address.URI;
import javax.sip.header.ContentDispositionHeader;
import javax.sip.header.ContentTypeHeader;
import javax.sip.header.FromHeader;
import javax.sip.header.RequireHeader;
import javax.sip.header.SupportedHeader;
import javax.sip.header.ToHeader;
import javax.sip.message.Request;
import javax.sip.message.Response;

/**
 * 
 * @author Takashi Kasuya
 *
 */
class DefaultListener implements SipListener {

	public final static List<String> supportMethods;
	private SipCore sipCore;
	protected List<String> extensions; // サポートする拡張
	protected List<String> requires; // 必要とする拡張

	private static org.apache.log4j.Logger logger = org.apache.log4j.Logger
			.getLogger(DefaultListener.class);

	static {
		String[] methods = { "INVITE", "MESSAGE", "SUBSCRIBE", "NOTIFY", "ACK", "REGISTER" };
		supportMethods = Arrays.asList(methods);
	}

	MethodDelegate delegate;

	ResponseDelegate resDelegate;

	public DefaultListener() {
		delegate = new RequestDelegate();
		resDelegate = new ResponseDelegate();
	}

	/*
	 * (non-Javadoc)
	 * @see javax.sip.SipListener#processRequest(javax.sip.RequestEvent)
	 */
	public void processRequest(RequestEvent evt) {
		if (sipCore == null) {
			sipCore = SipCore.getInstance();
		}
		Request req = evt.getRequest();
		FromHeader from = (FromHeader) req.getHeader(FromHeader.NAME);
		logger.info(evt.getRequest().getMethod() + " Message from: "
				+ from.getAddress().getURI().toString());
		// UASの基本動作
		try {
			// 1. メソッド検査
			methodInspection(evt);
			// 2. ヘッダ部検査
			headerInspection(evt);
			// 3. メッセージボディ処理
			contentProcessing(evt);
			// 4. 拡張機能の適用
			applyingExtensions(evt);
		} catch (Exception e) {
			logger.warn(e.getMessage());
			return;
		}
		// 5. リクエスト処理、6. レスポンス送信
		delegate.forward(evt);
	}

	/*
	 * (non-Javadoc)
	 * @see javax.sip.SipListener#processResponse(javax.sip.ResponseEvent)
	 */
	public void processResponse(ResponseEvent evt) {
		Response res = evt.getResponse();
		ToHeader to = (ToHeader) res.getHeader(ToHeader.NAME);
		logger.info(res.getStatusCode() + " Response from: "
				+ to.getAddress().toString());
		resDelegate.forward(evt);
	}

	/*
	 * (non-Javadoc)
	 * @see javax.sip.SipListener#processTimeout(javax.sip.TimeoutEvent)
	 */
	public void processTimeout(TimeoutEvent evt) {
		Transaction transaction = null;
		if (evt.isServerTransaction()) {
			// transaction = evt.getServerTransaction();
		} else {
			transaction = evt.getClientTransaction();
		}
		// TODO
	}

	/**
	 * 
	 * @param evt
	 * @throws SipException
	 * @throws ParseException
	 * @throws InvalidArgumentException
	 */
	protected void methodInspection(RequestEvent evt) throws SipException,
			ParseException, InvalidArgumentException {
		Request req = evt.getRequest();
		RequireHeader require = (RequireHeader) req
				.getHeader(RequireHeader.NAME);
		if (require == null) {
			return;
		}

		String method = req.getMethod();
		if (!supportMethods.contains(method)) {
			// 405 (Method Not Allowed) あと、本当は501も
			sipCore.sendResponse(Response.METHOD_NOT_ALLOWED, req);
			throw new SipException("METHOD_NOT_ALLOWED :" + method);
		}

	}

	/**
	 * SIPはリクエストに含まれるヘッダ部に関して、認識できないヘッダやフォーマットエラーになっているヘッダであっても、処理上必要でない限り無視をして処理を継続する。
	 * まず、USAはRequest-URIの値を検査する。サポートされないURL Schemeの場合、416レスポンスを返信する。
	 * Requireタグがある場合はそこに含まれるオプションタグを検査する。サポートされていない場合は420を返信する。
	 * 
	 * @param evt
	 * @throws SipException
	 * @throws InvalidArgumentException
	 * @throws ParseException
	 */

	protected void headerInspection(RequestEvent evt) throws SipException,
			ParseException, InvalidArgumentException {
		Request req = evt.getRequest();

		if (!checkURIScheme(req)) {
			// 416 (Unsepported URI Scheme)
			sipCore.sendResponse(Response.UNSUPPORTED_URI_SCHEME, req);
			throw new SipException("UNSUPPORTED_URI_SCHEME :"
					+ evt.getRequest().getRequestURI());
		}
		RequireHeader rHeader = (RequireHeader) req.getHeader("Require");

		if (rHeader == null) {
			return;
		}

		String tag = rHeader.getOptionTag();
		if (extensions == null || !extensions.contains(tag)) {
			if (supportMethods.contains(tag)) {
				return;
			}
			// 420 (Bad Extention)
			sipCore.sendResponse(Response.BAD_EXTENSION, req);
			throw new SipException("BAD_EXTENSION :" + tag);
		}
		if (extensions != null) {
			if (extensions.contains(tag)) {
				return;
			}
		}

		if (mergeCheck(req)) {
			// 482 (Loop Detected)
			sipCore.sendResponse(Response.LOOP_DETECTED, req);
		}
	}

	/**
	 * Content-Type, Content-Language, Content-Encodingヘッダで示される内容が処理可能かどうかの検査。
	 * 
	 * @param evt
	 * @throws SipException
	 * @throws ParseException
	 * @throws InvalidArgumentException
	 */
	protected void contentProcessing(RequestEvent evt) throws SipException,
			ParseException, InvalidArgumentException {

		Request req = evt.getRequest();
		byte[] raw = req.getRawContent();
		if (raw == null) {
			return;
		}
		ContentTypeHeader contentType = (ContentTypeHeader) req
				.getHeader("Content-Type");
		ContentDispositionHeader disposition = req.getContentDisposition();
		// ContentEncodingHeader encode = req.getContentEncoding();
		// ContentLanguageHeader language = req.getContentLanguage();

		if (disposition == null) {
			return;
		}
		if (disposition.getDispositionType().equals("handling=required")) {
			// MediaTypeのチェック
			sipCore.sendResponse(Response.UNSUPPORTED_MEDIA_TYPE, req);
			throw new SipException("UNSUPPORTED_MEDIA_TYPE :"
					+ contentType.toString());
		}

		// TODO encoding, languageのチェック
	}

	/**
	 * 拡張機能の適用。
	 * UASがSIPの拡張機能を適用する場合、受信したリクエストのSupportedヘッダに含まれるオプションタグの中から適用する拡張機能を示すオプションタグを選択し、返送するレスポンスのRequireヘッダに含ませる必要がある。
	 * 
	 * @param evt
	 * @throws SipException
	 * @throws ParseException
	 * @throws InvalidArgumentException
	 */
	protected void applyingExtensions(RequestEvent evt) throws SipException,
			ParseException, InvalidArgumentException {
		Request req = evt.getRequest();
		SupportedHeader support = (SupportedHeader) req.getHeader("Supported");
		if (requires == null) {
			return;
		} else if (!requires.contains(support)) {
			sipCore.sendResponse(Response.EXTENSION_REQUIRED, req);
			throw new SipException("EXTENSION_REQUIRED :" + requires.toString());
		}
	}

	/**
	 * 
	 * @param req
	 * @return
	 */
	private boolean checkURIScheme(Request req) {
		URI uri = req.getRequestURI();
		if (uri instanceof SipURI)
			return true;
		else
			return false;
	}

	/**
	 * 
	 * @param req
	 * @return
	 */
	protected boolean mergeCheck(Request req) {
		// 未実装
		// TODO
		return false;
	}

	/**
	 * 
	 * @param req
	 * @return
	 */
	protected boolean checkContent(Request req) {
		// 8.2.3 Content Processing 参照
		// TODO
		return true;
	}

	/**
	 * サポートするメソッドの取得
	 * 
	 * @return
	 */
	public List getSupportMethods() {
		return supportMethods;
	}

	/**
	 * 拡張機能の取得
	 * 
	 * @return
	 */
	public List<String> getExtensions() {
		return extensions;
	}

	/*
	 * (non-Javadoc)
	 * @see javax.sip.SipListener#processDialogTerminated(javax.sip.DialogTerminatedEvent)
	 */
	public void processDialogTerminated(DialogTerminatedEvent evt) {
		logger.info("Dialog terminated :" + evt.getDialog());
	}

	/*
	 * (non-Javadoc)
	 * @see javax.sip.SipListener#processIOException(javax.sip.IOExceptionEvent)
	 */
	public void processIOException(IOExceptionEvent arg0) {
		// TODO Auto-generated method stub

	}

	/*
	 * (non-Javadoc)
	 * @see javax.sip.SipListener#processTransactionTerminated(javax.sip.TransactionTerminatedEvent)
	 */
	public void processTransactionTerminated(TransactionTerminatedEvent evt) {
		// TODO Auto-generated method stub
		if (!evt.isServerTransaction()) {
			logger.info("ClientTransaction terminated : "
					+ evt.getClientTransaction());

		} else
			logger.info("ServerTransaction terminated : "
					+ evt.getServerTransaction());
	}
}
