var isString = function(type) {
    if (type === C.TYPE_ASCII || type === C.TYPE_HEX) {
        return true;
    } else {
        return false;
    }
};

calcLength = function(type, value) {
    var size = NaN;
    switch (type) {
    case C.TYPE_HEX : size = Buffer.byteLength(value, 'hex'); break;
    case C.TYPE_ASCII : size = Buffer.byteLength(value, 'ascii'); break;
    case C.TYPE_UINT8 : size = 1; break;
    case C.TYPE_UINT16 : size = 2; break;
    case C.TYPE_UINT32 : size = 4; break;
    case C.TYPE_FLOAT : size = 4; break;
    case C.TYPE_DOUBLE : size = 8; break;
    case C.TYPE_INT8 : size = 1; break;
    case C.TYPE_INT16 : size = 2; break;
    case C.TYPE_INT32 : size = 4; break;    
    default :break;
}
    return size;
};

var RWStream = function() {
    this.endian = C.BIG_ENDIAN;
};

RWStream.prototype.setEndian = function(endian) {   
    this.endian = endian;
};

RWStream.prototype.getEncoding = function(type) {
    return RWStream.encodings[type];
};

RWStream.prototype.getWriteType = function(type) {
    return RWStream.writes[this.endian][type];
};

RWStream.prototype.getReadType = function(type) {
    return RWStream.reads[this.endian][type];
};  

WriteStream = function() {
    RWStream.call(this);
    this.defaultBufferSize = 512; //512 bytes
    this.rawBuffer = new Buffer(this.defaultBufferSize);
    this.offset = 0;
    this.contentSize = 0;
};

util.inherits(WriteStream, RWStream);

WriteStream.prototype.increment = function(add) {
    this.offset += add;
    if (this.offset > this.contentSize) {
        this.contentSize = this.offset;
    }
};

WriteStream.prototype.size = function() {
    return this.contentSize;
};

WriteStream.prototype.skip = function(amount) {
    this.increment(amount);
};

WriteStream.prototype.checkSize = function(length) {
    if (this.offset + length > this.rawBuffer.length) {
        // we need more size, copying old one to new buffer
        var oldLength = this.rawBuffer.length, 
            newBuffer = new Buffer(oldLength + length + (oldLength / 2));
        this.rawBuffer.copy(newBuffer, 0, 0, this.contentSize);
        this.rawBuffer = newBuffer;
    }
};

WriteStream.prototype.writeToBuffer = function(type, value, length) {
    if (value === '' || value === null) return;
  
    this.checkSize(length);
    this.rawBuffer[this.getWriteType(type)](value, this.offset);
    this.increment(length);
};

WriteStream.prototype.writeRawBuffer = function(source, start, length) {
    if (!source) return;
    this.checkSize(length);
    source.copy(this.rawBuffer, this.offset, start, length);
    this.increment(length);
};

WriteStream.prototype.write = function(type, value) {
    if (isString(type)) {
        this.writeString(value, type);
    } else {
        this.writeToBuffer(type, value, calcLength(type));
    }
};

WriteStream.prototype.writeString = function(string, type) {
    var encoding = this.getEncoding(type),
   length = Buffer.byteLength(string, encoding);
    this.rawBuffer.write(string, this.offset, length, encoding);
    this.increment(length);
};

WriteStream.prototype.buffer = function() {
    return this.rawBuffer.slice(0, this.contentSize);
};

WriteStream.prototype.toReadBuffer = function() {
    return new ReadStream(this.buffer());
};

WriteStream.prototype.concat = function(newStream) {
    var newSize = this.size() + newStream.size();
    this.rawBuffer = Buffer.concat([this.buffer(), newStream.buffer()], newSize);
    this.contentSize = newSize;
    this.offset = newSize;
};

ReadStream = function(buffer) {
    RWStream.call(this);
    this.rawBuffer = buffer;
    this.offset = 0;
};

util.inherits(ReadStream, RWStream);

ReadStream.prototype.size = function() {
    return this.rawBuffer.length;
};

ReadStream.prototype.increment = function(add) {
    this.offset += add;
};  

ReadStream.prototype.more = function(length) {
    var newBuf = this.rawBuffer.slice(this.offset, this.offset + length);
    this.increment(length);
    return new ReadStream(newBuf);
};

ReadStream.prototype.reset = function() {
    this.offset = 0;
    return this;
};

ReadStream.prototype.end = function() {
    return this.offset >= this.size();
};

ReadStream.prototype.readFromBuffer = function(type, length) {
    //this.checkSize(length);
    //if (this.offset + length > this.rawBuffer.length) throw ("out of bound " + this.offset + "," + length + "," + this.rawBuffer.length);
    var value = this.rawBuffer[this.getReadType(type)](this.offset);
    this.increment(length);
    return value;
};  

ReadStream.prototype.read = function(type, length) {
    var value = null;
    if (isString(type)) {
        value = this.readString(length, type);
    } else {
        value = this.readFromBuffer(type, calcLength(type));
    }

    return value;
};

ReadStream.prototype.readString = function(length, type) {
    var encoding = this.getEncoding(type), 
        str = this.rawBuffer.toString(encoding, this.offset, this.offset + length);
    this.increment(length);
    return str;
};  

ReadStream.prototype.buffer = function() {
    return this.rawBuffer;
};

ReadStream.prototype.concat = function(newStream) {
    var newSize = this.size() + newStream.size();
    this.rawBuffer = Buffer.concat([this.buffer(), newStream.buffer()], newSize);
    this.contentSize = newSize;
    this.offset = newSize;
};  

RWStream.writes = {};
RWStream.writes[C.BIG_ENDIAN] = {};
RWStream.writes[C.BIG_ENDIAN][C.TYPE_UINT8] = 'writeUInt8';
RWStream.writes[C.BIG_ENDIAN][C.TYPE_UINT16] = 'writeUInt16BE';
RWStream.writes[C.BIG_ENDIAN][C.TYPE_UINT32] = 'writeUInt32BE';
RWStream.writes[C.BIG_ENDIAN][C.TYPE_INT8] = 'writeInt8';
RWStream.writes[C.BIG_ENDIAN][C.TYPE_INT16] = 'writeInt16BE';
RWStream.writes[C.BIG_ENDIAN][C.TYPE_INT32] = 'writeInt32BE';
RWStream.writes[C.BIG_ENDIAN][C.TYPE_FLOAT] = 'writeFloatBE';
RWStream.writes[C.BIG_ENDIAN][C.TYPE_DOUBLE] = 'writeDoubleBE';

RWStream.writes[C.LITTLE_ENDIAN] = {};
RWStream.writes[C.LITTLE_ENDIAN][C.TYPE_UINT8] = 'writeUInt8';
RWStream.writes[C.LITTLE_ENDIAN][C.TYPE_UINT16] = 'writeUInt16LE';
RWStream.writes[C.LITTLE_ENDIAN][C.TYPE_UINT32] = 'writeUInt32LE';
RWStream.writes[C.LITTLE_ENDIAN][C.TYPE_INT8] = 'writeInt8';
RWStream.writes[C.LITTLE_ENDIAN][C.TYPE_INT16] = 'writeInt16LE';
RWStream.writes[C.LITTLE_ENDIAN][C.TYPE_INT32] = 'writeInt32LE'; 
RWStream.writes[C.LITTLE_ENDIAN][C.TYPE_FLOAT] = 'writeFloatLE';
RWStream.writes[C.LITTLE_ENDIAN][C.TYPE_DOUBLE] = 'writeDoubleLE';

RWStream.reads = {};
RWStream.reads[C.BIG_ENDIAN] = {};
RWStream.reads[C.BIG_ENDIAN][C.TYPE_UINT8] = 'readUInt8';
RWStream.reads[C.BIG_ENDIAN][C.TYPE_UINT16] = 'readUInt16BE';
RWStream.reads[C.BIG_ENDIAN][C.TYPE_UINT32] = 'readUInt32BE';
RWStream.reads[C.BIG_ENDIAN][C.TYPE_INT8] = 'readInt8';
RWStream.reads[C.BIG_ENDIAN][C.TYPE_INT16] = 'readInt16BE';
RWStream.reads[C.BIG_ENDIAN][C.TYPE_INT32] = 'readInt32BE';
RWStream.reads[C.BIG_ENDIAN][C.TYPE_FLOAT] = 'readFloatBE';
RWStream.reads[C.BIG_ENDIAN][C.TYPE_DOUBLE] = 'readDoubleBE';

RWStream.reads[C.LITTLE_ENDIAN] = {};
RWStream.reads[C.LITTLE_ENDIAN][C.TYPE_UINT8] = 'readUInt8';
RWStream.reads[C.LITTLE_ENDIAN][C.TYPE_UINT16] = 'readUInt16LE';
RWStream.reads[C.LITTLE_ENDIAN][C.TYPE_UINT32] = 'readUInt32LE';
RWStream.reads[C.LITTLE_ENDIAN][C.TYPE_INT8] = 'readInt8';
RWStream.reads[C.LITTLE_ENDIAN][C.TYPE_INT16] = 'readInt16LE';
RWStream.reads[C.LITTLE_ENDIAN][C.TYPE_INT32] = 'readInt32LE';
RWStream.reads[C.LITTLE_ENDIAN][C.TYPE_FLOAT] = 'readFloatLE';
RWStream.reads[C.LITTLE_ENDIAN][C.TYPE_DOUBLE] = 'readDoubleLE';

RWStream.encodings = {};
RWStream.encodings[C.TYPE_HEX] = 'hex';
RWStream.encodings[C.TYPE_ASCII] = 'ascii';
