/*
 * The contents of this file are subject to the terms
 * of the Common Development and Distribution License
 * (the License).  You may not use this file except in
 * compliance with the License.
 *
 * You can obtain a copy of the license at
 * https://glassfish.dev.java.net/public/CDDLv1.0.html or
 * glassfish/bootstrap/legal/CDDLv1.0.txt.
 * See the License for the specific language governing
 * permissions and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL
 * Header Notice in each file and include the License file
 * at glassfish/bootstrap/legal/CDDLv1.0.txt.
 * If applicable, add the following below the CDDL Header,
 * with the fields enclosed by brackets [] replaced by
 * you own identifying information:
 * "Portions Copyrighted [year] [name of copyright owner]"
 *
 * Copyright (c) Ericsson AB, 2004-2007. All rights reserved.
 */
package com.ericsson.ssa.sip;

import com.ericsson.ssa.container.SipParserErrorHandler;
import com.ericsson.ssa.sip.dns.SipTransports;
import com.ericsson.ssa.sip.dns.TargetTuple;

import java.io.UnsupportedEncodingException;

import java.net.InetSocketAddress;

import java.nio.ByteBuffer;

import java.util.ArrayList;
import java.util.ListIterator;
import java.util.logging.Level;

// inserted by hockey (automatic)
import java.util.logging.Logger;

import javax.servlet.sip.ServletParseException;
import javax.servlet.sip.SipFactory;
import javax.servlet.sip.URI;


/**
 * This is a very nice class that no-one had javadoc'ed before.
 *
 * ehswolm 2006-12-01: I don't feel like formatting this code just right now...
 *
 * @author ???
 * @reviewed eandbur 2006-dec-01
 */
public class SipParser {
    private static SipFactory sf = SipFactoryImpl.getInstance();
    private static SipParser _instance = null;
    Logger log = (Logger) Logger.getLogger("SipContainer");
    private ArrayList<MandatoryHeaderValidator> mandatoryHeaderValidators;

    // Prevent instance creation of this singleton.
    private SipParser() {
        // Initialization of validators
        mandatoryHeaderValidators = new ArrayList<MandatoryHeaderValidator>();
        mandatoryHeaderValidators.add(new CallIDValidator());
        mandatoryHeaderValidators.add(new AddressHeaderValidator(Header.FROM));
        mandatoryHeaderValidators.add(new AddressHeaderValidator(Header.TO));
        mandatoryHeaderValidators.add(new CSeqHeaderValidator());
        mandatoryHeaderValidators.add(new ViaHeaderValidator());
        mandatoryHeaderValidators.add(new MaxForwardsValidator());
    }

    public synchronized static SipParser getInstance() {
        if (_instance != null) {
            return _instance;
        }

        return _instance = new SipParser();
    }

    public SipServletMessageImpl parseMessage(SipServletMessageImpl message,
        ByteBuffer bb, InetSocketAddress local, TargetTuple remote,
        SipParserErrorHandler errorHandler) throws UnsupportedEncodingException {
        long startTime = 0;
        long endTime;

        if (log.isLoggable(Level.FINE)) {
            log.log(Level.FINE, "Data is ready for process : " + bb.limit());
        }

        if (log.isLoggable(Level.FINE)) {
            startTime = System.nanoTime();
        }

        //bb.rewind();
        byte[] bs = bb.array(); //Maybe not working on all platforms
                                // Process the first line

        int cnt = bb.position();
        int limit = bb.limit();
        int offset = bb.arrayOffset();

        while ((message == null) && (cnt < limit)) {
            try {
                int position = bb.position();
                int foundSipVersion = -1;
                boolean isRequest = false;
                boolean isResponse = false;

                while (((cnt + 1) < limit) &&
                        ((bs[cnt + offset] != '\r') ||
                        (bs[cnt + 1 + offset] != '\n'))) {
                    // there is room for the version string
                    if (!isRequest && !isResponse && ((cnt + 6) < limit) &&
                            ((bs[cnt + offset] == 's') ||
                            (bs[cnt + offset] == 'S'))) { // Check
                                                          // for
                                                          // ip/2.0

                        if (((bs[cnt + 1 + offset] == 'i') ||
                                (bs[cnt + 1 + offset] == 'I')) &&
                                ((bs[cnt + 2 + offset] == 'p') ||
                                (bs[cnt + 2 + offset] == 'P')) &&
                                (bs[cnt + 3 + offset] == '/') &&
                                (bs[cnt + 4 + offset] == '2') &&
                                (bs[cnt + 5 + offset] == '.') &&
                                (bs[cnt + 6 + offset] == '0')) {
                            foundSipVersion = cnt; // Marks begining of SIP/2.0

                            if (log.isLoggable(Level.FINE)) {
                                log.log(Level.FINE,
                                    "Found SIP/2.0 version at index = " +
                                    foundSipVersion);
                            }

                            // Check if it is at the end, then it is a request
                            if (((cnt + 8) < limit) &&
                                    (bs[cnt + 7 + offset] == '\r') &&
                                    (bs[cnt + 8 + offset] == '\n')) {
                                isRequest = true;
                                cnt += 6;

                                if (log.isLoggable(Level.FINE)) {
                                    log.log(Level.FINE,
                                        "The message is a SIP request");
                                }
                            } else if (((cnt + 7) < limit) &&
                                    (bs[cnt + 7 + offset] == ' ')) {
                                isResponse = true;
                                cnt += 7;

                                if (log.isLoggable(Level.FINE)) {
                                    log.log(Level.FINE,
                                        "The message is a SIP response");
                                }
                            } else// Not a valid SIP/2.0 string
                             {
                                cnt += 6;

                                if (log.isLoggable(Level.FINE)) {
                                    log.log(Level.FINE,
                                        "Not a valid SIP/2.0 string");
                                }

                                foundSipVersion = -1;
                            }
                        }
                    }

                    cnt++;
                }

                if ((cnt + 2) < limit) {
                    bb.position(cnt + 2);
                } else {
                    return null;
                }

                if (log.isLoggable(Level.FINE)) {
                    log.log(Level.FINE,
                        "The first line is : " +
                        new String(bs, position + offset, (cnt - position),
                            SipFactoryImpl.SIP_CHARSET));
                }

                if (isRequest) {
                    int x = foundSipVersion - 2;

                    while ((x > 0) && (bs[x + offset] != ' ')) {
                        x--;
                    }

                    String reqUri = new String(bs, x + 1 + offset,
                            (foundSipVersion - x - 2),
                            SipFactoryImpl.SIP_CHARSET);

                    if (log.isLoggable(Level.FINE)) {
                        log.log(Level.FINE,
                            "ReqUri = " + reqUri + " x = " + x + " len = " +
                            (foundSipVersion - x - 2));
                    }

                    if (x == 0) {
                        // TODO point after CRLF
                        // bb.position()
                        if (log.isLoggable(Level.FINE)) {
                            log.log(Level.FINE,
                                "Error in parser the first line is corrupt!");
                        }

                        return null;
                    }

                    // The rest is method
                    // TODO check for capital case e.g. INVITE
                    int foundMethod = x--;

                    while ((x > 0) && (bs[x - 1] > 0x40) && (bs[x - 1] < 0x5B)) {
                        x--;
                    }

                    String method = new String(bs, x + offset,
                            (foundMethod - x), SipFactoryImpl.SIP_CHARSET);

                    //String nice = new String(bs, x + 1 + offset, (foundMethod - x + 1), SipFactoryImpl.SIP_CHARSET);
                    if (log.isLoggable(Level.FINE)) {
                        log.log(Level.FINE, "method = " + method);
                    }

                    URI uri = sf.createURI(reqUri);

                    if (uri == null) {
                        if (log.isLoggable(Level.FINE)) {
                            log.log(Level.FINE,
                                "Error could not create Request Uri.");
                        }

                        return null;
                    }

                    message = new SipServletRequestImpl(method, uri,
                            SipFactoryImpl.PROTOCOL_LINE);
                    message.setLocal(local);
                    message.setRemote(remote);
                } else if (isResponse) { // Start after the SIP/2.0 token

                    int x = foundSipVersion + 8; // SIP/2.0 len +1
                    int start = foundSipVersion + 8;

                    while ((x < cnt) && (bs[x + offset] != ' ')) {
                        x++;
                    }

                    String codeStr = new String(bs, start + offset,
                            (x - start), SipFactoryImpl.SIP_CHARSET);
                    String phrase = new String(bs, (x + 1 + offset),
                            (cnt - x - 1), SipFactoryImpl.SIP_CHARSET);

                    if (log.isLoggable(Level.FINE)) {
                        log.log(Level.FINE,
                            "codeStr = " + codeStr + " phrase = " + phrase);
                    }

                    int code = Integer.parseInt(codeStr);

                    if ((699 < code) || (code < 100)) {
                        if (log.isLoggable(Level.FINE)) {
                            log.log(Level.FINE,
                                "Error code out of range in response : " +
                                code);
                        }

                        return null;
                    }

                    message = new SipServletResponseImpl(null,
                            SipFactoryImpl.PROTOCOL_LINE, code, phrase);
                    message.setLocal(local);
                    message.setRemote(remote);
                } else {
                    if (log.isLoggable(Level.FINE)) {
                        log.log(Level.FINE,
                            "The URI is not a request nor a response!");
                    }

                    return null;
                }
            } catch (Exception e) {
                if (log.isLoggable(Level.FINE)) {
                    log.log(Level.FINE, "Error in parser = ", e);
                }

                return null;
            }

            cnt += 2; // Continuing point to the firs character after the initial
                      // line CRLF
        }

        //cnt += 2; // Continuing point to the firs character after the initial
        // line CRLF
        if (message == null) {
            return null;
        }

        if ((cnt < limit) && !message.isHeadersComplete()) {
            Header header = null;
            int quoteCnt = 0;
            boolean startToken = true;
            int position = bb.position();
            boolean incrCnt = true; // Whether the cnt should be incremented when continuing for loop

            for (; cnt < limit;) {
                incrCnt = true; // Default is increment, reset every time.

                if ((bs[cnt + offset] == ':') && (header == null)) {
                    if ((cnt - position) < 1) {
                        // Line starts with colon. Do not accept
                        if (errorHandler != null) {
                            errorHandler.handleError(((SipServletRequestImpl) (message)),
                                400, "Malformed header");
                        }

                        return null;
                    }

                    header = Header.create(bs, position + offset,
                            (cnt - position), message);
                    position = cnt + 1;
                    startToken = true;
                } else if (startToken &&
                        ((bs[cnt + offset] == ' ') ||
                        (bs[cnt + offset] == '\t'))) {
                    position = cnt + 1;
                } else if (bs[cnt + offset] == '"') {
                    quoteCnt++;
                } else if ((bs[cnt + offset] == ',') && ((quoteCnt % 2) == 0) &&
                        (header != null)) { // multi header on one line
                                            // header.setInLineValues(true);
                                            // Must check if Date header

                    if ((header != null) && !header._singleLine &&
                            !(((cnt - position) == 3) && ((cnt + 1) < limit) &&
                            (bs[cnt + 1 + offset] == ' '))) {
                        // TODO - Should we throw a parse exception if in single
                        // line
                        // header map?
                        // This will have an impact on performance so it has to
                        // be
                        // measured.
                        // Eg trap Unsupported op ex and generate parse ex.
                        header.setValue(new String(bs, position + offset,
                                (cnt - position), SipFactoryImpl.SIP_CHARSET),
                            false);
                        header.setIsNextValueOnSameLine(true, false);
                        position = ++cnt;

                        // Trim WS
                        while ((cnt < limit) && (bs[cnt + offset] == ' '))
                            position = ++cnt;

                        if (header._singleLine && log.isLoggable(Level.INFO)) {
                            log.log(Level.INFO,
                                "SingleLineHeader " + header.getName() +
                                " is parsed as multiline header with a separator \',\' ");

                            if (log.isLoggable(Level.FINE)) {
                                log.log(Level.FINE,
                                    "The SingleLineHeader value : " +
                                    header.getValue() +
                                    " will be overwritten!");
                            }
                        }
                    }
                } else if (((cnt + 1) < limit) && (bs[cnt + offset] == '\r') &&
                        (bs[cnt + 1 + offset] == '\n')) {
                    if (header == null) {
                        message.setHeadersComplete(true);
                        cnt += 2;
                        position = cnt;
                        // MARK
                        bb.position((cnt > limit) ? limit : cnt);

                        break;
                    } else if (((cnt + 3) < limit) &&
                            (bs[cnt + 2 + offset] == '\r') &&
                            (bs[cnt + 3 + offset] == '\n')) {
                        // The rest is body
                        header.setValue(new String(bs, position + offset,
                                (cnt - position), SipFactoryImpl.SIP_CHARSET),
                            false);
                        header.setIsNextValueOnSameLine(false, false);
                        message.addHeader(header);
                        message.setHeadersComplete(true);
                        cnt += 4;
                        // MARK
                        bb.position((cnt > limit) ? limit : cnt);

                        if (log.isLoggable(Level.FINE)) {
                            if (header._singleLine) {
                                log.log(Level.FINE,
                                    "Adding Header " + header.getName() +
                                    " = " + header.getValue() + " (" +
                                    bb.position() + ')');
                            } else {
                                ListIterator<String> li = header.getValues();
                                int x = 0;

                                while (li.hasNext()) {
                                    log.log(Level.FINE,
                                        "Adding Header " + header.getName() +
                                        " = " + li.next() + " (" +
                                        bb.position() + ')' + '<' + (++x) +
                                        '>');
                                }
                            }
                        }

                        header = null;
                        quoteCnt = 0;
                        position = cnt;

                        break;
                    } else if (((cnt + 2) < limit) &&
                            ((bs[cnt + 2] == ' ') || (bs[cnt + 2] == '\t'))) {
                        // Append String - multi line header
                        bs[cnt + offset] = ' ';
                        bs[cnt + 1 + offset] = ' ';
                        bs[cnt + 2 + offset] = ' ';
                        cnt += 3;
                    } else if (header != null) {
                        header.setValue(new String(bs, position + offset,
                                (cnt - position), SipFactoryImpl.SIP_CHARSET),
                            false);
                        header.setIsNextValueOnSameLine(false, false);
                        message.addHeader(header);
                        cnt += 2;
                        // MARK
                        bb.position((cnt > limit) ? limit : cnt);

                        if (log.isLoggable(Level.FINE)) {
                            if (header._singleLine) {
                                log.log(Level.FINE,
                                    "Adding Header " + header.getName() +
                                    " = " + header.getValue() + " (" +
                                    bb.position() + ')');
                            } else {
                                ListIterator<String> li = header.getValues();
                                int x = 0;

                                while (li.hasNext()) {
                                    log.log(Level.FINE,
                                        "Adding Header " + header.getName() +
                                        " = " + li.next() + " (" +
                                        bb.position() + ')' + '<' + (++x) +
                                        '>');
                                }
                            }
                        }

                        header = null;
                        quoteCnt = 0;
                        startToken = true;
                        // Don't increment as we have already stepped forward.
                        incrCnt = false;
                    }

                    position = cnt;
                } else if (startToken) {
                    startToken = false;
                }

                // Do the increment if we should.
                if (incrCnt) {
                    cnt++;
                }
            }
        }

        if (message.isHeadersComplete() && !message.isMessageComplete()) {
            String errorStr = null;
            Header header = message.getRawHeader(Header.CALL_ID);

            if ((header == null) || (header.getValue() == null) ||
                    (header.getValue().length() == 0)) {
                errorStr = "Failed to validate mandatory header CallId";
            }

            header = message.getRawHeader(Header.TO);

            if ((header == null) || (header.getValue() == null) ||
                    (header.getValue().length() == 0)) {
                errorStr = "Failed to validate mandatory header To";
            }

            if (message.getMessageType() == SipServletMessageImpl.SipMessageType.SipRequest) {
                try {
                    if (header.getAddressValue().getParameter("tag") == null) {
                        ((SipServletRequestImpl) message).setInitial(true);
                    }
                } catch (ServletParseException ignore) {
                    errorStr = "To header not formated as a name address!";
                }
            }

            header = message.getRawHeader(Header.FROM);

            if ((header == null) || (header.getValue() == null) ||
                    (header.getValue().length() == 0)) {
                errorStr = "Failed to validate mandatory header From";
            }

            header = message.getRawHeader(Header.CSEQ);

            if ((header == null) || (header.getValue() == null) ||
                    (header.getValue().length() == 0)) {
                errorStr = "Failed to validate mandatory header Cseq";
            }

            header = message.getRawHeader(Header.VIA);

            if ((header == null) || (header.getValue() == null) ||
                    (header.getValue().length() == 0)) {
                errorStr = "Failed to validate mandatory header Via";
            }

            header = message.getRawHeader(Header.MAX_FORWARDS);

            if (((header == null) || (header.getValue() == null)) &&
                    (message.getMessageType() == SipServletMessageImpl.SipMessageType.SipRequest)) {
                Header mf = Header.createFormated(Header.MAX_FORWARDS, message);
                mf.setValue("70", false);
                message.setHeader(mf);

                if (log.isLoggable(Level.INFO)) {
                    log.log(Level.INFO, "Added Max-Forwards = " + 70);
                }
            }

            //TODO Move to separate Layer
            /*for(MandatoryHeaderValidator validator : mandatoryHeaderValidators ){
                    if(!validator.validate(message)){
                            errorStr = "Failed to parse mandatory header \'" + validator.getHeaderName() + "\'" ;
                            if(errorHandler!=null) {
                        errorHandler.handleError(((SipServletRequestImpl)(message)),400,errorStr);
                            }
                            if (errorStr != null && log.isLoggable(Level.WARNING) )
                    {
                       log.log(Level.WARNING, errorStr + " \r\n" + message);
                    }
                    return null;
                    }
            }*/

            // validate mandatory header Max-Forwards";
            // TODO - Create max fw = 70 maybe not here
            if (errorStr != null) {
                if (log.isLoggable(Level.FINE)) {
                    log.log(Level.FINE, errorStr + " \r\n" + message);
                }

                return null;
            }

            if (log.isLoggable(Level.FINE)) {
                endTime = System.nanoTime();
                log.log(Level.FINE,
                    "Headers = " + ((endTime - startTime) / 1000) + "�s");
            }

            int contentLength = message.getContentLengthHeader();

            if (contentLength < 0) { // No Content-Length

                if (message.getRemote().getProtocol() == SipTransports.UDP_PROT) {
                    contentLength = bb.remaining();

                    if (log.isLoggable(Level.FINE)) {
                        log.log(Level.FINE, "Assigning missing Content-Length");
                    }

                    // decision by stoffe to insert Content-Length header if not
                    // previously present
                    message.internalSetContentLength(contentLength);
                } else {
                    if (log.isLoggable(Level.FINE)) {
                        log.log(Level.FINE,
                            "Content length is mandatory for TCP!");
                    }

                    return null;
                }
            }

            boolean contentComplete = false;

            if (contentLength == 0) {
                contentComplete = true;
            }

            if (contentComplete == false) {
                int position = bb.position();
                int remaining = bb.remaining();
                int bytesToCopy;
                int remainingContent = (contentLength -
                    message.getContentOffset());

                if (remainingContent < remaining) {
                    bytesToCopy = remainingContent;
                } else {
                    bytesToCopy = remaining;
                }

                if (bytesToCopy > 0) {
                    if (log.isLoggable(Level.FINE)) {
                        log.log(Level.FINE, "Copy content bytes:" +
                            bytesToCopy);
                    }

                    message.addContent(bs, position + offset, bytesToCopy);
                    remainingContent = (contentLength -
                        message.getContentOffset());

                    if (remainingContent == 0) {
                        contentComplete = true;
                    }

                    if (log.isLoggable(Level.FINE)) {
                        if (remainingContent != 0) {
                            log.log(Level.FINE,
                                "Content remaining after this chunk:" +
                                remainingContent);
                        }
                    }

                    bb.position(position + bytesToCopy);
                }
            }

            if (contentComplete) {
                if (log.isLoggable(Level.FINE)) {
                    log.log(Level.FINE,
                        "Message content is complete. --> Message is complete took : ");
                }

                message.setMessageComplete(true);

                if (log.isLoggable(Level.FINE)) {
                    endTime = System.nanoTime();
                    log.log(Level.FINE,
                        "Total = " + ((endTime - startTime) / 1000) + "�s");
                }
            }
        }

        return message;
    }
}
