/*
 * 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)
 * Nitin Khanna, Hughes Systique Corp. (Reason: Android specific change, optmization, bug fix) 
 */

package org.zoolu.sip.message;

import org.zoolu.sip.address.*;
import org.zoolu.sip.header.*;
import org.zoolu.sip.dialog.Dialog;
import org.zoolu.sip.provider.SipStack;
import org.zoolu.sip.provider.SipProvider;
import org.zoolu.sip.message.Message;
import org.zoolu.sip.message.SipMethods;

import java.util.Enumeration;
import java.util.Vector;

/**
 * BaseMessageFactory is used to create SIP messages, requests and responses by
 * means of two static methods: createRequest(), createResponse(). <BR>
 * A valid SIP request sent by a UAC MUST, at least, contain the following
 * header fields: To, From, CSeq, Call-ID, Max-Forwards, and Via; all of these
 * header fields are mandatory in all SIP requests. These sip header fields are
 * the fundamental building blocks of a SIP message, as they jointly provide for
 * most of the critical message routing services including the addressing of
 * messages, the routing of responses, limiting message propagation, ordering of
 * messages, and the unique identification of transactions. These header fields
 * are in addition to the mandatory request line, which contains the method,
 * Request-URI, and SIP version.
 */
public abstract class BaseMessageFactory {

	/**
	 * Creates a SIP request message.
	 * 
	 * @param method
	 *            method name
	 * @param request_uri
	 *            request-uri
	 * @param to
	 *            ToHeader NameAddress
	 * @param from
	 *            FromHeader NameAddress
	 * @param contact
	 *            Contact NameAddress (if null, no ContactHeader is added)
	 * @param host_addr
	 *            Via address
	 * @param host_port
	 *            Via port number
	 * @param call_id
	 *            Call-ID value
	 * @param cseq
	 *            CSeq value
	 * @param local_tag
	 *            tag in FromHeader
	 * @param remote_tag
	 *            tag in ToHeader (if null, no tag is added)
	 * @param branch
	 *            branch value (if null, a random value is picked)
	 * @param body
	 *            body (if null, no body is added)
	 */
	public static Message createRequest(String method, SipURL request_uri,
			NameAddress to, NameAddress from, NameAddress contact,
			String proto, String via_addr, int host_port, boolean rport,
			String call_id, long cseq, String local_tag, String remote_tag,
			String branch, String body) {
		Message req = new Message();
		// mandatory headers first (To, From, Via, Max-Forwards, Call-ID, CSeq):
		req.setRequestLine(new RequestLine(method, request_uri));
		ViaHeader via = new ViaHeader(proto, via_addr, host_port);
		if (rport)
			via.setRport();
		if (branch == null)
			branch = SipProvider.pickBranch();
		via.setBranch(branch);
		req.addViaHeader(via);
		req.setMaxForwardsHeader(new MaxForwardsHeader(70));
		if (remote_tag == null)
			req.setToHeader(new ToHeader(to));
		else
			req.setToHeader(new ToHeader(to, remote_tag));
		req.setFromHeader(new FromHeader(from, local_tag));
		req.setCallIdHeader(new CallIdHeader(call_id));
		req.setCSeqHeader(new CSeqHeader(cseq, method));
		// optional headers:
		if (contact != null) {
			MultipleHeader contacts = new MultipleHeader(SipHeaders.Contact);
			contacts.addBottom(new ContactHeader(contact));
			// System.out.println("DEBUG: Contact: "+contact.toString());
			req.setContacts(contacts);
		}
		req.setExpiresHeader(new ExpiresHeader(String
				.valueOf(SipStack.default_expires)));
		// add User-Agent header field
		if (SipStack.ua_info != null)
			req.setUserAgentHeader(new UserAgentHeader(SipStack.ua_info));
		// if (body!=null) req.setBody(body); else req.setBody("");
		req.setBody(body);
		// System.out.println("DEBUG: MessageFactory: request:\n"+req);
		return req;
	}

	/**
	 * Creates a SIP request message. Where
	 * <UL>
	 * <LI> via address and port are taken from SipProvider
	 * <LI> transport protocol is taken from request-uri (if transport parameter
	 * is present) or the default transport for the SipProvider is used.
	 * </UL>
	 * 
	 * @param sip_provider
	 *            the SipProvider used to fill the Via field
	 * @see #createRequest(String,SipURL,NameAddress,NameAddress,NameAddress,String,String,int,String,long,String,String,String,String)
	 */
	public static Message createRequest(SipProvider sip_provider,
			String method, SipURL request_uri, NameAddress to,
			NameAddress from, NameAddress contact, String call_id, long cseq,
			String local_tag, String remote_tag, String branch, String body) {
		String via_addr = sip_provider.getViaAddress();
		int host_port = sip_provider.getPort();
		boolean rport = sip_provider.isRportSet();
		String proto;
		if (request_uri.hasTransport())
			proto = request_uri.getTransport();
		else
			proto = sip_provider.getDefaultTransport();

		return createRequest(method, request_uri, to, from, contact, proto,
				via_addr, host_port, rport, call_id, cseq, local_tag,
				remote_tag, branch, body);
	}

	/**
	 * Creates a SIP request message. Where
	 * <UL>
	 * <LI> request-uri equals the To sip url
	 * <LI> via address and port are taken from SipProvider
	 * <LI> transport protocol is taken from request-uri (if transport parameter
	 * is present) or the default transport for the SipProvider is used.
	 * <LI> call_id is picked random
	 * <LI> cseq is picked random
	 * <LI> local_tag is picked random
	 * <LI> branch is picked random
	 * </UL>
	 * 
	 * @see #createRequest(String,SipURL,NameAddress,NameAddress,NameAddress,String,String,int,String,long,String,String,String,String)
	 */
	public static Message createRequest(SipProvider sip_provider,
			String method, SipURL request_uri, NameAddress to,
			NameAddress from, NameAddress contact, String body) { // SipURL
		// request_uri=to.getAddress();
		String call_id = sip_provider.pickCallId();
		int cseq = SipProvider.pickInitialCSeq();
		String local_tag = SipProvider.pickTag();
		// String branch=SipStack.pickBranch();
		return createRequest(sip_provider, method, request_uri, to, from,
				contact, call_id, cseq, local_tag, null, null, body);
	}

	/**
	 * Creates a SIP request message. Where
	 * <UL>
	 * <LI> request-uri equals the To sip url
	 * <LI> via address and port are taken from SipProvider
	 * <LI> transport protocol is taken from request-uri (if transport parameter
	 * is present) or the default transport for the SipProvider is used.
	 * <LI> contact is formed by the 'From' user-name and by the address and
	 * port taken from SipProvider
	 * <LI> call_id is picked random
	 * <LI> cseq is picked random
	 * <LI> local_tag is picked random
	 * <LI> branch is picked random
	 * </UL>
	 * 
	 * @see #createRequest(SipProvider,String,NameAddress,NameAddress,NameAddress,String)
	 */
	public static Message createRequest(SipProvider sip_provider,
			String method, NameAddress to, NameAddress from, String body) {
		String contact_user = from.getAddress().getUserName();
		NameAddress contact = new NameAddress(new SipURL(contact_user,
				sip_provider.getViaAddress(), sip_provider.getPort()));
		return createRequest(sip_provider, method, to.getAddress(), to, from,
				contact, body);
	}

	/**
	 * Creates a SIP request message within a dialog, with a new branch
	 * via-parameter.
	 * 
	 * @param dialog
	 *            the Dialog used to compose the various Message headers
	 * @param method
	 *            the request method
	 * @param body
	 *            the message body
	 */
	public static Message createRequest(Dialog dialog, String method,
			String body) {
		NameAddress to = dialog.getRemoteName();
		NameAddress from = dialog.getLocalName();
		NameAddress target = dialog.getRemoteContact();
		if (target == null)
			target = to;
		SipURL request_uri = target.getAddress();
		if (request_uri == null)
			request_uri = dialog.getRemoteName().getAddress();
		SipProvider sip_provider = dialog.getSipProvider();
		String via_addr = sip_provider.getViaAddress();
		int host_port = sip_provider.getPort();
		boolean rport = sip_provider.isRportSet();
		String proto;
		if (target.getAddress().hasTransport())
			proto = target.getAddress().getTransport();
		else
			proto = sip_provider.getDefaultTransport();
		NameAddress contact = dialog.getLocalContact();
		if (contact == null)
			contact = from;
		// increment the CSeq, if method is not ACK nor CANCEL
		if (!SipMethods.isAck(method) && !SipMethods.isCancel(method))
			dialog.incLocalCSeq();
		String call_id = dialog.getCallID();
		long cseq = dialog.getLocalCSeq();
		String local_tag = dialog.getLocalTag();
		String remote_tag = dialog.getRemoteTag();
		// String branch=SipStack.pickBranch();
		Message req = createRequest(method, request_uri, to, from, contact,
				proto, via_addr, host_port, rport, call_id, cseq, local_tag,
				remote_tag, null, body);
		Vector<NameAddress> route = dialog.getRoute();
		if (route != null && route.size() > 0) {
			Vector<String> route_s = new Vector<String>(route.size());
			for (Enumeration<NameAddress> e = route.elements(); 
					e.hasMoreElements(); ) {
				NameAddress elem = e.nextElement();
				route_s.add(elem.toString());
			}
			req.addRoutes(new MultipleHeader(SipHeaders.Route, route_s));
		}
		req.rfc2543RouteAdapt();
		return req;
	}

	/**
	 * Creates a new INVITE request out of any pre-existing dialogs.
	 * 
	 * @see #createRequest(String,SipURL,NameAddress,NameAddress,NameAddress,String,String,int,boolean,String,long,String,String,String,String)
	 */
	public static Message createInviteRequest(SipProvider sip_provider,
			SipURL request_uri, NameAddress to, NameAddress from,
			NameAddress contact, String body) {
		String call_id = sip_provider.pickCallId();
		int cseq = SipProvider.pickInitialCSeq();
		String local_tag = SipProvider.pickTag();
		// String branch=SipStack.pickBranch();
		if (contact == null)
			contact = from;
		return createRequest(sip_provider, SipMethods.INVITE, request_uri, to,
				from, contact, call_id, cseq, local_tag, null, null, body);
	}

	/**
	 * Creates a new INVITE request within a dialog (re-invite).
	 * 
	 * @see #createRequest(Dialog,String,String)
	 */
	public static Message createInviteRequest(Dialog dialog, String body) {
		return createRequest(dialog, SipMethods.INVITE, body);
	}

	/**
	 * Creates an ACK request for a 2xx response.
	 * 
	 * @see #createRequest(Dialog,String,String)
	 */
	public static Message create2xxAckRequest(Dialog dialog, String body) {
		return createRequest(dialog, SipMethods.ACK, body);
	}

	/** Creates an ACK request for a non-2xx response */
	public static Message createNon2xxAckRequest(SipProvider sip_provider,
			Message method, Message resp) {
		SipURL request_uri = method.getRequestLine().getAddress();
		FromHeader from = method.getFromHeader();
		ToHeader to = resp.getToHeader();
		String via_addr = sip_provider.getViaAddress();
		int host_port = sip_provider.getPort();
		boolean rport = sip_provider.isRportSet();
		String proto;
		if (request_uri.hasTransport())
			proto = request_uri.getTransport();
		else
			proto = sip_provider.getDefaultTransport();
		String branch = method.getViaHeader().getBranch();
		NameAddress contact = null;
		Message ack = createRequest(SipMethods.ACK, request_uri, to
				.getNameAddress(), from.getNameAddress(), contact, proto,
				via_addr, host_port, rport, method.getCallIdHeader()
						.getCallId(), method.getCSeqHeader()
						.getSequenceNumber(), from.getParameter("tag"), to
						.getParameter("tag"), branch, null);
		ack.removeExpiresHeader();
		if (method.hasRouteHeader())
			ack.setRoutes(method.getRoutes());
		return ack;
	}

	/**
	 * Creates an ACK request for a 2xx-response. Contact value is taken from
	 * SipStack
	 */
	/*
	 * public static Message create2xxAckRequest(Message resp, String body) {
	 * ToHeader to=resp.getToHeader(); FromHeader from=resp.getFromHeader(); int
	 * code=resp.getStatusLine().getCode(); SipURL request_uri;
	 * request_uri=resp.getContactHeader().getNameAddress().getAddress(); if
	 * (request_uri==null) request_uri=to.getNameAddress().getAddress(); String
	 * branch=SipStack.pickBranch(); NameAddress contact=null; if
	 * (SipStack.contact_url!=null) contact=new
	 * NameAddress(SipStack.contact_url); return
	 * createRequest(SipMethods.ACK,request_uri,to.getNameAddress(),from.getNameAddress(),contact,resp.getCallIdHeader().getCallId(),resp.getCSeqHeader().getSequenceNumber(),from.getParameter("tag"),to.getParameter("tag"),branch,body); }
	 */

	/** Creates an ACK request for a 2xx-response within a dialog */
	/*
	 * public static Message create2xxAckRequest(Dialog dialog, NameAddress
	 * contact, String body) { return
	 * createRequest(SipMethods.ACK,dialog,contact,body); }
	 */

	/** Creates an ACK request for a 2xx-response within a dialog */
	/*
	 * public static Message create2xxAckRequest(Dialog dialog, String body) {
	 * return createRequest(SipMethods.ACK,dialog,body); }
	 */

	/** Creates a CANCEL request. */
	public static Message createCancelRequest(Message method,Dialog dialog) {
		ToHeader to = method.getToHeader();
		FromHeader from = method.getFromHeader();
		SipURL request_uri = method.getRequestLine().getAddress();
		NameAddress contact = method.getContactHeader().getNameAddress();
		ViaHeader via = method.getViaHeader();
		String host_addr = via.getHost();
		int host_port = via.getPort();
		boolean rport = via.hasRport();
		String proto = via.getProtocol();
		String branch = method.getViaHeader().getBranch();
		dialog.incLocalCSeq();
		long cseq = dialog.getLocalCSeq();
		return createRequest(SipMethods.CANCEL, request_uri, to
				.getNameAddress(), from.getNameAddress(), contact, proto,
				host_addr, host_port, rport, method.getCallIdHeader()
						.getCallId(), /* method.getCSeqHeader()
						.getSequenceNumber() */ cseq, from.getParameter("tag"), to // modified
						.getParameter("tag"), branch, "");
	}

	/** Creates a BYE request. */
	public static Message createByeRequest(Dialog dialog) {
		Message msg = createRequest(dialog, SipMethods.BYE, null);
		msg.removeExpiresHeader();
		msg.removeContacts();
		return msg;
	}

	/**
	 * Creates a new REGISTER request.
	 * <p>
	 * If contact is null, set contact as star * (register all)
	 */
	public static Message createRegisterRequest(SipProvider sip_provider,
			NameAddress to, NameAddress from, NameAddress contact) {
		SipURL to_url = to.getAddress();
		SipURL registrar = new SipURL(to_url.getHost(), to_url.getPort());
		String via_addr = sip_provider.getViaAddress();
		int host_port = sip_provider.getPort();
		boolean rport = sip_provider.isRportSet();
		String proto;
		if (to_url.hasTransport())
			proto = to_url.getTransport();
		else
			proto = sip_provider.getDefaultTransport();
		String call_id = sip_provider.pickCallId();
		int cseq = SipProvider.pickInitialCSeq();
		String local_tag = SipProvider.pickTag();
		// String branch=SipStack.pickBranch();
		Message req = createRequest(SipMethods.REGISTER, registrar, to, from,
				contact, proto, via_addr, host_port, rport, call_id, cseq,
				local_tag, null, null, null);
		// if no contact, deregister all
		if (contact == null) {
			ContactHeader star = new ContactHeader(); // contact is *
			req.setContactHeader(star);
			req.setExpiresHeader(new ExpiresHeader(String
					.valueOf(SipStack.default_expires)));
		}
		return req;
	}

	// ################ Can be removed? ################
	/**
	 * Creates a new REGISTER request.
	 * <p>
	 * If contact is null, set contact as star * (register all)
	 */
	/*
	 * public static Message createRegisterRequest(SipProvider sip_provider,
	 * NameAddress to, NameAddress contact) { return
	 * createRegisterRequest(sip_provider,to,to,contact); }
	 */

	/**
	 * Creates a SIP response message.
	 * 
	 * @param req
	 *            the request message
	 * @param code
	 *            the response code
	 * @param reason
	 *            the response reason
	 * @param contact
	 *            the contact address
	 * @param local_tag
	 *            the local tag in the 'To' header
	 * @param body
	 *            the message body
	 */
	public static Message createResponse(Message req, int code, String reason,
			String local_tag, NameAddress contact, String content_type,
			String body) {
		Message resp = new Message();
		resp.setStatusLine(new StatusLine(code, reason));
		resp.setVias(req.getVias());
		if (code >= 180 && code < 300 && req.hasRecordRouteHeader())
			resp.setRecordRoutes(req.getRecordRoutes());
		ToHeader toh = req.getToHeader();
		if (local_tag != null)
			toh.setParameter("tag", local_tag);
		resp.setToHeader(toh);
		resp.setFromHeader(req.getFromHeader());
		resp.setCallIdHeader(req.getCallIdHeader());
		resp.setCSeqHeader(req.getCSeqHeader());
		if (contact != null)
			resp.setContactHeader(new ContactHeader(contact));
		// add Server header field
		if (SipStack.server_info != null)
			resp.setServerHeader(new ServerHeader(SipStack.server_info));
		// if (body!=null) resp.setBody(body); else resp.setBody("");
		if (content_type == null)
			resp.setBody(body);
		else
			resp.setBody(content_type, body);
		// System.out.println("DEBUG: MessageFactory:
		// response:\n"+resp.toString());
		return resp;
	}

	/**
	 * Creates a SIP response message. For 2xx responses generates the local tag
	 * by means of the SipStack.pickTag(req) method.
	 * 
	 * @see #createResponse(Message,int,String,NameAddress,String,String body)
	 */
	public static Message createResponse(Message req, int code, String reason,
			NameAddress contact) { // String
		// reason=SipResponses.reasonOf(code);
		String localtag = null;
		if (req.createsDialog() && !req.getToHeader().hasTag()) {
			if (SipStack.early_dialog || (code >= 200 && code < 300))
				localtag = SipProvider.pickTag(req);
		}
		return createResponse(req, code, reason, localtag, contact, null, null);
	}

}