define('jls/lang/ByteBuffer', [
  'jls/lang/Class',
  'jls/lang/Exception',
  'jls/lang/Buffer',
  'jls/lang/IllegalArgumentException',
  'jls/lang/BufferOverflowException',
  'jls/lang/BufferUnderflowException'
], function (
  Class,
  Exception,
  Buffer,
  IllegalArgumentException,
  BufferOverflowException,
  BufferUnderflowException
) {

var ByteBuffer;
// Late binding
var Charset, CharBuffer;
require(['jls/io/cs/Charset', 'jls/lang/CharBuffer'], function(cs, cb) {
    Charset = cs;
    CharBuffer = cb;
});

/**
 * @augments jls.lang.Buffer
 * @class The buffer class provides facilities to get and put datas from/to a native byte array.
 * @name jls.lang.ByteBuffer
 */
ByteBuffer = Class.create(Buffer, /** @lends jls.lang.ByteBuffer.prototype */
{
    initialize : function($super, barray, offset, length, limit, position, byteOrder) {
        if (! (barray instanceof _native.core.ByteArray)) {
            throw new Exception('Invalid barray argument (' + (typeof barray) + ')');
        }
        this._barray = barray;
        offset = offset || 0;
        var capacity = (typeof length != 'undefined') ? length : this._barray.size() - offset;
        $super(capacity, limit, position, offset);
        this._byteOrder = (typeof byteOrder != 'undefined') ? byteOrder : ByteBuffer.DEFAULT_BYTE_ORDER;
    },
    /**
     * Returns the native byte array of this buffer.
     * 
     * @returns {_native.core.ByteArray} The native byte array.
     */
    byteArray : function() {
        return this._barray;
    },
    /**
     * Free the associated byte array.
     * 
     * @returns {jls.lang.Buffer} This buffer.
     */
    free : function() {
        this._barray.free();
        return this;
    },
    /**
     * Creates a new buffer sharing the native byte array.
     * 
     * @returns {jls.lang.Buffer} The new buffer.
     */
    duplicate : function() {
        return new ByteBuffer(this._barray, this._offset, this._capacity, this._limit, this._position, this._byteOrder);
    },
    /**
     * Creates a new buffer starting at the current position and with the remaining bytes.
     * 
     * @returns {jls.lang.Buffer} The new buffer.
     */
    slice : function() {
        return new ByteBuffer(this._barray, this.position(), this.remaining(), this.remaining(), 0, this._byteOrder);
    },
    /**
     * Puts a byte into this buffer at the current position, and then increments the position.
     * 
     * @param {Number} b The byte to put.
     * @returns {jls.lang.Buffer} This buffer.
     */
    putByte : function(b) {
        if (this.remaining() < 1) {
            throw new BufferOverflowException();
        }
        this._barray.put(this.offset(), b);
        this._position++;
        return this;
    },
    /**
     * Gets a byte from this buffer at the current position, and then increments the position.
     * 
     * @param {Boolean} signed Whether the byte to get is signed.
     * @returns {Number} The byte.
     */
    getByte : function(signed) {
        if (this.remaining() < 1) {
            throw new BufferUnderflowException();
        }
        var b = this._barray.get(this.offset());
        this._position++;
        return (signed && (b >= 0x80)) ? b - 0x100 : b;
    },
    putString : function(s, csn) {
        if (this.remaining() < s.length) {
            throw new BufferOverflowException();
        }
        // TODO Use cache
    	var cs = csn ? Charset.forName(csn) : Charset.defaultCharset();
    	var encoder = cs.newEncoder();
        var cb = CharBuffer.wrap(s);
        encoder.encode(cb, this);
        return this;
    },
    getString : function(csn) {
        // TODO Use cache
    	var cs = csn ? Charset.forName(csn) : Charset.defaultCharset();
    	var decoder = cs.newDecoder();
        var cb = decoder.decode(this);
    	cb.flip();
    	return cb.toNewString();
    },
    putBuffer : function(buffer, length) {
		length = length || buffer.remaining();
        //jls.logger.info('ByteBuffer.putBuffer(' + length + ')');
        if (this.remaining() < length) {
            throw new BufferOverflowException();
        }
        this._barray.memcpy(this.offset(), buffer.byteArray(), buffer.position(), length);
        this._position += length;
        buffer.incrementPosition(length);
        return this;
    },
    putByteArray : function(ba) {
        if (this.remaining() < ba.length) {
            throw new BufferOverflowException();
        }
        for (var i = 0; i < ba.length; i++) {
            this._barray.put(this.offset() + i, ba[i]);
        }
        this._position += ba.length;
        return this;
    },
    getByteArray : function(length) {
        if (length) {
            if (this.remaining() < length) {
                throw new BufferUnderflowException();
            }
        } else {
            length = this.remaining();
        }
        var ba = [];
        for (var i = 0; i < length; i++) {
            ba.push(this._barray.get(this.offset() + i));
        }
        this._position += length;
        return ba;
    },
    getByteOrder : function() {
        return this._byteOrder;
    },
    setByteOrder : function(byteOrder) {
        this._byteOrder = byteOrder;
    },
    putShort : function(value) {
        if (this._byteOrder == Buffer.BIG_ENDIAN) {
            this.putByte((value >> 8) & 0xff);
            this.putByte(value & 0xff);
        } else {
            this.putByte(value & 0xff);
            this.putByte((value >> 8) & 0xff);
        }
    },
    getShort : function(signed) {
        var value;
        if (this._byteOrder == Buffer.BIG_ENDIAN) {
            value = this.getByte() << 8;
            value |= this.getByte();
        } else {
            value = this.getByte();
            value |= this.getByte() << 8;
        }
        return (signed && (value >= 0x8000)) ? value - 0x10000 : value;
    },
    putInt : function(value) {
        if (this._byteOrder == Buffer.BIG_ENDIAN) {
            this.putByte((value >> 24) & 0xff);
            this.putByte((value >> 16) & 0xff);
            this.putByte((value >> 8) & 0xff);
            this.putByte(value & 0xff);
        } else {
            this.putByte(value & 0xff);
            this.putByte((value >> 8) & 0xff);
            this.putByte((value >> 16) & 0xff);
            this.putByte((value >> 24) & 0xff);
        }
    },
    getInt : function(signed) {
        var value;
        if (this._byteOrder == Buffer.BIG_ENDIAN) {
            value = this.getByte() << 24;
            value |= this.getByte() << 16;
            value |= this.getByte() << 8;
            value |= this.getByte();
        } else {
            value = this.getByte();
            value |= this.getByte() << 8;
            value |= this.getByte() << 16;
            value |= this.getByte() << 24;
        }
        return (signed && (value >= 0x80000000)) ? value - 0x100000000 : value;
    },
    /*
     * Bitwise operators treat their operands as a set of 32 bits (zeros and ones), rather than as decimal,
     * hexadecimal, or octal numbers. For example, the decimal number nine has a binary representation of 1001.
     * Bitwise operators perform their operations on such binary representations, but they return standard JavaScript numerical values. 
     */
    putLong : function(value) {
        // var hi = (value - (value % 0x100000000)) / 0x100000000;
        var hi = value / 0x100000000;
        if (value >= 0) {
            hi = Math.floor(hi);
        } else {
            hi = Math.ceil(hi);
            if (hi == 0) {
                hi = -1; // -0 !
            }
        }
        if (this._byteOrder == Buffer.BIG_ENDIAN) {
            this.putByte((hi >> 24) & 0xff);
            this.putByte((hi >> 16) & 0xff);
            this.putByte((hi >> 8) & 0xff);
            this.putByte(hi & 0xff);
            this.putByte((value >> 24) & 0xff);
            this.putByte((value >> 16) & 0xff);
            this.putByte((value >> 8) & 0xff);
            this.putByte(value & 0xff);
        } else {
            this.putByte(value & 0xff);
            this.putByte((value >> 8) & 0xff);
            this.putByte((value >> 16) & 0xff);
            this.putByte((value >> 24) & 0xff);
            this.putByte(hi & 0xff);
            this.putByte((hi >> 8) & 0xff);
            this.putByte((hi >> 16) & 0xff);
            this.putByte((hi >> 24) & 0xff);
        }
    },
    getLong : function(signed) {
        var value;
        if (this._byteOrder == Buffer.BIG_ENDIAN) {
            value = this.getByte() * 0x100000000000000;
            value |= this.getByte() * 0x1000000000000;
            value |= this.getByte() * 0x10000000000;
            value |= this.getByte() * 0x100000000;
            value |= this.getByte() << 24;
            value |= this.getByte() << 16;
            value |= this.getByte() << 8;
            value |= this.getByte();
        } else {
            value = this.getByte();
            value |= this.getByte() << 8;
            value |= this.getByte() << 16;
            value |= this.getByte() << 24;
            value |= this.getByte() * 0x100000000;
            value |= this.getByte() * 0x10000000000;
            value |= this.getByte() * 0x1000000000000;
            value |= this.getByte() * 0x100000000000000;
        }
        return (signed && (value >= 0x8000000000000000)) ? value - 0x10000000000000000 : value;
    },
    putPointer : function(value) {
        switch (ByteBuffer.POINTER_SIZE) {
        case 4:
            this.putInt(value);
            break;
        case 8:
            this.putLong(value);
            break;
        default:
            throw new Exception('Unimplemented pointer size (' + ByteBuffer.POINTER_SIZE + ')');
        }
    },
    getPointer : function() {
        switch (ByteBuffer.POINTER_SIZE) {
        case 4:
            return this.getInt(false);
        case 8:
            return this.getLong(false);
        default:
            throw new Exception('Unimplemented pointer size (' + ByteBuffer.POINTER_SIZE + ')');
        }
    }
});

Object.extend(ByteBuffer, /** @lends jls.lang.ByteBuffer */
{
    /**
     * Allocates a new buffer.
     * 
     * @param {Number} capacity The capacity of the byte array.
     * @returns {jls.lang.Buffer} The new buffer.
     */
    allocate : function(capacity) {
        return ByteBuffer.wrap(new _native.core.ByteArray(capacity));
    },
    /**
     * Wraps an existing native byte array into a new buffer.
     * 
     * @param {ByteArray} barray The native byte array to wrap.
     * @param {Number} offset The offset of the byte array to use for this buffer.
     * @param {Number} length The length of the buffer.
     * @returns {jls.lang.Buffer} The new buffer.
     */
    wrap : function(barray, offset, length) {
        offset = offset || 0;
        length = length || (barray.size() - offset);
        return new ByteBuffer(barray, offset, length);
    },
    /**
     * TODO Remove
     * @deprecated
     */
    fromString : function(s, csn) {
        var buffer = ByteBuffer.allocate(s.length * 2);
        buffer.putString(s, csn);
        buffer.flip();
        return buffer;
    },
    DEFAULT_BYTE_ORDER : (_native.core.properties['cpu.endian'] == 'big' ? Buffer.BIG_ENDIAN : Buffer.LITTLE_ENDIAN),
    POINTER_SIZE : parseInt(_native.core.properties['cpu.pointer.size'])
});


return ByteBuffer;
});
