'use strict';

var Buffer = require('safe-buffer').Buffer;
var isArray = require('isarray');
var typedArrayBuffer = require('typed-array-buffer');

var isView = ArrayBuffer.isView || function isView(obj) {
	try {
		typedArrayBuffer(obj);
		return true;
	} catch (e) {
		return false;
	}
};

var useUint8Array = typeof Uint8Array !== 'undefined';
var useArrayBuffer = typeof ArrayBuffer !== 'undefined'
	&& typeof Uint8Array !== 'undefined';
var useFromArrayBuffer = useArrayBuffer && (Buffer.prototype instanceof Uint8Array || Buffer.TYPED_ARRAY_SUPPORT);

module.exports = function toBuffer(data, encoding) {
	/*
	 * No need to do anything for exact instance
	 * This is only valid when safe-buffer.Buffer === buffer.Buffer, i.e. when Buffer.from/Buffer.alloc existed
	 */
	if (data instanceof Buffer) {
		return data;
	}

	if (typeof data === 'string') {
		return Buffer.from(data, encoding);
	}

	/*
	 * Wrap any TypedArray instances and DataViews
	 * Makes sense only on engines with full TypedArray support -- let Buffer detect that
	 */
	if (useArrayBuffer && isView(data)) {
		// Bug in Node.js <6.3.1, which treats this as out-of-bounds
		if (data.byteLength === 0) {
			return Buffer.alloc(0);
		}

		// When Buffer is based on Uint8Array, we can just construct it from ArrayBuffer
		if (useFromArrayBuffer) {
			var res = Buffer.from(data.buffer, data.byteOffset, data.byteLength);
			/*
			 * Recheck result size, as offset/length doesn't work on Node.js <5.10
			 * We just go to Uint8Array case if this fails
			 */
			if (res.byteLength === data.byteLength) {
				return res;
			}
		}

		// Convert to Uint8Array bytes and then to Buffer
		var uint8 = data instanceof Uint8Array ? data : new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
		var result = Buffer.from(uint8);

		/*
		 * Let's recheck that conversion succeeded
		 * We have .length but not .byteLength when useFromArrayBuffer is false
		 */
		if (result.length === data.byteLength) {
			return result;
		}
	}

	/*
	 * Uint8Array in engines where Buffer.from might not work with ArrayBuffer, just copy over
	 * Doesn't make sense with other TypedArray instances
	 */
	if (useUint8Array && data instanceof Uint8Array) {
		return Buffer.from(data);
	}

	var isArr = isArray(data);
	if (isArr) {
		for (var i = 0; i < data.length; i += 1) {
			var x = data[i];
			if (
				typeof x !== 'number'
				|| x < 0
				|| x > 255
				|| ~~x !== x // NaN and integer check
			) {
				throw new RangeError('Array items must be numbers in the range 0-255.');
			}
		}
	}

	/*
	 * Old Buffer polyfill on an engine that doesn't have TypedArray support
	 * Also, this is from a different Buffer polyfill implementation then we have, as instanceof check failed
	 * Convert to our current Buffer implementation
	 */
	if (
		isArr || (
			Buffer.isBuffer(data)
				&& data.constructor
				&& typeof data.constructor.isBuffer === 'function'
				&& data.constructor.isBuffer(data)
		)
	) {
		return Buffer.from(data);
	}

	throw new TypeError('The "data" argument must be a string, an Array, a Buffer, a Uint8Array, or a DataView.');
};
