text: /* Copyright 2015-present Samsung Electronics Co., Ltd. and other contributors
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  
  var stream = require('stream');
  var util = require('util');
  var Stream = stream.Stream;
  var Duplex = stream.Duplex;
  
  var defaultHighWaterMark = 128;
  
  
  function WriteReq(chunk, callback) {
    this.chunk = chunk;
    this.callback = callback;
  }
  
  
  function WritableState(options) {
    // buffer of WriteReq
    this.buffer = [];
  
    // total length of messages not flushed yet.
    this.length = 0;
  
    // high water mark.
    // The point where write() starts retuning false.
    this.highWaterMark = (options && util.isNumber(options.highWaterMark)) ?
      options.highWaterMark : defaultHighWaterMark;
  
    // 'true' if stream is ready to write.
    this.ready = false;
  
    // `true` if stream is writing down data underlying system.
    this.writing = false;
  
    // the length of message being writing.
    this.writingLength = 0;
  
    // turn 'true' when some messages are buffered. After buffered messages are
    // all sent, 'drain' event will be emitted.
    this.needDrain = false;
  
    // become `true` when `end()` called.
    this.ending = false;
  
    // become `true` when there are no date to write.
    this.ended = false;
  }
  
  
  function Writable(options) {
    if (!(this instanceof Writable) && !(this instanceof stream.Duplex)) {
      return new Writable(options);
    }
  
    this._writableState = new WritableState(options);
  
    Stream.call(this);
  }
  
  
  util.inherits(Writable, Stream);
  
  
  // Write chunk of data to underlying system stream.
  //  Control flow in general description
  //    Writable.prototype.write()
  //      ||
  //    writeOrBuffer()
  //      ||
  //    doWrite()
  //      ||
  //    ConcreteStream.prototype._write()
  //      |
  //    underlying stream
  //      |
  //    Writable.prototype._onwrite()
  Writable.prototype.write = function(chunk, callback) {
    var state = this._writableState;
  
    if (state.ended) {
      writeAfterEnd(this, callback);
      return false;
    }
  
    return writeOrBuffer(this, chunk, callback);
  };
  
  
  // This function object never to be called. concrete stream should override
  // this method.
  Writable.prototype._write = function(chunk, callback, onwrite) {
    throw new Error('unreachable');
  }
  
  
  Writable.prototype.end = function(chunk, callback) {
    var state = this._writableState;
  
    // Because NuttX cannot poll 'EOF',so forcely raise EOF event.
    if (process.platform == 'nuttx') {
      if (!state.ending) {
        if (util.isNullOrUndefined(chunk)) {
          chunk = '\\e\\n\\d';
        } else {
          chunk += '\\e\\n\\d';
        }
      }
    }
  
    if (!util.isNullOrUndefined(chunk)) {
      this.write(chunk);
    }
  
    if (!state.ending) {
      endWritable(this, callback);
    }
  };
  
  
  // When stream is ready to write, concrete stream implementation should call
  // this method to inform it.
  Writable.prototype._readyToWrite = function() {
    var state = this._writableState;
  
    state.ready = true;
    writeBuffered(this);
  };
  
  
  // A chunk of data has been written down to stream.
  Writable.prototype._onwrite = function() {
    var state = this._writableState;
  
    state.length -= state.writingLength;
  
    state.writing = false;
    state.writingLength = 0;
  
    writeBuffered(this);
  };
  
  
  // A write call occured after end.
  function writeAfterEnd(stream, callback) {
    var err = new Error('write after end');
    stream.emit('error', err);
    if (util.isFunction(callback)) {
      process.nextTick(callback.bind(undefined, err));
    }
  }
  
  
  function writeOrBuffer(stream, chunk, callback) {
    var state = stream._writableState;
  
    if (util.isString(chunk)) {
      chunk = new Buffer(chunk);
    }
  
    state.length += chunk.length;
  
    if (!state.ready || state.writing || state.buffer.length > 0) {
      // stream not yet ready or there is pending request to write.
      // push this request into write queue.
      state.buffer.push(new WriteReq(chunk, callback));
    } else {
      // here means there is no pending data. write out.
      doWrite(stream, chunk, callback);
    }
  
    // total length of buffered message exceeded high water mark.
    if (state.length >= state.highWaterMark) {
      state.needDrain = true;
    }
  
    return !state.needDrain;
  }
  
  
  function writeBuffered(stream) {
    var state = stream._writableState;
    if (!state.writing) {
      if (state.buffer.length == 0) {
        onEmptyBuffer(stream);
      } else {
        var req = state.buffer.shift();
        doWrite(stream, req.chunk, req.callback);
      }
    }
  }
  
  
  function doWrite(stream, chunk, callback) {
    var state = stream._writableState;
  
    if (state.writing) {
      return new Error('write during writing');
    }
  
    // The stream is now writing.
    state.writing = true;
    state.writingLength = chunk.length;
  
    // Write down the chunk data.
    stream._write(chunk, callback, stream._onwrite.bind(stream));
  }
  
  
  // No more data to write. if this stream is being finishing, emit 'finish'.
  function onEmptyBuffer(stream) {
    var state = stream._writableState;
    if (state.ending) {
      emitFinish(stream);
    } else if (state.needDrain) {
      emitDrain(stream);
    }
  }
  
  
  // Writable.prototype.end() was called. register callback for 'finish' event.
  // After finish writing out buffered data, 'finish' event will be fired.
  function endWritable(stream, callback) {
    var state = stream._writableState;
    state.ending = true;
    if (callback) {
      stream.once('finish', callback);
    }
  
    // If nothing left, emit finish event at next tick.
    if (!state.writing && state.buffer.length == 0) {
      process.nextTick(emitFinish.bind(undefined, stream));
    }
  }
  
  
  // Emit 'drain' event
  function emitDrain(stream) {
    var state = stream._writableState;
    if (state.needDrain) {
      state.needDrain = false;
      stream.emit('drain');
    }
  }
  
  
  // Emit 'finish' event to notify this stream is finished.
  function emitFinish(stream) {
    var state = stream._writableState;
    if (!state.ended) {
      state.ended = true;
      stream.emit('finish');
    }
  }
  
  
  module.exports = Writable;
  