/**
* @license Apache-2.0
*
* Copyright (c) 2021 The Stdlib Authors.
*
* 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.
*/

'use strict';

// MODULES //

var tape = require( 'tape' );
var Float64Array = require( '@stdlib/array/float64' );
var Int32Array = require( '@stdlib/array/int32' );
var array = require( '@stdlib/ndarray/array' );
var ndarray = require( '@stdlib/ndarray/ctor' );
var shape2strides = require( '@stdlib/ndarray/base/shape2strides' );
var dispatch = require( './../lib' );


// FIXTURES //

var table = require( './fixtures/table.js' );


// TESTS //

tape( 'main export is a function', function test( t ) {
	t.ok( true, __filename );
	t.strictEqual( typeof dispatch, 'function', 'main export is a function' );
	t.end();
});

tape( 'the function throws an error if provided an invalid table object', function test( t ) {
	var values;
	var i;

	values = [
		'5',
		5,
		NaN,
		true,
		false,
		null,
		void 0,
		[],
		{
			'scalar': null
		},
		{
			'scalar': [ 'number' ]
		},
		{
			'array': {}
		},
		{
			'array': [ 'float64' ]
		},
		{
			'ndarray': 'float32'
		},
		{
			'ndarray': [ 'float64' ]
		},
		function noop() {}
	];

	for ( i = 0; i < values.length; i++ ) {
		t.throws( badValue( values[ i ] ), Error, 'throws an error when provided ' + values[ i ] );
	}
	t.end();

	function badValue( value ) {
		return function badValue() {
			dispatch( value );
		};
	}
});

tape( 'the function returns a unary function', function test( t ) {
	var fcn = dispatch( table );
	t.strictEqual( typeof fcn, 'function', 'returns expected value' );
	t.strictEqual( fcn.length, 1, 'has expected arity' );
	t.end();
});

tape( 'the function returns a function which throws an error if provided a value other than a number, array-like object, or ndarray', function test( t ) {
	var values;
	var abs;
	var i;

	abs = dispatch( table );

	values = [
		'5',
		null,
		void 0,
		true,
		false,
		{},
		function noop() {}
	];

	for ( i = 0; i < values.length; i++ ) {
		t.throws( badValue( values[ i ] ), TypeError, 'throws an error when provided ' + values[ i ] );
	}
	t.end();

	function badValue( value ) {
		return function badValue() {
			abs( value );
		};
	}
});

tape( 'the function returns a function which throws an error if provided a number when a table resolution object is unable to resolve a corresponding function', function test( t ) {
	var abs = dispatch( {} );
	t.throws( notSupported, TypeError, 'throws an error' );
	t.end();

	function notSupported() {
		abs( 5.0 );
	}
});

tape( 'the function returns a function which throws an error if provided an array-like object when a table resolution object is unable to resolve a corresponding function', function test( t ) {
	var abs = dispatch( {} );
	t.throws( notSupported, TypeError, 'throws an error' );
	t.end();

	function notSupported() {
		abs( [ 1.0, 2.0, 3.0 ] );
	}
});

tape( 'the function returns a function which throws an error if provided an ndarray when a table resolution object is unable to resolve a corresponding function', function test( t ) {
	var abs = dispatch( {} );
	t.throws( notSupported, TypeError, 'throws an error' );
	t.end();

	function notSupported() {
		var x = array( [ 1.0, 2.0, 3.0 ] );
		abs( x );
	}
});

tape( 'the function returns a function which supports operating on a number', function test( t ) {
	var expected;
	var values;
	var abs;
	var i;

	abs = dispatch( table );

	values = [
		-1.0,
		-3.14,
		2.0
	];
	expected = [
		1.0,
		3.14,
		2.0
	];
	for ( i = 0; i < values.length; i++ ) {
		t.strictEqual( abs( values[ i ] ), expected[ i ], 'returns expected value when provided ' + values[ i ] );
	}
	t.end();
});

tape( 'the function returns a function which supports operating on an array-like object', function test( t ) {
	var expected;
	var values;
	var abs;

	abs = dispatch( table );

	values = new Float64Array([
		-1.0,
		-3.14,
		2.0
	]);
	expected = new Float64Array([
		1.0,
		3.14,
		2.0
	]);
	t.deepEqual( abs( values ), expected, 'returns expected value' );

	values = {
		'length': 3,
		'0': -1.0,
		'1': -3.14,
		'2': 2.0
	};
	expected = [
		1.0,
		3.14,
		2.0
	];
	t.deepEqual( abs( values ), expected, 'returns expected value' );

	values = new ArrayLike([
		-1.0,
		-3.14,
		2.0
	]);
	expected = [
		1.0,
		3.14,
		2.0
	];
	t.deepEqual( abs( values ), expected, 'returns expected value' );
	t.end();

	function ArrayLike( x ) {
		var i;
		if ( !(this instanceof ArrayLike) ) {
			return new ArrayLike( x );
		}
		for ( i = 0; i < x.length; i++ ) {
			this[ i ] = x[ i ];
		}
		this.length = x.length;
		return this;
	}
});

tape( 'the function returns a function which supports operating on an ndarray (1D,contiguous)', function test( t ) {
	var expected;
	var values;
	var abs;

	abs = dispatch( table );

	values = array([
		-1.0,
		-3.14,
		2.0
	]);
	expected = array([
		1.0,
		3.14,
		2.0
	]);
	t.deepEqual( abs( values ), expected, 'returns expected value' );
	t.end();
});

tape( 'the function returns a function which supports operating on an ndarray (nd,contiguous)', function test( t ) {
	var expected;
	var values;
	var abs;
	var buf;
	var sh;
	var st;

	abs = dispatch( table );

	buf = new Float64Array( [ -1.0, -3.14, -2.0, 0.0 ] );
	sh = [ 2, 2, 1 ];
	st = shape2strides( sh, 'row-major' );
	values = ndarray( 'float64', buf, sh, st, 0, 'row-major' );

	buf = new Float64Array( [ 1.0, 3.14, 2.0, 0.0 ] );
	sh = [ 2, 2, 1 ];
	st = shape2strides( sh, 'row-major' );
	expected = ndarray( 'float64', buf, sh, st, 0, 'row-major' );

	t.deepEqual( abs( values ), expected, 'returns expected value' );
	t.end();
});

tape( 'the function returns a function which supports operating on an ndarray (nd,contiguous,reverse_iteration_order)', function test( t ) {
	var expected;
	var values;
	var abs;
	var buf;
	var sh;
	var st;

	abs = dispatch( table );

	buf = new Float64Array( [ -1.0, -3.14, -2.0, 0.0 ] );
	sh = [ 2, 2, 1 ];
	st = [ -2, -1, -1 ];
	values = ndarray( 'float64', buf, sh, st, buf.length-1, 'row-major' );

	buf = new Float64Array( [ 0.0, 2.0, 3.14, 1.0 ] );
	sh = [ 2, 2, 1 ];
	st = shape2strides( sh, 'row-major' );
	expected = ndarray( 'float64', buf, sh, st, 0, 'row-major' );

	t.deepEqual( abs( values ), expected, 'returns expected value' );
	t.end();
});

tape( 'the function returns a function which supports operating on an ndarray (nd,noncontiguous)', function test( t ) {
	var expected;
	var values;
	var abs;
	var buf;
	var sh;
	var st;

	abs = dispatch( table );

	buf = new Float64Array([
		-1.0,
		-2.0,
		-3.0,
		-4.0,
		-5.0,
		-6.0,
		-7.0,
		-8.0
	]);
	sh = [ 2, 2, 1 ];
	st = [ 4, 1, 1 ];
	values = ndarray( 'float64', buf, sh, st, 0, 'row-major' );

	buf = new Float64Array([
		1.0,
		2.0,
		5.0,
		6.0
	]);
	sh = [ 2, 2, 1 ];
	st = shape2strides( sh, 'row-major' );
	expected = ndarray( 'float64', buf, sh, st, 0, 'row-major' );

	t.deepEqual( abs( values ), expected, 'returns expected value' );
	t.end();
});

tape( 'the function returns a function which supports operating on an ndarray (nd,singleton_dimensions)', function test( t ) {
	var expected;
	var values;
	var abs;
	var buf;
	var sh;
	var st;

	abs = dispatch( table );

	buf = new Float64Array([
		-1.0,
		-2.0,
		-3.0,
		-4.0,
		-5.0,
		-6.0,
		-7.0,
		-8.0
	]);
	sh = [ 4, 1, 1 ];
	st = [ 2, 1, 1 ];
	values = ndarray( 'float64', buf, sh, st, 0, 'row-major' );

	buf = new Float64Array([
		1.0,
		3.0,
		5.0,
		7.0
	]);
	sh = [ 4, 1, 1 ];
	st = [ 1, 1, 1 ];
	expected = ndarray( 'float64', buf, sh, st, 0, 'row-major' );

	t.deepEqual( abs( values ), expected, 'returns expected value' );

	buf = new Float64Array([
		-1.0,
		-2.0,
		-3.0,
		-4.0,
		-5.0,
		-6.0,
		-7.0,
		-8.0
	]);
	sh = [ 1, 1, 4 ];
	st = [ 4, 4, 2 ];
	values = ndarray( 'float64', buf, sh, st, 0, 'row-major' );

	buf = new Float64Array([
		1.0,
		3.0,
		5.0,
		7.0
	]);
	sh = [ 1, 1, 4 ];
	st = [ 4, 4, 1 ];
	expected = ndarray( 'float64', buf, sh, st, 0, 'row-major' );

	t.deepEqual( abs( values ), expected, 'returns expected value' );
	t.end();
});

tape( 'the function returns a function which defaults to a "generic" implementation if unable to resolve a function corresponding to an array-like object\'s underlying data type', function test( t ) {
	var expected;
	var values;
	var abs;

	abs = dispatch( table );

	values = new Int32Array([
		-1.0,
		-3.0,
		2.0
	]);
	expected = new Int32Array([
		1.0,
		3.0,
		2.0
	]);
	t.deepEqual( abs( values ), expected, 'returns expected value' );
	t.end();
});

tape( 'the function returns a function which defaults to a "generic" implementation if unable to resolve a function corresponding to an ndarray\'s underlying data type', function test( t ) {
	var expected;
	var values;
	var abs;

	abs = dispatch( table );

	values = new Int32Array([
		-1.0,
		-3.0,
		2.0
	]);
	values = array( values, {
		'dtype': 'int32'
	});
	expected = new Int32Array([
		1.0,
		3.0,
		2.0
	]);
	expected = array( expected, {
		'dtype': 'int32'
	});
	t.deepEqual( abs( values ), expected, 'returns expected value' );
	t.end();
});

// TODO: add tests for complex numbers
