/**
* @license Apache-2.0
*
* Copyright (c) 2018 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 objectKeys = require( '@stdlib/utils/keys' );
var getPrototypeOf = require( '@stdlib/utils/get-prototype-of' );
var isDate = require( './../../is-date-object' );
var isError = require( './../../is-error' );
var isBuffer = require( './../../is-buffer' );
var isRegExp = require( './../../is-regexp' );


// MAIN //

/**
* Tests for deep equality between two values.
*
* @param {*} a - first comparison value
* @param {*} b - second comparison value
* @returns {boolean} boolean indicating if `a` is deep equal to `b`
*
* @example
* var bool = deepEqual( [ 1, 2, 3 ], [ 1, 2, 3 ] );
* // returns true
*
* @example
* var bool = deepEqual( [ 1, 2, 3 ], [ 1, 2, '3' ] );
* // returns false
*
* @example
* var bool = deepEqual( { 'a': 2 }, { 'a': [ 2 ] } );
* // returns false
*
* @example
* var bool = deepEqual( [], {} );
* // returns false
*
* @example
* var bool = deepEqual( null, null );
* // returns true
*/
function deepEqual( a, b ) {
	var aKeys;
	var bKeys;
	var typeA;
	var typeB;
	var key;
	var i;

	typeA = typeof a;
	typeB = typeof b;
	if ( a === null || typeA !== 'object' ) {
		if ( b === null || typeB !== 'object' ) {
			return a === b;
		}
		return false;
	}
	// Case: `a` is of type 'object'
	if ( typeB !== 'object' ) {
		return false;
	}
	if ( getPrototypeOf( a ) !== getPrototypeOf( b ) ) {
		return false;
	}
	if ( isDate( a ) ) {
		return a.getTime() === b.getTime();
	}
	if ( isRegExp( a ) ) {
		return a.source === b.source && a.flags === b.flags;
	}
	if ( isError( a ) ) {
		if ( a.message !== b.message || a.name !== b.name ) {
			return false;
		}
	}
	if ( isBuffer( a ) ) {
		if ( a.length !== b.length ) {
			return false;
		}
		for ( i = 0; i < a.length; i++ ) {
			if ( a[ i ] !== b[ i ] ) {
				return false;
			}
		}
		return true;
	}
	aKeys = objectKeys( a );
	bKeys = objectKeys( b );
	if ( aKeys.length !== bKeys.length ) {
		return false;
	}
	aKeys.sort();
	bKeys.sort();

	// Cheap key test:
	for ( i = 0; i < aKeys.length; i++ ) {
		if ( aKeys[ i ] !== bKeys[ i ] ) {
			return false;
		}
	}
	// Possibly expensive deep equality test for each corresponding key:
	for ( i = 0; i < aKeys.length; i++ ) {
		key = aKeys[ i ];
		if ( !deepEqual( a[ key ], b[ key ] ) ) {
			return false;
		}
	}
	return typeA === typeB;
}


// EXPORTS //

module.exports = deepEqual;
