text: /**
   * @fileoverview SIP Message
   */
  
  (function(SIP) {
  var
    OutgoingRequest,
    IncomingMessage,
    IncomingRequest,
    IncomingResponse;
  
  /**
   * @augments SIP
   * @class Class for outgoing SIP request.
   * @param {String} method request method
   * @param {String} ruri request uri
   * @param {SIP.UA} ua
   * @param {Object} params parameters that will have priority over ua.configuration parameters:
   * <br>
   *  - cseq, call_id, from_tag, from_uri, from_displayName, to_uri, to_tag, route_set
   * @param {Object} [headers] extra headers
   * @param {String} [body]
   */
  OutgoingRequest = function(method, ruri, ua, params, extraHeaders, body) {
    var
      to,
      from,
      call_id,
      cseq;
  
    params = params || {};
  
    // Mandatory parameters check
    if(!method || !ruri || !ua) {
      return null;
    }
  
    this.logger = ua.getLogger('sip.sipmessage');
    this.ua = ua;
    this.headers = {};
    this.method = method;
    this.ruri = ruri;
    this.body = body;
    this.extraHeaders = extraHeaders || [];
    this.statusCode = params.status_code;
    this.reasonPhrase = params.reason_phrase;
  
    // Fill the Common SIP Request Headers
  
    // Route
    if (params.route_set) {
      this.setHeader('route', params.route_set);
    } else if (ua.configuration.usePreloadedRoute){
      this.setHeader('route', ua.transport.server.sip_uri);
    }
  
    // Via
    // Empty Via header. Will be filled by the client transaction.
    this.setHeader('via', '');
  
    // Max-Forwards
    this.setHeader('max-forwards', SIP.UA.C.MAX_FORWARDS);
  
    // To
    to = (params.to_displayName || params.to_displayName === 0) ? '"' + params.to_displayName + '" ' : '';
    to += '<' + (params.to_uri || ruri) + '>';
    to += params.to_tag ? ';tag=' + params.to_tag : '';
    this.to = new SIP.NameAddrHeader.parse(to);
    this.setHeader('to', to);
  
    // From
    if (params.from_displayName || params.from_displayName === 0) {
      from = '"' + params.from_displayName + '" ';
    } else if (ua.configuration.displayName) {
      from = '"' + ua.configuration.displayName + '" ';
    } else {
      from = '';
    }
    from += '<' + (params.from_uri || ua.configuration.uri) + '>;tag=';
    from += params.from_tag || SIP.Utils.newTag();
    this.from = new SIP.NameAddrHeader.parse(from);
    this.setHeader('from', from);
  
    // Call-ID
    call_id = params.call_id || (ua.configuration.sipjsId + SIP.Utils.createRandomToken(15));
    this.call_id = call_id;
    this.setHeader('call-id', call_id);
  
    // CSeq
    cseq = params.cseq || Math.floor(Math.random() * 10000);
    this.cseq = cseq;
    this.setHeader('cseq', cseq + ' ' + method);
  };
  
  OutgoingRequest.prototype = {
    /**
     * Replace the the given header by the given value.
     * @param {String} name header name
     * @param {String | Array} value header value
     */
    setHeader: function(name, value) {
      this.headers[SIP.Utils.headerize(name)] = (value instanceof Array) ? value : [value];
    },
  
    /**
     * Get the value of the given header name at the given position.
     * @param {String} name header name
     * @returns {String|undefined} Returns the specified header, undefined if header doesn't exist.
     */
    getHeader: function(name) {
      var regexp, idx,
        length = this.extraHeaders.length,
        header = this.headers[SIP.Utils.headerize(name)];
  
      if(header) {
        if(header[0]) {
          return header[0];
        }
      } else {
        regexp = new RegExp('^\\s*' + name + '\\s*:','i');
        for (idx = 0; idx < length; idx++) {
          header = this.extraHeaders[idx];
          if (regexp.test(header)) {
            return header.substring(header.indexOf(':')+1).trim();
          }
        }
      }
  
      return;
    },
  
    /**
     * Get the header/s of the given name.
     * @param {String} name header name
     * @returns {Array} Array with all the headers of the specified name.
     */
    getHeaders: function(name) {
      var idx, length, regexp,
        header = this.headers[SIP.Utils.headerize(name)],
        result = [];
  
      if(header) {
        length = header.length;
        for (idx = 0; idx < length; idx++) {
          result.push(header[idx]);
        }
        return result;
      } else {
        length = this.extraHeaders.length;
        regexp = new RegExp('^\\s*' + name + '\\s*:','i');
        for (idx = 0; idx < length; idx++) {
          header = this.extraHeaders[idx];
          if (regexp.test(header)) {
            result.push(header.substring(header.indexOf(':')+1).trim());
          }
        }
        return result;
      }
    },
  
    /**
     * Verify the existence of the given header.
     * @param {String} name header name
     * @returns {boolean} true if header with given name exists, false otherwise
     */
    hasHeader: function(name) {
      var regexp, idx,
        length = this.extraHeaders.length;
  
      if (this.headers[SIP.Utils.headerize(name)]) {
        return true;
      } else {
        regexp = new RegExp('^\\s*' + name + '\\s*:','i');
        for (idx = 0; idx < length; idx++) {
          if (regexp.test(this.extraHeaders[idx])) {
            return true;
          }
        }
      }
  
      return false;
    },
  
    toString: function() {
      var msg = '', header, length, idx, supported = [];
  
      msg += this.method + ' ' + this.ruri + ' SIP/2.0\r\n';
  
      for (header in this.headers) {
        length = this.headers[header].length;
        for (idx = 0; idx < length; idx++) {
          msg += header + ': ' + this.headers[header][idx] + '\r\n';
        }
      }
  
      length = this.extraHeaders.length;
      for (idx = 0; idx < length; idx++) {
        msg += this.extraHeaders[idx].trim() +'\r\n';
      }
  
      //Supported
      if (this.method === SIP.C.REGISTER) {
        supported.push('path', 'gruu');
      } else if (this.method === SIP.C.INVITE && 
                 (this.ua.contact.pub_gruu || this.ua.contact.temp_gruu)) {
        supported.push('gruu');
      }
  
      if (this.ua.configuration.rel100 === 'supported') {
        supported.push('100rel');
      }
  
      supported.push('outbound');
  
      msg += 'Supported: ' +  supported +'\r\n';
      msg += 'User-Agent: ' + this.ua.configuration.userAgentString +'\r\n';
  
      if(this.body) {
        length = SIP.Utils.str_utf8_length(this.body);
        msg += 'Content-Length: ' + length + '\r\n\r\n';
        msg += this.body;
      } else {
        msg += 'Content-Length: 0\r\n\r\n';
      }
  
      return msg;
    }
  };
  
  /**
   * @augments SIP
   * @class Class for incoming SIP message.
   */
  IncomingMessage = function(){
    this.data = null;
    this.headers = null;
    this.method =  null;
    this.via = null;
    this.via_branch = null;
    this.call_id = null;
    this.cseq = null;
    this.from = null;
    this.from_tag = null;
    this.to = null;
    this.to_tag = null;
    this.body = null;
  };
  
  IncomingMessage.prototype = {
    /**
    * Insert a header of the given name and value into the last position of the
    * header array.
    * @param {String} name header name
    * @param {String} value header value
    */
    addHeader: function(name, value) {
      var header = { raw: value };
  
      name = SIP.Utils.headerize(name);
  
      if(this.headers[name]) {
        this.headers[name].push(header);
      } else {
        this.headers[name] = [header];
      }
    },
  
    /**
     * Get the value of the given header name at the given position.
     * @param {String} name header name
     * @returns {String|undefined} Returns the specified header, null if header doesn't exist.
     */
    getHeader: function(name) {
      var header = this.headers[SIP.Utils.headerize(name)];
  
      if(header) {
        if(header[0]) {
          return header[0].raw;
        }
      } else {
        return;
      }
    },
  
    /**
     * Get the header/s of the given name.
     * @param {String} name header name
     * @returns {Array} Array with all the headers of the specified name.
     */
    getHeaders: function(name) {
      var idx, length,
        header = this.headers[SIP.Utils.headerize(name)],
        result = [];
  
      if(!header) {
        return [];
      }
  
      length = header.length;
      for (idx = 0; idx < length; idx++) {
        result.push(header[idx].raw);
      }
  
      return result;
    },
  
    /**
     * Verify the existence of the given header.
     * @param {String} name header name
     * @returns {boolean} true if header with given name exists, false otherwise
     */
    hasHeader: function(name) {
      return(this.headers[SIP.Utils.headerize(name)]) ? true : false;
    },
  
    /**
    * Parse the given header on the given index.
    * @param {String} name header name
    * @param {Number} [idx=0] header index
    * @returns {Object|undefined} Parsed header object, undefined if the header is not present or in case of a parsing error.
    */
    parseHeader: function(name, idx) {
      var header, value, parsed;
  
      name = SIP.Utils.headerize(name);
  
      idx = idx || 0;
  
      if(!this.headers[name]) {
        this.logger.log('header "' + name + '" not present');
        return;
      } else if(idx >= this.headers[name].length) {
        this.logger.log('not so many "' + name + '" headers present');
        return;
      }
  
      header = this.headers[name][idx];
      value = header.raw;
  
      if(header.parsed) {
        return header.parsed;
      }
  
      //substitute '-' by '_' for grammar rule matching.
      parsed = SIP.Grammar.parse(value, name.replace(/-/g, '_'));
  
      if(parsed === -1) {
        this.headers[name].splice(idx, 1); //delete from headers
        this.logger.warn('error parsing "' + name + '" header field with value "' + value + '"');
        return;
      } else {
        header.parsed = parsed;
        return parsed;
      }
    },
  
    /**
     * Message Header attribute selector. Alias of parseHeader.
     * @param {String} name header name
     * @param {Number} [idx=0] header index
     * @returns {Object|undefined} Parsed header object, undefined if the header is not present or in case of a parsing error.
     *
     * @example
     * message.s('via',3).port
     */
    s: function(name, idx) {
      return this.parseHeader(name, idx);
    },
  
    /**
    * Replace the value of the given header by the value.
    * @param {String} name header name
    * @param {String} value header value
    */
    setHeader: function(name, value) {
      var header = { raw: value };
      this.headers[SIP.Utils.headerize(name)] = [header];
    },
  
    toString: function() {
      return this.data;
    }
  };
  
  /**
   * @augments IncomingMessage
   * @class Class for incoming SIP request.
   */
  IncomingRequest = function(ua) {
    this.logger = ua.getLogger('sip.sipmessage');
    this.ua = ua;
    this.headers = {};
    this.ruri = null;
    this.transport = null;
    this.server_transaction = null;
  };
  IncomingRequest.prototype = new IncomingMessage();
  
  /**
  * Stateful reply.
  * @param {Number} code status code
  * @param {String} reason reason phrase
  * @param {Object} headers extra headers
  * @param {String} body body
  * @param {Function} [onSuccess] onSuccess callback
  * @param {Function} [onFailure] onFailure callback
  */
  IncomingRequest.prototype.reply = function(code, reason, extraHeaders, body, onSuccess, onFailure) {
    var rr, vias, length, idx, response,
    supported = [],
      to = this.getHeader('To'),
      r = 0,
      v = 0;
  
    code = code || null;
    reason = reason || null;
  
    // Validate code and reason values
    if (!code || (code < 100 || code > 699)) {
      throw new TypeError('Invalid status_code: '+ code);
    } else if (reason && typeof reason !== 'string' && !(reason instanceof String)) {
      throw new TypeError('Invalid reason_phrase: '+ reason);
    }
  
    reason = reason || SIP.C.REASON_PHRASE[code] || '';
    extraHeaders = extraHeaders || [];
  
    response = 'SIP/2.0 ' + code + ' ' + reason + '\r\n';
  
    if(this.method === SIP.C.INVITE && code > 100 && code <= 200) {
      rr = this.getHeaders('record-route');
      length = rr.length;
  
      for(r; r < length; r++) {
        response += 'Record-Route: ' + rr[r] + '\r\n';
      }
    }
  
    vias = this.getHeaders('via');
    length = vias.length;
  
    for(v; v < length; v++) {
      response += 'Via: ' + vias[v] + '\r\n';
    }
  
    if(!this.to_tag && code > 100) {
      to += ';tag=' + SIP.Utils.newTag();
    } else if(this.to_tag && !this.s('to').hasParam('tag')) {
      to += ';tag=' + this.to_tag;
    }
  
    response += 'To: ' + to + '\r\n';
    response += 'From: ' + this.getHeader('From') + '\r\n';
    response += 'Call-ID: ' + this.call_id + '\r\n';
    response += 'CSeq: ' + this.cseq + ' ' + this.method + '\r\n';
  
    length = extraHeaders.length;
    for (idx = 0; idx < length; idx++) {
      response += extraHeaders[idx].trim() +'\r\n';
    }
  
    //Supported
    if (this.method === SIP.C.INVITE && 
                 (this.ua.contact.pub_gruu || this.ua.contact.temp_gruu)) {
      supported.push('gruu');
    }
  
    if (this.ua.configuration.rel100 === 'supported') {
      supported.push('100rel');
    }
  
    supported.push('outbound');
  
    response += 'Supported: ' + supported + '\r\n';
  
    if(body) {
      length = SIP.Utils.str_utf8_length(body);
      response += 'Content-Type: application/sdp\r\n';
      response += 'Content-Length: ' + length + '\r\n\r\n';
      response += body;
    } else {
      response += 'Content-Length: ' + 0 + '\r\n\r\n';
    }
  
    this.server_transaction.receiveResponse(code, response, onSuccess, onFailure);
  
    return response;
  };
  
  /**
  * Stateless reply.
  * @param {Number} code status code
  * @param {String} reason reason phrase
  */
  IncomingRequest.prototype.reply_sl = function(code, reason) {
    var to, response,
      v = 0,
      vias = this.getHeaders('via'),
      length = vias.length;
  
    code = code || null;
    reason = reason || null;
  
    // Validate code and reason values
    if (!code || (code < 100 || code > 699)) {
      throw new TypeError('Invalid status_code: '+ code);
    } else if (reason && typeof reason !== 'string' && !(reason instanceof String)) {
      throw new TypeError('Invalid reason_phrase: '+ reason);
    }
  
    reason = reason || SIP.C.REASON_PHRASE[code] || '';
  
    response = 'SIP/2.0 ' + code + ' ' + reason + '\r\n';
  
    for(v; v < length; v++) {
      response += 'Via: ' + vias[v] + '\r\n';
    }
  
    to = this.getHeader('To');
  
    if(!this.to_tag && code > 100) {
      to += ';tag=' + SIP.Utils.newTag();
    } else if(this.to_tag && !this.s('to').hasParam('tag')) {
      to += ';tag=' + this.to_tag;
    }
  
    response += 'To: ' + to + '\r\n';
    response += 'From: ' + this.getHeader('From') + '\r\n';
    response += 'Call-ID: ' + this.call_id + '\r\n';
    response += 'CSeq: ' + this.cseq + ' ' + this.method + '\r\n';
    response += 'Content-Length: ' + 0 + '\r\n\r\n';
  
    this.transport.send(response);
  };
  
  
  /**
   * @augments IncomingMessage
   * @class Class for incoming SIP response.
   */
  IncomingResponse = function(ua) {
    this.logger = ua.getLogger('sip.sipmessage');
    this.headers = {};
    this.status_code = null;
    this.reason_phrase = null;
  };
  IncomingResponse.prototype = new IncomingMessage();
  
  SIP.OutgoingRequest = OutgoingRequest;
  SIP.IncomingRequest = IncomingRequest;
  SIP.IncomingResponse = IncomingResponse;
  }(SIP));
  