/*
 * Copyright (C) 2005 Luca Veltri - University of Parma - Italy
 * 
 * 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)
 * Nitin Khanna, Hughes Systique Corp. (Reason: Android specific change, optmization, bug fix) 
 */

package org.zoolu.sip.message;

import org.zoolu.sip.provider.*;
import org.zoolu.sip.header.*;
import org.zoolu.net.UdpPacket;
import java.util.*;

/**
 * Class BaseMessageOtp implements a generic SIP Message. It extends class
 * BaseMessage adding one-time-parsing functionality (it parses the entire
 * Message just when it is costructed). <p/> At the contrary, class BaseMessage
 * works in a just-in-time manner (it parses the message each time a particular
 * header field is requested).
 */
public abstract class BaseMessageOtp extends BaseMessage {

	protected RequestLine request_line;
	protected StatusLine status_line;

	protected Vector<Header> headers;
	protected String body;

	/** Inits empty Message */
	private void init() {
		request_line = null;
		status_line = null;
		headers = null;
		body = null;
	}

	/** Costructs a new empty Message */
	public BaseMessageOtp() {
		init();
		headers = new Vector<Header>();
	}

	/** Costructs a new Message */
	public BaseMessageOtp(byte[] data, int offset, int len) {
		init();
		parseIt(new String(data, offset, len));
	}

	/** Costructs a new Message */
	public BaseMessageOtp(UdpPacket packet) {
		init();
		parseIt(new String(packet.getData(), packet.getOffset(), packet
				.getLength()));
	}

	/** Costructs a new Message */
	public BaseMessageOtp(String str) {
		init();
		parseIt(str);
	}

	/** Costructs a new Message */
	public BaseMessageOtp(BaseMessageOtp msg) {
		init();
		remote_addr = msg.remote_addr;
		remote_port = msg.remote_port;
		transport_proto = msg.transport_proto;
		connection_id = msg.connection_id;
		// packet_length=msg.packet_length;
		request_line = msg.request_line;
		status_line = msg.status_line;
		headers = new Vector<Header>();
		for (int i = 0; i < msg.headers.size(); i++)
			headers.addElement(msg.headers.elementAt(i));
		body = msg.body;
	}

	/** Sets the entire message */
	public void setMessage(String str) {
		parseIt(str);
	}

	/** Parses the Message from a String. */
	private void parseIt(String str) {
		SipParser par = new SipParser(str);
		String version = str.substring(0, 4);
		if (version.equalsIgnoreCase("SIP/"))
			status_line = par.getStatusLine();
		else
			request_line = par.getRequestLine();

		headers = new Vector<Header>();
		Header h = par.getHeader();
		while (h != null) {
			headers.addElement(h);
			h = par.getHeader();
		}
		ContentLengthHeader clh = getContentLengthHeader();
		if (clh != null) {
			int len = clh.getContentLength();
			body = par.getString(len);
		} else if (getContentTypeHeader() != null) {
			body = par.getRemainingString();
			if (body.length() == 0)
				body = null;
		}
	}

	/** Gets string representation of Message */
	public String toString() {
		StringBuffer str = new StringBuffer();
		if (request_line != null)
			str.append(request_line.toString());
		else if (status_line != null)
			str.append(status_line.toString());
		for (int i = 0; i < headers.size(); i++)
			str.append(((Header) headers.elementAt(i)).toString());
		str.append("\r\n");
		if (body != null)
			str.append(body);
		return str.toString();
	}

	/** Gets message length */
	public int getLength() {
		return toString().length();
	}

	// **************************** Requests ****************************/

	/** Whether Message is a Request */
	public boolean isRequest() {
		if (request_line != null)
			return true;
		else
			return false;
	}

	/** Whether Message is a <i>method</i> request */
	public boolean isRequest(String method) {
		if (request_line != null
				&& request_line.getMethod().equalsIgnoreCase(method))
			return true;
		else
			return false;
	}

	/** Whether Message has Request-line */
	protected boolean hasRequestLine() {
		return request_line != null;
	}

	/**
	 * Gets RequestLine in Message (Returns null if called for no request
	 * message)
	 */
	public RequestLine getRequestLine() {
		return request_line;
	}

	/** Sets RequestLine of the Message */
	public void setRequestLine(RequestLine rl) {
		request_line = rl;
	}

	/** Removes RequestLine of the Message */
	public void removeRequestLine() {
		request_line = null;
	}

	// **************************** Responses ****************************/

	/** Whether Message is a Response */
	public boolean isResponse() throws NullPointerException {
		if (status_line != null)
			return true;
		else
			return false;
	}

	/** Whether Message has Status-line */
	protected boolean hasStatusLine() {
		return status_line != null;
	}

	/**
	 * Gets StautsLine in Message (Returns null if called for no response
	 * message)
	 */
	public StatusLine getStatusLine() {
		return status_line;
	}

	/** Sets StatusLine of the Message */
	public void setStatusLine(StatusLine sl) {
		status_line = sl;
	}

	/** Removes StatusLine of the Message */
	public void removeStatusLine() {
		status_line = null;
	}

	// **************************** Generic Headers
	// ****************************/

	/** Removes Request\Status Line of the Message */
	protected void removeFirstLine() {
		removeRequestLine();
		removeStatusLine();
	}

	/** Gets the position of header <i>hname</i>. */
	protected int indexOfHeader(String hname) {
		for (int i = 0; i < headers.size(); i++) {
			Header h = (Header) headers.elementAt(i);
			if (hname.equalsIgnoreCase(h.getName()))
				return i;
		}
		return -1;
	}

	/**
	 * Gets the first Header of specified name (Returns null if no Header is
	 * found)
	 */
	public Header getHeader(String hname) {
		int i = indexOfHeader(hname);
		if (i < 0)
			return null;
		else
			return (Header) headers.elementAt(i);
	}

	/**
	 * Gets a Vector of all Headers of specified name (Returns empty Vector if
	 * no Header is found)
	 */
	public Vector<Header> getHeaders(String hname) {
		Vector<Header> v = new Vector<Header>();
		for (int i = 0; i < headers.size(); i++) {
			Header h = (Header) headers.elementAt(i);
			if (hname.equalsIgnoreCase(h.getName()))
				v.addElement(h);
		}
		return v;
	}

	/**
	 * Adds Header at the top/bottom. The bottom is considered before the
	 * Content-Length and Content-Type headers
	 */
	public void addHeader(Header header, boolean top) {
		if (top)
			headers.insertElementAt(header, 0);
		else
			headers.addElement(header);
	}

	/** Adds a Vector of Headers at the top/bottom */
	public void addHeaders(Vector<Header> headers, boolean top) {
		for (int i = 0; i < headers.size(); i++)
			if (top)
				this.headers.insertElementAt(headers.elementAt(i), i);
			else
				this.headers.addElement(headers.elementAt(i));
	}

	/** Adds MultipleHeader(s) <i>mheader</i> at the top/bottom */
	public void addHeaders(MultipleHeader mheader, boolean top) {
		if (mheader.isCommaSeparated())
			addHeader(mheader.toHeader(), top);
		else
			addHeaders(mheader.getHeaders(), top);
	}

	/**
	 * Adds Header before the first header <i>refer_hname</i> .
	 * <p>
	 * If there is no header of such type, it is added at top
	 */
	public void addHeaderBefore(Header new_header, String refer_hname) {
		int i = indexOfHeader(refer_hname);
		if (i < 0)
			i = 0;
		headers.insertElementAt(new_header, i);
	}

	/**
	 * Adds MultipleHeader(s) before the first header <i>refer_hname</i> .
	 * <p>
	 * If there is no header of such type, they are added at top
	 */
	public void addHeadersBefore(MultipleHeader mheader, String refer_hname) {
		if (mheader.isCommaSeparated())
			addHeaderBefore(mheader.toHeader(), refer_hname);
		else {
			int index = indexOfHeader(refer_hname);
			if (index < 0)
				index = 0;
			Vector<Header> hs = mheader.getHeaders();
			for (int k = 0; k < hs.size(); k++)
				headers.insertElementAt(hs.elementAt(k), index + k);
		}
	}

	/**
	 * Adds Header after the first header <i>refer_hname</i> .
	 * <p>
	 * If there is no header of such type, it is added at bottom
	 */
	public void addHeaderAfter(Header new_header, String refer_hname) {
		int i = indexOfHeader(refer_hname);
		if (i >= 0)
			i++;
		else
			i = headers.size();
		headers.insertElementAt(new_header, i);
	}

	/**
	 * Adds MultipleHeader(s) after the first header <i>refer_hname</i> .
	 * <p>
	 * If there is no header of such type, they are added at bottom
	 */
	public void addHeadersAfter(MultipleHeader mheader, String refer_hname) {
		if (mheader.isCommaSeparated())
			addHeaderAfter(mheader.toHeader(), refer_hname);
		else {
			int index = indexOfHeader(refer_hname);
			if (index >= 0)
				index++;
			else
				index = headers.size();
			Vector<Header> hs = mheader.getHeaders();
			for (int k = 0; k < hs.size(); k++)
				headers.insertElementAt(hs.elementAt(k), index + k);
		}
	}

	/** Removes first Header of specified name */
	public void removeHeader(String hname) {
		removeHeader(hname, true);
	}

	/** Removes first (or last) Header of specified name. */
	public void removeHeader(String hname, boolean first) {
		int index = -1;
		for (int i = 0; i < headers.size(); i++) {
			Header h = (Header) headers.elementAt(i);
			if (hname.equalsIgnoreCase(h.getName())) {
				index = i;
				if (first)
					i = headers.size();
			}
		}
		if (index >= 0)
			headers.removeElementAt(index);
	}

	/** Removes all Headers of specified name */
	public void removeAllHeaders(String hname) {
		for (int i = 0; i < headers.size(); i++) {
			Header h = (Header) headers.elementAt(i);
			if (hname.equalsIgnoreCase(h.getName())) {
				headers.removeElementAt(i);
				i--;
			}
		}
	}

	/**
	 * Sets the Header <i>hd</i> removing any previous headers of the same
	 * type.
	 */
	public void setHeader(Header hd) {
		boolean first = true;
		String hname = hd.getName();
		for (int i = 0; i < headers.size(); i++) {
			Header h = (Header) headers.elementAt(i);
			if (hname.equalsIgnoreCase(h.getName())) {
				if (first) { // replace it
					headers.setElementAt(h, i);
					first = false;
				} else { // remove it
					headers.removeElementAt(i);
					i--;
				}
			}
		}
		if (first)
			headers.addElement(hd);
	}

	/** Sets MultipleHeader <i>mheader</i> */
	public void setHeaders(MultipleHeader mheader) {
		if (mheader.isCommaSeparated())
			setHeader(mheader.toHeader());
		else {
			boolean first = true;
			String hname = mheader.getName();
			for (int i = 0; i < headers.size(); i++) {
				Header h = (Header) headers.elementAt(i);
				if (hname.equalsIgnoreCase(h.getName())) {
					if (first) { // replace it
						Vector<Header> hs = mheader.getHeaders();
						for (int k = 0; k < hs.size(); k++)
							headers.insertElementAt(hs.elementAt(k), i + k);
						first = false;
						i += hs.size() - 1;
					} else { // remove it
						headers.removeElementAt(i);
						i--;
					}
				}
			}
		}
	}

	// **************************** Specific Headers
	// ****************************/

	/** Whether Message has Body */
	public boolean hasBody() {
		return this.body != null;
	}

	/** Gets body(content) type */
	public String getBodyType() {
		return getContentTypeHeader().getContentType();
	}

	/** Sets the message body */
	public void setBody(String content_type, String body) {
		removeBody();
		if (body != null && body.length() > 0) {
			setContentTypeHeader(new ContentTypeHeader(content_type));
			setContentLengthHeader(new ContentLengthHeader(body.length()));
			this.body = body;
		} else {
			setContentLengthHeader(new ContentLengthHeader(0));
			this.body = null;
		}
	}

	/**
	 * Gets message body. The end of body is evaluated from the Content-Length
	 * header if present (SIP-RFC compliant), or from the end of message if no
	 * Content-Length header is present (non-SIP-RFC compliant)
	 */
	public String getBody() {
		return this.body;
	}

	/** Removes the message body (if it exists) and the final empty line */
	public void removeBody() {
		removeContentLengthHeader();
		removeContentTypeHeader();
		this.body = null;
	}

}
