/**
* @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.
*/

#include "stdlib/ndarray/ctor.h"
#include "stdlib/ndarray/base/bytes_per_element.h"
#include "stdlib/ndarray/base/iteration_order.h"
#include "stdlib/ndarray/base/minmax_view_buffer_index.h"
#include "stdlib/ndarray/base/numel.h"
#include "stdlib/ndarray/base/strides2order.h"
#include "stdlib/ndarray/dtypes.h"
#include "stdlib/ndarray/index_modes.h"
#include "stdlib/ndarray/orders.h"
#include <stdlib.h>
#include <stdint.h>

// NOTE: keep functions in alphabetical order...

/**
* Returns a pointer to a dynamically allocated ndarray.
*
* ## Notes
*
* -   The user is responsible for freeing the allocated memory.
*
* -   To create a zero-dimensional array,
*
*     -   provide an `ndims` argument equal to `0`.
*     -   a `shape` argument equal to a null pointer.
*     -   a `strides` argument containing a single element equal to `0`.
*
*     The `order` argument can be either row-major or column-major and has no effect on data storage or access.
*
*     ```javascript
*     #include "stdlib/ndarray/ctor.h"
*     #include "stdlib/ndarray/dtypes.h"
*     #include "stdlib/ndarray/index_modes.h"
*     #include "stdlib/ndarray/orders.h"
*     #include <stdint.h>
*
*     uint8_t buffer[] = { 0, 0, 0, 0, 0, 0, 0, 0 };
*     int64_t *shape = NULL;
*     int64_t strides = { 0 };
*     int64_t offset = 0;
*
*     struct ndarray *x = stdlib_ndarray_allocate( STDLIB_NDARRAY_FLOAT64, buffer, 0, shape, strides, offset, STDLIB_NDARRAY_ROW_MAJOR, STDLIB_NDARRAY_INDEX_ERROR, 1, { STDLIB_NDARRAY_INDEX_ERROR } );
*     ```
*
* @param dtype      data type
* @param data       pointer to the underlying byte array
* @param ndims      number of dimensions
* @param shape      array shape (dimensions)
* @param strides    array strides (in bytes)
* @param offset     byte offset specifying the location of the first element
* @param order      specifies whether an array is row-major (C-style) or column-major (Fortran-style)
* @param imode      specifies how to handle indices which exceed array dimensions
* @param nsubmodes  number of subscript modes
* @param submodes   specifies how to handle subscripts which exceed array dimensions on a per dimension basis (if provided fewer submodes than dimensions, submodes are recycled using modulo arithmetic)
* @return           pointer to a dynamically allocated ndarray or, if unable to allocate memory, a null pointer
*
* @example
* #include "stdlib/ndarray/ctor.h"
* #include "stdlib/ndarray/dtypes.h"
* #include "stdlib/ndarray/index_modes.h"
* #include "stdlib/ndarray/orders.h"
* #include "stdlib/ndarray/base/bytes_per_element.h"
* #include <stdlib.h>
* #include <stdio.h>
* #include <stdint.h>
*
* // Specify the underlying data type:
* enum STDLIB_NDARRAY_DTYPE dtype = STDLIB_NDARRAY_FLOAT64;
*
* // Create an underlying byte array:
* uint8_t buffer[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
*
* // Specify the number of array dimensions:
* int64_t ndims = 1;
*
* // Specify the array shape:
* int64_t shape[] = { 3 }; // vector consisting of 3 doubles
*
* // Specify the array strides:
* int64_t strides[] = { STDLIB_NDARRAY_FLOAT64_BYTES_PER_ELEMENT };
*
* // Specify the byte offset:
* int64_t offset = 0;
*
* // Specify the array order (note: this does not matter for a 1-dimensional array):
* enum STDLIB_NDARRAY_ORDER order = STDLIB_NDARRAY_ROW_MAJOR;
*
* // Specify the index mode:
* enum STDLIB_NDARRAY_INDEX_MODE imode = STDLIB_NDARRAY_INDEX_ERROR;
*
* // Specify the subscript index modes:
* int8_t submodes[] = { STDLIB_NDARRAY_INDEX_ERROR };
* int64_t nsubmodes = 1;
*
* // Create an ndarray:
* struct ndarray *x = stdlib_ndarray_allocate( dtype, buffer, ndims, shape, strides, offset, order, imode, nsubmodes, submodes );
* if ( x == NULL ) {
*     fprintf( stderr, "Error allocating memory.\n" );
*     exit( 1 );
* }
*
* // Free allocated memory:
* stdlib_ndarray_free( x );
*/
struct ndarray * stdlib_ndarray_allocate( int16_t dtype, uint8_t *data, int64_t ndims, int64_t *shape, int64_t *strides, int64_t offset, int8_t order, int8_t imode, int64_t nsubmodes, int8_t *submodes ) {
	int64_t len;

	struct ndarray *arr = malloc( sizeof( struct ndarray ) );
	if ( arr == NULL ) {
		return NULL;
	}
	arr->data = data;
	arr->dtype = dtype;
	arr->imode = imode;
	arr->ndims = ndims;
	arr->nsubmodes = nsubmodes;
	arr->offset = offset;
	arr->order = order;
	arr->shape = shape;
	arr->strides = strides;
	arr->submodes = submodes;

	if ( shape == NULL ) {
		len = 1;
	} else {
		len = stdlib_ndarray_numel( ndims, shape );
	}
	arr->length = len;

	arr->BYTES_PER_ELEMENT = stdlib_ndarray_bytes_per_element( dtype );
	arr->byteLength = len * (arr->BYTES_PER_ELEMENT);
	arr->flags = stdlib_ndarray_flags( arr );

	return arr;
}

/**
* Returns the size of an ndarray (in bytes).
*
* @param arr  input ndarray
* @return     array size
*/
int64_t stdlib_ndarray_byte_length( const struct ndarray *arr ) {
	return arr->byteLength;
}

/**
* Returns the number of bytes per ndarray element.
*
* @param arr  input ndarray
* @return     bytes per element
*/
int64_t stdlib_ndarray_byte_length_per_element( const struct ndarray *arr ) {
	return arr->BYTES_PER_ELEMENT;
}

/**
* Returns a pointer to an ndarray's underlying byte array.
*
* @param arr  input ndarray
* @return     underlying byte array
*/
uint8_t * stdlib_ndarray_data( const struct ndarray *arr ) {
	return arr->data;
}

/**
* Returns an ndarray dimension size.
*
* ## Notes
*
* -   The function does not perform any sanity checks.
* -   If an input ndarray is zero-dimensional, the function always returns `-1`.
*
* @param arr  input ndarray
* @param i    dimension index
* @return     dimension size
*/
int64_t stdlib_ndarray_dimension( const struct ndarray *arr, const int64_t i ) {
	if ( arr->shape == NULL ) {
		return -1;
	}
	return arr->shape[ i ];
}

/**
* Disables specified ndarray flags.
*
* ## Notes
*
* -   The function does not perform any sanity checks and **assumes** the user knows what s/he is doing.
*
* @param arr    input ndarray
* @param flags  bit mask to disable flags
* @return       status code
*/
int8_t stdlib_ndarray_disable_flags( struct ndarray *arr, const int64_t flags ) {
	arr->flags &= ~flags;
	return 0;
}

/**
* Returns an ndarray data type.
*
* @param arr  input ndarray
* @return     array data type
*/
int16_t stdlib_ndarray_dtype( const struct ndarray *arr ) {
	return arr->dtype;
}

/**
* Enables specified ndarray flags.
*
* ## Notes
*
* -   The function does not perform any sanity checks and **assumes** the user knows what s/he is doing.
*
* @param arr    input ndarray
* @param flags  bit mask to enable flags
* @return       status code
*/
int8_t stdlib_ndarray_enable_flags( struct ndarray *arr, const int64_t flags ) {
	arr->flags |= flags;
	return 0;
}

/**
* Returns ndarray flags.
*
* @param arr  input ndarray
* @return     flags
*/
int64_t stdlib_ndarray_flags( const struct ndarray *arr ) {
	uint8_t contiguous;
	int64_t *strides;
	int64_t nbytes;
	int64_t tmp[2];
	int64_t ndims;
	int64_t flags;
	int64_t len;
	int8_t ord;

	// Cache various ndarray data:
	len = arr->length;
	ndims = arr->ndims;
	strides = arr->strides;
	nbytes = arr->BYTES_PER_ELEMENT;

	// Initialize the memory for `flags`:
	flags = 0;

	// Determine if the array can be stored contiguously...
	if ( len == 0 || stdlib_ndarray_iteration_order( ndims, strides ) == 0 ) {
		// If an array does not contain any elements, then no data to store, and, if the array is unordered, adjacent array elements are not guaranteed to be stored next to each other:
		contiguous = 0;
	} else if ( ndims == 0 ) {
		// If an array is zero-dimensional, data consists of a single element which means it is trivially compatible with a single memory segment:
		contiguous = 1;
	} else {
		// Ensure that the array is compatible with a single memory segment:
		stdlib_ndarray_minmax_view_buffer_index( ndims, arr->shape, strides, arr->offset, tmp );
		if ( (len*nbytes) == ( (tmp[1]-tmp[0])+nbytes ) ) {
			// Compatible:
			contiguous = 1;
		} else {
			// Incompatible:
			contiguous = 0;
		}
	}
	// Determine if the array is row-major/column-major contiguous:
	if ( contiguous == 1 ) {
		// Infer the array "order" from the stride array (this is supplementary to `arr->order`):
		ord = stdlib_ndarray_strides2order( ndims, strides );

		if ( ord == 1 || ord == 3 ) {
			flags |= STDLIB_NDARRAY_ROW_MAJOR_CONTIGUOUS_FLAG;
		}
		if ( ord == 2 || ord == 3 ) {
			flags |= STDLIB_NDARRAY_COLUMN_MAJOR_CONTIGUOUS_FLAG;
		}
	}
	return flags;
}

/**
* Frees an ndarray's allocated memory.
*
* @param arr  input ndarray
*/
void stdlib_ndarray_free( struct ndarray *arr ) {
	free( arr );
}

/**
* Tests whether an ndarray has specified flags enabled.
*
* @param arr    input ndarray
* @param flags  bit mask specifying the flags to test against
* @return       `1` if flags are set and `0` otherwise
*/
int8_t stdlib_ndarray_has_flags( const struct ndarray *arr, const int64_t flags ) {
	if ( ( arr->flags & flags ) == flags ) {
		return 1;
	}
	return 0;
}

/**
* Returns the index mode of an ndarray.
*
* @param arr  input ndarray
* @return     index mode
*/
int8_t stdlib_ndarray_index_mode( const struct ndarray *arr ) {
	return arr->imode;
}

/**
* Returns the number of elements in an ndarray.
*
* @param arr  input ndarray
* @return     number of elements
*/
int64_t stdlib_ndarray_length( const struct ndarray *arr ) {
	return arr->length;
}

/**
* Returns the number of ndarray dimensions.
*
* @param arr  input ndarray
* @return     number of dimensions
*/
int64_t stdlib_ndarray_ndims( const struct ndarray *arr ) {
	return arr->ndims;
}

/**
* Returns the number of ndarray subscript modes.
*
* @param arr  input ndarray
* @return     number of subscript modes
*/
int64_t stdlib_ndarray_nsubmodes( const struct ndarray *arr ) {
	return arr->nsubmodes;
}

/**
* Returns an ndarray index offset (in bytes).
*
* @param arr  input ndarray
* @return     index offset
*/
int64_t stdlib_ndarray_offset( const struct ndarray *arr ) {
	return arr->offset;
}

/**
* Returns an ndarray index offset (in elements).
*
* ## Notes
*
* -   The function assumes that an ndarray is aligned (i.e., offset is a multiple of the number of bytes per ndarray element).
*
* @param arr  input ndarray
* @return     index offset
*/
int64_t stdlib_ndarray_offset_elements( const struct ndarray *arr ) {
	return arr->offset / arr->BYTES_PER_ELEMENT;
}

/**
* Returns the order of an ndarray.
*
* @param arr  input ndarray
* @return     array order
*/
int8_t stdlib_ndarray_order( const struct ndarray *arr ) {
	return arr->order;
}

/**
* Returns a pointer to an array containing an ndarray shape (dimensions).
*
* ## Notes
*
* -   If an input ndarray is zero-dimensional, the function returns a null pointer.
*
* @param arr  input ndarray
* @return     array shape (dimensions)
*/
int64_t * stdlib_ndarray_shape( const struct ndarray *arr ) {
	return arr->shape;
}

/**
* Returns an ndarray stride (in bytes).
*
* ## Notes
*
* -   The function does not perform any sanity checks.
*
* @param arr  input ndarray
* @param i    dimension index
* @return     array stride
*/
int64_t stdlib_ndarray_stride( const struct ndarray *arr, const int64_t i ) {
	return arr->strides[ i ];
}

/**
* Returns an ndarray stride (in elements).
*
* ## Notes
*
* -   The function does not perform any sanity checks.
* -   The function assumes that an ndarray is aligned (i.e., stride is a multiple of the number of bytes per ndarray element).
*
* @param arr  input ndarray
* @param i    dimension index
* @return     array stride
*/
int64_t stdlib_ndarray_stride_elements( const struct ndarray *arr, const int64_t i ) {
	return arr->strides[ i ] / arr->BYTES_PER_ELEMENT;
}

/**
* Returns a pointer to an array containing ndarray strides (in bytes).
*
* @param arr  input ndarray
* @return     array strides
*/
int64_t * stdlib_ndarray_strides( const struct ndarray *arr ) {
	return arr->strides;
}

/**
* Returns an ndarray subscript mode.
*
* ## Notes
*
* -   If an ndarray has fewer subscript modes than dimensions, modes are recycled using modulo arithmetic.
* -   The function does not perform any sanity checks.
*
* @param arr  input ndarray
* @param i    dimension index
* @return     subscript mode
*/
int8_t stdlib_ndarray_submode( const struct ndarray *arr, const int64_t i ) {
	return arr->submodes[ i%(arr->nsubmodes) ];
}

/**
* Returns ndarray subscript modes.
*
* @param arr  input ndarray
* @return     subscript modes
*/
int8_t * stdlib_ndarray_submodes( const struct ndarray *arr ) {
	return arr->submodes;
}
