/* */ 
(function(Buffer) {
  var Buffer = require('buffer').Buffer,
      s = 0,
      S = {
        PARSER_UNINITIALIZED: s++,
        START: s++,
        START_BOUNDARY: s++,
        HEADER_FIELD_START: s++,
        HEADER_FIELD: s++,
        HEADER_VALUE_START: s++,
        HEADER_VALUE: s++,
        HEADER_VALUE_ALMOST_DONE: s++,
        HEADERS_ALMOST_DONE: s++,
        PART_DATA_START: s++,
        PART_DATA: s++,
        PART_END: s++,
        END: s++
      },
      f = 1,
      F = {
        PART_BOUNDARY: f,
        LAST_BOUNDARY: f *= 2
      },
      LF = 10,
      CR = 13,
      SPACE = 32,
      HYPHEN = 45,
      COLON = 58,
      A = 97,
      Z = 122,
      lower = function(c) {
        return c | 0x20;
      };
  for (s in S) {
    exports[s] = S[s];
  }
  function MultipartParser() {
    this.boundary = null;
    this.boundaryChars = null;
    this.lookbehind = null;
    this.state = S.PARSER_UNINITIALIZED;
    this.index = null;
    this.flags = 0;
  }
  ;
  exports.MultipartParser = MultipartParser;
  MultipartParser.stateToString = function(stateNumber) {
    for (var state in S) {
      var number = S[state];
      if (number === stateNumber)
        return state;
    }
  };
  MultipartParser.prototype.initWithBoundary = function(str) {
    this.boundary = new Buffer(str.length + 4);
    this.boundary.write('\r\n--', 'ascii', 0);
    this.boundary.write(str, 'ascii', 4);
    this.lookbehind = new Buffer(this.boundary.length + 8);
    this.state = S.START;
    this.boundaryChars = {};
    for (var i = 0; i < this.boundary.length; i++) {
      this.boundaryChars[this.boundary[i]] = true;
    }
  };
  MultipartParser.prototype.write = function(buffer) {
    var self = this,
        i = 0,
        len = buffer.length,
        prevIndex = this.index,
        index = this.index,
        state = this.state,
        flags = this.flags,
        lookbehind = this.lookbehind,
        boundary = this.boundary,
        boundaryChars = this.boundaryChars,
        boundaryLength = this.boundary.length,
        boundaryEnd = boundaryLength - 1,
        bufferLength = buffer.length,
        c,
        cl,
        mark = function(name) {
          self[name + 'Mark'] = i;
        },
        clear = function(name) {
          delete self[name + 'Mark'];
        },
        callback = function(name, buffer, start, end) {
          if (start !== undefined && start === end) {
            return;
          }
          var callbackSymbol = 'on' + name.substr(0, 1).toUpperCase() + name.substr(1);
          if (callbackSymbol in self) {
            self[callbackSymbol](buffer, start, end);
          }
        },
        dataCallback = function(name, clear) {
          var markSymbol = name + 'Mark';
          if (!(markSymbol in self)) {
            return;
          }
          if (!clear) {
            callback(name, buffer, self[markSymbol], buffer.length);
            self[markSymbol] = 0;
          } else {
            callback(name, buffer, self[markSymbol], i);
            delete self[markSymbol];
          }
        };
    for (i = 0; i < len; i++) {
      c = buffer[i];
      switch (state) {
        case S.PARSER_UNINITIALIZED:
          return i;
        case S.START:
          index = 0;
          state = S.START_BOUNDARY;
        case S.START_BOUNDARY:
          if (index == boundary.length - 2) {
            if (c != CR) {
              return i;
            }
            index++;
            break;
          } else if (index - 1 == boundary.length - 2) {
            if (c != LF) {
              return i;
            }
            index = 0;
            callback('partBegin');
            state = S.HEADER_FIELD_START;
            break;
          }
          if (c != boundary[index + 2]) {
            index = -2;
          }
          if (c == boundary[index + 2]) {
            index++;
          }
          break;
        case S.HEADER_FIELD_START:
          state = S.HEADER_FIELD;
          mark('headerField');
          index = 0;
        case S.HEADER_FIELD:
          if (c == CR) {
            clear('headerField');
            state = S.HEADERS_ALMOST_DONE;
            break;
          }
          index++;
          if (c == HYPHEN) {
            break;
          }
          if (c == COLON) {
            if (index == 1) {
              return i;
            }
            dataCallback('headerField', true);
            state = S.HEADER_VALUE_START;
            break;
          }
          cl = lower(c);
          if (cl < A || cl > Z) {
            return i;
          }
          break;
        case S.HEADER_VALUE_START:
          if (c == SPACE) {
            break;
          }
          mark('headerValue');
          state = S.HEADER_VALUE;
        case S.HEADER_VALUE:
          if (c == CR) {
            dataCallback('headerValue', true);
            callback('headerEnd');
            state = S.HEADER_VALUE_ALMOST_DONE;
          }
          break;
        case S.HEADER_VALUE_ALMOST_DONE:
          if (c != LF) {
            return i;
          }
          state = S.HEADER_FIELD_START;
          break;
        case S.HEADERS_ALMOST_DONE:
          if (c != LF) {
            return i;
          }
          callback('headersEnd');
          state = S.PART_DATA_START;
          break;
        case S.PART_DATA_START:
          state = S.PART_DATA;
          mark('partData');
        case S.PART_DATA:
          prevIndex = index;
          if (index == 0) {
            i += boundaryEnd;
            while (i < bufferLength && !(buffer[i] in boundaryChars)) {
              i += boundaryLength;
            }
            i -= boundaryEnd;
            c = buffer[i];
          }
          if (index < boundary.length) {
            if (boundary[index] == c) {
              if (index == 0) {
                dataCallback('partData', true);
              }
              index++;
            } else {
              index = 0;
            }
          } else if (index == boundary.length) {
            index++;
            if (c == CR) {
              flags |= F.PART_BOUNDARY;
            } else if (c == HYPHEN) {
              flags |= F.LAST_BOUNDARY;
            } else {
              index = 0;
            }
          } else if (index - 1 == boundary.length) {
            if (flags & F.PART_BOUNDARY) {
              index = 0;
              if (c == LF) {
                flags &= ~F.PART_BOUNDARY;
                callback('partEnd');
                callback('partBegin');
                state = S.HEADER_FIELD_START;
                break;
              }
            } else if (flags & F.LAST_BOUNDARY) {
              if (c == HYPHEN) {
                callback('partEnd');
                callback('end');
                state = S.END;
              } else {
                index = 0;
              }
            } else {
              index = 0;
            }
          }
          if (index > 0) {
            lookbehind[index - 1] = c;
          } else if (prevIndex > 0) {
            callback('partData', lookbehind, 0, prevIndex);
            prevIndex = 0;
            mark('partData');
            i--;
          }
          break;
        case S.END:
          break;
        default:
          return i;
      }
    }
    dataCallback('headerField');
    dataCallback('headerValue');
    dataCallback('partData');
    this.index = index;
    this.state = state;
    this.flags = flags;
    return len;
  };
  MultipartParser.prototype.end = function() {
    var callback = function(self, name) {
      var callbackSymbol = 'on' + name.substr(0, 1).toUpperCase() + name.substr(1);
      if (callbackSymbol in self) {
        self[callbackSymbol]();
      }
    };
    if ((this.state == S.HEADER_FIELD_START && this.index == 0) || (this.state == S.PART_DATA && this.index == this.boundary.length)) {
      callback(this, 'partEnd');
      callback(this, 'end');
    } else if (this.state != S.END) {
      return new Error('MultipartParser.end(): stream ended unexpectedly: ' + this.explain());
    }
  };
  MultipartParser.prototype.explain = function() {
    return 'state = ' + MultipartParser.stateToString(this.state);
  };
})(require('buffer').Buffer);
