/*
 * Copyright (C) 2014 United States Government as represented by the Administrator of the
 * National Aeronautics and Space Administration. All Rights Reserved.
 */
/**
 * @exports ByteBuffer
 * @version $Id: ByteBuffer.js 2954 2015-04-01 22:08:16Z danm $
 */
define(['../error/ArgumentError',
        '../util/Logger'
        ],
    function (ArgumentError,
              Logger) {
        "use strict";

        /**
         * Constructs a wrapper around an array buffer that enables byte-level access to its data.
         * This wrapper strives to minimize secondary allocations when subarrays are accessed.
         * The one exception is when double precision floating point data is access that is not properly aligned.
         * @alias ByteBuffer
         * @classdesc A structured wrapper around an array buffer that provides byte-level access to its data.
         * @param {ArrayBuffer} array An array buffer containing source data.
         * @constructor
         */
        var ByteBuffer = function(array) {
            if (!array) {
                throw new ArgumentError(
                    Logger.logMessage(Logger.LEVEL_SEVERE, "ByteBuffer", "constructor", "missingArray"));
            }

            /**
             * The raw data of the array buffer.
             * @type {ArrayBuffer}
             */
            this.array = array;

            /**
             * A data view on the array buffer.
             * This data view is used to extract integer and floating point data from that array buffer.
             * @type {DataView}
             */
            this.data = new DataView(array);

            /**
             * The current position in the array buffer.
             * This position is implicitly used to access all data.
             * @type {Number}
             */
            this.position = 0;

            /**
             * The byte order in which the data is encoded.
             * Byte order will either be big endian or little endian.
             * @type {Boolean}
             * @default ByteByffer.LITTLE_ENDIAN
             * @private
             */
            this._order = ByteBuffer.LITTLE_ENDIAN;
        };

        /**
         * Get a byte from the current position and advance the position.
         * @returns {Number}
         */
        ByteBuffer.prototype.getByte = function() {
            var result = this.data.getUint8(this.position);
            this.position += ByteBuffer.BYTE_SIZE;
            return result;
        };

        /**
         * Get a byte array from the current position and advance the position.
         * To avoid secondary allocation, a TypedArray shadows the underlying ArrayBuffer.
         * @param {Number} numBytes The number of bytes in the desired array.
         * @returns {Uint8Array}
         */
        ByteBuffer.prototype.getByteArray = function(numBytes) {
            var result = new Uint8Array(this.array, this.position, numBytes);
            this.position += ByteBuffer.BYTE_SIZE * numBytes;
            return result;
        };

        /**
         * Get a 16-bit integer from the current position and advance the position.
         * @returns {Number}
         */
        ByteBuffer.prototype.getInt16 = function() {
            var result = this.data.getInt16(this.position, this._order);
            this.position += ByteBuffer.INT16_SIZE;
            return result;
        };

        /**
         * Get a 16-bit integer array from the current position and advance the position.
         * To avoid secondary allocation, a TypedArray shadows the underlying ArrayBuffer.
         * @param {Number} numInt16s The number of 16-bit integers in the desired array.
         * @returns {Int16Array}
         */
        ByteBuffer.prototype.getInt16Array = function(numInt16s) {
            var result = new Int16Array(this.array, this.position, numInt16s);
            this.position += ByteBuffer.INT16_SIZE * numInt16s;
            return result;
        };

        /**
         * Get a 32-bit integer from the current position and advance the position.
         * @returns {Number}
         */
        ByteBuffer.prototype.getInt32 = function() {
            var result = this.data.getInt32(this.position, this._order);
            this.position += ByteBuffer.INT32_SIZE;
            return result;
        };

        /**
         * Get a single precision floating point array from the current position and advance the position.
         * To avoid secondary allocation, a TypedArray shadows the underlying ArrayBuffer.
         * @param {Number} numInt32s The number of 32-bit integers in the desired array.
         * @returns {Int32Array}
         */
        ByteBuffer.prototype.getInt32Array = function(numInt32s) {
            var result = new Int32Array(this.array, this.position, numInt32s);
            this.position += ByteBuffer.INT32_SIZE * numInt32s;
            return result;
        };

        /**
         * Get a single precision floating point number from the current position and advance the position.
         * @returns {Number}
         */
        ByteBuffer.prototype.getFloat = function() {
            var result = this.data.getFloat32(this.position, this._order);
            this.position += ByteBuffer.FLOAT_SIZE;
            return result;
        };

        /**
         * Get a single precision floating point array from the current position and advance the position.
         * To avoid secondary allocation, a TypedArray shadows the underlying ArrayBuffer.
         * @param {Number} numFloats The number of single precision floating point numbers in the desired array.
         * @returns {Float32Array}
         */
        ByteBuffer.prototype.getFloatArray = function(numFloats) {
            var result = new Float32Array(this.array, this.position, numFloats);
            this.position += ByteBuffer.FLOAT_SIZE * numFloats;
            return result;
        };

        /**
         * Get a double precision floating point number from the current position and advance the position.
         * @returns {Number}
         */
        ByteBuffer.prototype.getDouble = function() {
            var result = this.data.getFloat64(this.position, this._order);
            this.position += ByteBuffer.DOUBLE_SIZE;
            return result;
        };

        /**
         * Get a single precision floating point array from the current position and advance the position.
         * To avoid secondary allocation, a TypedArray shadows the underlying ArrayBuffer.
         * @param {Number} numDoubles The number of double precision floating point numbers in the desired array.
         * @returns {Float64Array}
         */
        ByteBuffer.prototype.getDoubleArray = function(numDoubles) {
            // Issue: Float64Array c'tor throws an exception if the starting offset is not a multiple of 8.
            // We see this in shapefiles.
            var result;
            // If the data is not DWORD aligned, ...
            if (this.position % 8 != 0) {
                var bytes = this.array.slice(this.position, this.position + numDoubles * ByteBuffer.DOUBLE_SIZE);
                result = new Float64Array(bytes);
            }
            else {
                result = new Float64Array(this.array, this.position, numDoubles);
            }
            this.position += ByteBuffer.DOUBLE_SIZE * numDoubles;
            return result;
        };

        /**
         * Skip over the specified number of bytes.
         * @param {Number} numBytes The number of bytes to skip.
         */
        ByteBuffer.prototype.skipBytes = function(numBytes) {
            this.position += numBytes * ByteBuffer.BYTE_SIZE;
        };

        /**
         * Skip over the specified number of 16-bit integers.
         * @param {Number} numInt16s The number of 16-bit integers to skip.
         */
        ByteBuffer.prototype.skipInt16s = function(numInt16s) {
            this.position += numInt16s * ByteBuffer.INT16_SIZE;
        };

        /**
         * Skip over the specified number of 32-bit integers.
         * @param {Number} numInt32s The number of 32-bit integers to skip.
         */
        ByteBuffer.prototype.skipInt32s = function(numInt32s) {
            this.position += numInt32s * ByteBuffer.INT32_SIZE;
        };

        /**
         * Skip over the specified number of single precision floating point numbers.
         * @param {Number} numFloats The number of single precision floating point numbers to skip.
         */
        ByteBuffer.prototype.skipFloats = function(numFloats) {
            this.position += numFloats * ByteBuffer.FLOAT_SIZE;
        };

        /**
         * Skip over the specified number of double precision floating point numbers.
         * @param {Number} numDoubles The number of double precision floating point numbers to skip.
         */
        ByteBuffer.prototype.skipDoubles = function(numDoubles) {
            this.position += numDoubles * ByteBuffer.DOUBLE_SIZE;
        };

        /**
         * Advance to a specific position.
         * @param {Number} position The specified position.
         */
        ByteBuffer.prototype.seek = function(position) {
            this.position = position;
        };

        /**
         * Set the byte order of the underlying data.
         * @param {Boolean} order The byte order of the underlying data.
         */
        ByteBuffer.prototype.order = function(order) {
            this._order = order;
        };

        /**
         * Return the total size of the underlying data.
         * @returns {Number} The size of the underlying data.
         */
        ByteBuffer.prototype.limit = function() {
            return this.data.byteLength;
        };

        /**
         * Indicates whether there remains any data to be accessed sequentially.
         * @returns {Boolean} True if more data can be accessed sequentially.
         */
        ByteBuffer.prototype.hasRemaining = function() {
            return this.position < this.data.byteLength;
        };

        /**
         * Access the underlying data in big endian order, where the most significant bits of the data are encountered first.
         * @type {Boolean}
         * @constant
         */
        ByteBuffer.BIG_ENDIAN = false;

        /**
         * Access the underlying data in little endian order, where the least significant bits of the data are encountered first.
         * @type {Boolean}
         * @constant
         */
        ByteBuffer.LITTLE_ENDIAN = true;

        /**
         * The size of a byte.
         * @type {Number}
         * @constant
         */
        ByteBuffer.BYTE_SIZE = 1;

        /**
         * The size of a 16-bit integer.
         * @type {Number}
         * @constant
         */
        ByteBuffer.INT16_SIZE = 2;

        /**
         * The size of a 32-bit integer.
         * @type {Number}
         * @constant
         */
        ByteBuffer.INT32_SIZE = 4;

        /**
         * The size of a single precision floating point number.
         * @type {Number}
         * @constant
         */
        ByteBuffer.FLOAT_SIZE = 4;

        /**
         * The size of a double precision floating point number.
         * @type {Number}
         * @constant
         */
        ByteBuffer.DOUBLE_SIZE = 8;

        return ByteBuffer;
    }
);