/*
* Conditions Of Use
*
* This software was developed by employees of the National Institute of
* Standards and Technology (NIST), an agency of the Federal Government.
* Pursuant to title 15 Untied States Code Section 105, works of NIST
* employees are not subject to copyright protection in the United States
* and are considered to be in the public domain.  As a result, a formal
* license is not needed to use the software.
*
* This software is provided by NIST as a service and is expressly
* provided "AS IS."  NIST MAKES NO WARRANTY OF ANY KIND, EXPRESS, IMPLIED
* OR STATUTORY, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTY OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT
* AND DATA ACCURACY.  NIST does not warrant or make any representations
* regarding the use of the software or the results thereof, including but
* not limited to the correctness, accuracy, reliability or usefulness of
* the software.
*
* Permission to use this software is contingent upon your acceptance
* of the terms of this agreement
*
* .
*
*/
package gov.nist.javax.sip.stack;

import gov.nist.core.Host;
import gov.nist.core.HostPort;
import gov.nist.core.InternalErrorHandler;
import gov.nist.javax.sip.ListeningPointImpl;
import gov.nist.javax.sip.header.Via;

import java.io.IOException;
import java.net.InetAddress;
import java.text.ParseException;

import javax.sip.InvalidArgumentException;

/**
 * This is the Stack abstraction for the active object that waits for messages
 * to appear on the wire and processes these messages by calling the
 * MessageFactory interface to create a ServerRequest or ServerResponse object.
 * The main job of the message processor is to instantiate message channels for
 * the given transport.
 *
 * @version 1.2 $Revision: 1.18 $ $Date: 2009/10/16 22:58:41 $
 *
 * @author M. Ranganathan <br/>
 *
 */
public abstract class MessageProcessor implements Runnable {
    /**
     * A string containing the 0.0.0.0 IPv4 ANY address.
     */
    protected static final String IN_ADDR_ANY = "0.0.0.0";

    /**
     * A string containing the ::0 IPv6 ANY address.
     */
    protected static final String IN6_ADDR_ANY = "::0";
    /**
     * My Sent by string ( which I use to set the outgoing via header)
     */
    private  String sentBy;

    private HostPort sentByHostPort;

    /*
     * The IP Address that was originally assigned ( Can be ANY )
     */

    private String savedIpAddress;

    /**
     * The IP address where I am listening.
     */
    private InetAddress ipAddress;

    /**
     * The port where I am listening
     */
    private int port;

    /**
     * The transport where I am listening
     */
    protected String transport;

    /**
     * The Listening Point to which I am assigned.
     */
    private ListeningPointImpl listeningPoint;

    private boolean sentBySet;

    /**
     * Our stack (that created us).
     */
    protected SIPTransactionStack sipStack;
    
    protected MessageProcessor( String transport ) {
    	this.transport = transport;
    }
    
    /**
     * Constructor
     *
     * @param ipAddress -- ip address where I am listening for incoming requests.
     * @param port -- port where i am listening for incoming requests.
     * @param transport -- transport to use for the message processor (UDP/TCP/TLS).
     */
    protected MessageProcessor( InetAddress ipAddress, int port, String transport,
    							SIPTransactionStack transactionStack ) {
    	this( transport );
    	this.initialize(ipAddress, port, transactionStack);
    }

    /**
     * Initializes this MessageProcessor. Needed for extensions
     * that use classloading
     * 
     * @param ipAddress2
     * @param transactionStack
     * @param port2
     */
	public final void initialize( InetAddress ipAddress, int port,
			SIPTransactionStack transactionStack ) {
		
		this.sipStack = transactionStack;
        this.savedIpAddress = ipAddress.getHostAddress();
        this.ipAddress = ipAddress;
        this.port = port;
        this.sentByHostPort = new HostPort();
        this.sentByHostPort.setHost(new Host(ipAddress.getHostAddress()));
        this.sentByHostPort.setPort(port);		
	}
    
    /**
     * Get the transport string.
     *
     * @return A string that indicates the transport. (i.e. "tcp" or "udp")
     */
    public String getTransport() {
        return this.transport;
    }

    /**
     * Get the port identifier.
     *
     * @return the port for this message processor. This is where you receive
     *         messages.
     */
    public int getPort() {
        return this.port;
    }

    /**
     * Get the Via header to assign for this message processor. The topmost via
     * header of the outoging messages use this.
     *
     * @return the ViaHeader to be used by the messages sent via this message processor.
     */
    public Via getViaHeader() {
        try {
            Via via = new Via();
            if (this.sentByHostPort != null) {
                via.setSentBy(sentByHostPort);
                via.setTransport(this.getTransport());
            } else {
                Host host = new Host();
                host.setHostname(this.getIpAddress().getHostAddress());
                via.setHost(host);
                via.setPort(this.getPort());
                via.setTransport(this.getTransport());
            }
            return via;
        } catch (ParseException ex) {
            ex.printStackTrace();
            return null;
        } catch (InvalidArgumentException ex) {
            ex.printStackTrace();
            return null;
        }
    }
    public ListeningPointImpl getListeningPoint() {
        if ( listeningPoint == null )  {
            if ( this.getSIPStack().isLoggingEnabled()) {
                this.getSIPStack().getStackLogger().logError("getListeningPoint" + this +
                        " returning null listeningpoint");

            }
        }
        return listeningPoint;
    }

    public void setListeningPoint(ListeningPointImpl lp) {
        if ( this.getSIPStack().isLoggingEnabled()) {
            this.getSIPStack().getStackLogger().logDebug("setListeningPoint" + this +
                    " listeningPoint = " + lp);

        }
        if ( lp.getPort() != this.getPort())
            InternalErrorHandler.handleException
            ("lp mismatch with provider",getSIPStack().getStackLogger());
        this.listeningPoint = lp;

    }

    /**
     * Get the saved IP Address.
     */
    public String getSavedIpAddress() {
        return this.savedIpAddress;
    }
    /**
     * @return the ip address for this message processor.
     */
    public InetAddress getIpAddress() {
          return this.ipAddress;
    }
    /**
     * @param ipAddress the ipAddress to set
     */
    protected void setIpAddress(InetAddress ipAddress) {
        this.sentByHostPort.setHost( new Host(ipAddress.getHostAddress()));
        this.ipAddress = ipAddress;
    }

    /**
     * Set the sentby string. This is used for stamping outgoing messages sent
     * from this listening point.
     *
     * @param sentBy
     */
    public void setSentBy(String sentBy) throws ParseException {

        int ind = sentBy.indexOf(":");
        if (ind == -1) {
            this.sentByHostPort = new HostPort();
            this.sentByHostPort.setHost(new Host(sentBy));
        } else {
            this.sentByHostPort = new HostPort();
            this.sentByHostPort.setHost(new Host(sentBy.substring(0, ind)));
            String portStr = sentBy.substring(ind + 1);
            try {
                int port = Integer.parseInt(portStr);
                this.sentByHostPort.setPort(port);
            } catch (NumberFormatException ex) {
                throw new ParseException("Bad format encountered at ", ind);
            }
        }
        this.sentBySet = true;
        this.sentBy = sentBy;

    }

    /**
     * Get the sentby string.
     *
     */
    public String getSentBy() {
        if ( this.sentBy == null && this.sentByHostPort != null) {
            this.sentBy = this.sentByHostPort.toString();
        }
        return this.sentBy;
    }

    ////////////////////////////////////////////////////////////////////////////////////////
    // Abstract methods
    ///////////////////////////////////////////////////////////////////////////////////////
    /**
     * Get the SIP Stack.
     *
     * @return the sip stack.
     */
    public abstract SIPTransactionStack getSIPStack();

    /**
     * Create a message channel for the specified host/port.
     *
     * @return New MessageChannel for this processor.
     */
    public abstract MessageChannel createMessageChannel(HostPort targetHostPort)
            throws IOException;

    /**
     * Create a message channel for the specified host/port.
     *
     * @return New MessageChannel for this processor.
     */
    public abstract MessageChannel createMessageChannel(InetAddress targetHost,
            int port) throws IOException;


    /**
     * Start our thread.
     */
    public abstract void start() throws IOException;

    /**
     * Stop method.
     */
    public abstract void stop();

    /**
     * Default target port used by this processor. This is 5060 for TCP / UDP
     */
    public abstract int getDefaultTargetPort();

    /**
     * Flags whether this processor is secure or not.
     */
    public abstract boolean isSecure();

    /**
     * Maximum number of bytes that this processor can handle.
     */
    public abstract int getMaximumMessageSize();

    /**
     * Return true if there are pending messages to be processed (which prevents
     * the message channel from being closed).
     */
    public abstract boolean inUse();



    /**
     * Run method.
     */
    public abstract void run();

    /**
     * @return Returns the sentBySet.
     */
    public boolean isSentBySet() {
        return sentBySet;
    }


    /**
     * Get the defalt port for the message processor.
     *
     * @param transport
     * @return -- the default port for the message processor.
     */

    public static int getDefaultPort(String transport) {

        return transport.equalsIgnoreCase("TLS")?5061:5060;
    }



}