/*
 * Copyright (C) 2005 Luca Veltri - University of Parma - Italy
 * Copyright (C) 2009 The Sipdroid Open Source Project
 * 
 * This file is part of MjSip (http://www.mjsip.org)
 * 
 * MjSip is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * MjSip 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 General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with MjSip; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * 
 * Author(s):
 * Luca Veltri (luca.veltri@unipr.it)
 */

package org.zoolu.sip.provider;

import org.zoolu.sip.address.*;
import org.zoolu.sip.header.Header;
import org.zoolu.sip.header.RequestLine;
import org.zoolu.sip.header.StatusLine;
import org.zoolu.sip.message.Message;
import org.zoolu.tools.DateFormat;
import java.util.Vector;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.zoolu.tools.Parser;

/**
 * Class SipParser extends class Parser for parsing of SIP messages.
 */
public class SipParser extends Parser {
	/** Creates a new SipParser based on String <i>s</i> */
	public SipParser(String s) {
		super(s);
	}

	/**
	 * Creates a new SipParser based on String <i>s</i> and starting from
	 * position <i>i</i>
	 */
	public SipParser(String s, int i) {
		super(s, i);
	}

	/** Creates a new SipParser based on StringBuffer <i>sb</i> */
	public SipParser(StringBuffer sb) {
		super(sb);
	}

	/**
	 * Creates a new SipParser based on StringBuffer <i>sb</i> and starting
	 * from position <i>i</i>
	 */
	public SipParser(StringBuffer sb, int i) {
		super(sb, i);
	}

	/** Creates a new SipParser starting from the current position. */
	public SipParser(Parser p) {
		super(p.getWholeString(), p.getPos());
	}

	/** MARK char[], composed by: '-' , '_' , '.' , '!' , '~' , '*' , '\'' , '|' */
	public static char[] MARK = { '-', '_', '.', '!', '~', '*', '\'', '|' };

	/** SEPARATOR char[], composed by: ' ','\t','\r','\n','(',')','<','>',',',';','\\','"','/','[',']','?','=','{','}' */
	public static char[] SEPARATOR = { ' ', '\t', '\r', '\n', '(', ')', '<',
			'>', ',', ';', '\\', '"', '/', '[', ']', '?', '=', '{', '}' };

	/** Checks whether a char is any MARK */
	public static boolean isMark(char c) { // return (c=='-' || c=='_' ||
		// c=='.' || c=='!' || c=='~' ||
		// c=='*' || c=='\'' || c=='|');
		return isAnyOf(MARK, c);
	}

	/** Unreserved char; that is an alphanum or a mark */
	public static boolean isUnreserved(char c) {
		return (isAlphanum(c) || isMark(c));
	}

	/**
	 * Separator; differently form RFC2543, do not include '@' and ':', while
	 * include '\r' and '\n'
	 */
	public static boolean isSeparator(char c) { // return (isSpace(c) ||
		// isCRLF(c) || c=='(' || c==')'
		// || c=='<' || c=='>' || c==','
		// || c==';' || c=='\\' ||
		// c=='"' || c=='/' || c=='[' ||
		// c==']' || c=='?' || c=='=' ||
		// c=='{' || c=='}');
		return isAnyOf(SEPARATOR, c);
	}

	/** Returns the first occurence of a separator or the end of the string */
	public int indexOfSeparator() {
		int begin = index;
		while (begin < str.length() && !isSeparator(str.charAt(begin)))
			begin++;
		return begin;
	}

	/** Index of the end of the header (EOH) */
	public int indexOfEOH() {
		SipParser par = new SipParser(str, index);
		while (true) {
			par.goTo(CRLF); // find the first CR or LF
			if (!par.hasMore())
				return str.length(); // if no CR/LF found return the end of
			// string
			int end = par.getPos();
			par.goToNextLine(); // skip the CR/LF chars
			if (!par.hasMore() || !isWSP(par.nextChar()))
				return end;
		}
	}

	/** Returns the begin of next header */
	public int indexOfNextHeader() {
		SipParser par = new SipParser(str, index);
		par.goToNextHeader();
		return par.getPos();
	}

	/**
	 * Returns the index of the begin of the first occurence of the Header
	 * <i>hname</i>
	 */
	public int indexOfHeader(String hname) {
	    Pattern p = Pattern.compile("^" + hname + ": ",
	        Pattern.CASE_INSENSITIVE
	        | Pattern.MULTILINE);
	    Matcher m = p.matcher(str);
	    if (m.find(index))
	      return m.start();
	    return str.length();
	}

	/** Goes to the begin of next header */
	public SipParser goToNextHeader() {
		index = indexOfEOH();
		goToNextLine();
		return this;
	}

	/**
	 * Go to the end of the last header. The final empty line delimiter is not
	 * considered as header
	 */
	public SipParser goToEndOfLastHeader() {
		String[] delimiters = { "\r\n\r\n", "\n\n" }; // double newline
		goTo(delimiters);
		if (!hasMore()) // no double newline found
		{
			if (str.startsWith("\r\n", str.length() - 2))
				index = str.length() - 2;
			else if (str.charAt(str.length() - 1) == '\n')
				index = str.length() - 1;
			else
				index = str.length();
		}
		return this;
	}

	/** Go to the begin (first char of) Message Body */
	public SipParser goToBody() {
		goToEndOfLastHeader();
		goTo('\n').skipChar();
		goTo('\n').skipChar();
		return this;
	}

	/** Returns the first header and goes to the next line. */
	public Header getHeader() {
		if (!hasMore())
			return null;
		int begin = getPos();
		int end = indexOfEOH();
		String header_str = getString(end - begin);
		goToNextLine();
		int colon = header_str.indexOf(':');
		if (colon < 0)
			return null;
		String hname = header_str.substring(0, colon).trim();
		String hvalue = header_str.substring(++colon).trim();
		return new Header(hname, hvalue);
	}

	/** Returns the first occurence of Header <i>hname</i>. */
	public Header getHeader(String hname) {
		SipParser par = new SipParser(str, indexOfHeader(hname));
		if (!par.hasMore())
			return null;
		par.skipN(hname.length());
		int begin = par.indexOf(':') + 1;
		int end = par.indexOfEOH();
		if (begin > end)
			return null;
		String hvalue = str.substring(begin, end).trim();
		index = end;
		return new Header(hname, hvalue);
	}

	// ************************ first-line ************************

	/** Returns the request-line. */
	public RequestLine getRequestLine() {
		String method = getString();
		skipWSP();
		int begin = getPos();
		int end = indexOfEOH();
		String request_uri = getString(end - begin);
		goToNextLine();
		return new RequestLine(method, (new SipParser(request_uri)).getSipURL());
	}

	/** Returns the status-line or null (if it doesn't start with "SIP/"). */
	public StatusLine getStatusLine() {
		String version = getString(4);
		if (!version.equalsIgnoreCase("SIP/")) {
			index = str.length();
			return null;
		}
		skipString().skipWSP(); // "SIP/2.0 "
		int code = getInt();
		int begin = getPos();
		int end = indexOfEOH();
		String reason = getString(end - begin).trim();
		goToNextLine();
		return new StatusLine(code, reason);
	}

	// *************************** URIs ***************************

	public static char[] uri_separators = { ' ', '>', '\n', '\r' };

	/**
	 * Returns the first URL. If no URL is found, it returns <b>null</b>
	 */
	public SipURL getSipURL() {
		goTo("sip:");
		if (!hasMore())
			return null;
		int begin = getPos();
		int end = indexOf(uri_separators);
		if (end < 0)
			end = str.length();
		String url = getString(end - begin);
		if (hasMore())
			skipChar();
		return new SipURL(url);
	}

	/*
	 * public static SipURL parseSipURL(String s) { SipParser par=new
	 * SipParser(s); return par.parseSipURL(); }
	 */
	/**
	 * Returns the first NameAddress in the string <i>str</i>. If no
	 * NameAddress is found, it returns <b>null</b>. A NameAddress is a string
	 * of the form of: <BR>
	 * <BLOCKQUOTE>
	 * 
	 * <PRE>
	 * &amp;nbsp&amp;nbsp &quot;user's name&quot; &lt;sip url&gt;
	 * </PRE>
	 * 
	 * </BLOCKQUOTE>
	 */
	public NameAddress getNameAddress() {
		String text = null;
		SipURL url = null;
		int begin = getPos();
		int begin_url = indexOf("<sip:");
		// System.out.println("DEBUG: inside parseNameAddress():
		// str="+this.getRemainingString());
		// System.out.println("DEBUG: inside parseNameAddress():
		// index="+begin_url);
		if (begin_url < 0) {
			url = getSipURL();
			// if (url==null) return null;
			if (url == null) {
				setPos(begin);
				url = new SipURL(getString());
			}
			return new NameAddress(url);
		} else {
			text = getString(begin_url - begin).trim();
			url = getSipURL();
			if (text.length() > 0 && text.charAt(0) == '\"'
					&& text.charAt(text.length() - 1) == '\"') {
				text = text.substring(1, text.length() - 1);
				// now you should eliminate escape chars ('\')..
			}
			if (text.length() == 0)
				return new NameAddress(url);
			else
				return new NameAddress(text, url);
		}
	}

	/**
	 * Skips the first NameAddress. If no NameAddress is found, it goes to the
	 * end of the string.
	 */
	/*
	 * public SipParser skipNameAddress() { int begin_url=indexOf("<sip:"); if
	 * (begin_url<0) begin_url=indexOf("sip:"); if (begin_url<0)
	 * index=str.length(); else { goTo(uri_separators);
	 * skipChars(uri_separators); } return this; }
	 */

	/**
	 * Returns the first quoted string in the string <i>str</i>; if no
	 * NameAddress is found, it returns <b>null</b>
	 */
	/*
	 * public String parseQuotedString() { int begin=str.indexOf('\"',index); if
	 * (begin<0) return null; begin++; int end=str.indexOf('\"',begin); String
	 * quotedtext=str.substring(begin,end); index=end; return quotedtext; }
	 */

	// *************************** DATE ***************************
	/** Returns a Date object according with the SIP standard date format */
	public Date getDate() {
		// DateFormat df=new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss
		// 'GMT'",Locale.US);
		try { // Date d=df.parse(str,new ParsePosition(index));
			Date d = DateFormat.parseEEEddMMM(str, index);
			index = str.indexOf("GMT", index) + 3;
			return d;
		} catch (Exception e) {
			e.printStackTrace();
			index = str.length();
			return null;
		}
	}

	// *************************** PARAMETERS ***************************

	public static char[] param_separators = { ' ', '=', ';', ',', '\n', '\r' };

	/**
	 * Gets the value of specified parameter.
	 * 
	 * @returns the parameter value or null if parameter does not exist or
	 *          doesn't have a value (i.e. in case of flag parameter).
	 */
	public String getParameter(String name) {
		while (hasMore()) {
			if (getWord(param_separators).equals(name)) {
				skipWSP();
				if (nextChar() == '=') {
					skipChar();
					return getWordSkippingQuoted(param_separators);
				} else
					return null;
			}
			goToSkippingQuoted(';');
			if (hasMore())
				skipChar(); // skip ';'
		}
		return null;
	}

	/**
	 * Gets a String Vector of parameter names. <BR>
	 * Returns null if no parameter is present
	 */
	public Vector<String> getParameters() {
		String name;
		Vector<String> params = new Vector<String>();
		while (hasMore()) {
			name = getWord(param_separators);
			if (name.length() > 0)
				params.addElement(new String(name));
			goToSkippingQuoted(';');
			if (hasMore())
				skipChar(); // skip ';'
		}
		return params;
	}

	/** Whether there is the specified parameter */
	public boolean hasParameter(String name) {
		while (hasMore()) {
			if (getWord(param_separators).equals(name))
				return true;
			goToSkippingQuoted(';');
			if (hasMore())
				skipChar(); // skip ';'
		}
		return false;
	}

	// ************************ MULTIPLE HEADERS ************************

	/** Finds the first comma-separator. Return -1 if no comma is found. */
	public int indexOfCommaHeaderSeparator() {
		boolean inside_quoted_string = false;
		for (int i = index; i < str.length(); i++) {
			char c = str.charAt(i);
			if (c == '"')
				inside_quoted_string = !inside_quoted_string;
			if (!inside_quoted_string && c == ',')
				return i;
		}
		return -1;
	}

	/**
	 * Goes to the first comma-separator. Goes to the end of string if no comma
	 * is found.
	 */
	public SipParser goToCommaHeaderSeparator() {
		int comma = indexOfCommaHeaderSeparator();
		if (comma < 0)
			index = str.length();
		else
			index = comma;
		return this;
	}

	// ************************** SIP MESSAGE ***************************

	/**
	 * Gets the first SIP message (all bytes until the first end of SIP
	 * message), if a SIP message delimiter is found.
	 * <p>
	 * The message begins from the first non-CRLF char.
	 */
	public Message getSipMessage() { // skip any CRLF sequence
		skipCRLF();
		// Get content length; if no Content-Length header found return null
		String text;
		if (getPos() == 0)
			text = str;
		else
			text = getRemainingString();
		Message msg = new Message(text);
		if (!msg.hasContentLengthHeader())
			return null;
		int body_len = msg.getContentLengthHeader().getContentLength();

		// gets the message (and go ahead), or returns null
		int begin = getPos();
		goToEndOfLastHeader();
		if (!hasMore())
			return null;
		goTo('\n');
		if (!hasMore())
			return null;
		skipChar().goTo('\n'); // skip the LF of last header and go the the new
		// line
		if (!hasMore())
			return null;
		int body_pos = skipChar().getPos(); // skip the LF of the empty line and
		// go the the body

		int end = body_pos + body_len;
		if (end <= str.length()) {
			index = end;
			return new Message(str.substring(begin, end));
		} else
			return null;
	}
}
