<?php

namespace TablePress\PhpOffice\PhpSpreadsheet\Calculation\MathTrig;

use TablePress\Matrix\Builder;
use TablePress\Matrix\Div0Exception as MatrixDiv0Exception;
use TablePress\Matrix\Exception as MatrixException;
use TablePress\Matrix\Matrix;
use TablePress\PhpOffice\PhpSpreadsheet\Calculation\Exception;
use TablePress\PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;

class MatrixFunctions
{
	/**
	 * Convert parameter to Matrix.
	 *
	 * @param mixed $matrixValues A matrix of values
	 */
	private static function getMatrix($matrixValues): TablePress\Matrix
	{
		$matrixData = [];
		if (!is_array($matrixValues)) {
			$matrixValues = [[$matrixValues]];
		}

		$row = 0;
		foreach ($matrixValues as $matrixRow) {
			if (!is_array($matrixRow)) {
				$matrixRow = [$matrixRow];
			}
			$column = 0;
			foreach ($matrixRow as $matrixCell) {
				if ((is_string($matrixCell)) || ($matrixCell === null)) {
					throw new Exception(ExcelError::VALUE());
				}
				$matrixData[$row][$column] = $matrixCell;
				++$column;
			}
			++$row;
		}

		return new Matrix($matrixData);
	}

	/**
	 * SEQUENCE.
	 *
	 * Generates a list of sequential numbers in an array.
	 *
	 * Excel Function:
	 *      SEQUENCE(rows,[columns],[start],[step])
	 *
	 * @param mixed $rows the number of rows to return, defaults to 1
	 * @param mixed $columns the number of columns to return, defaults to 1
	 * @param mixed $start the first number in the sequence, defaults to 1
	 * @param mixed $step the amount to increment each subsequent value in the array, defaults to 1
	 *
	 * @return array|string The resulting array, or a string containing an error
	 */
	public static function sequence($rows = 1, $columns = 1, $start = 1, $step = 1)
	{
		try {
			$rows = (int) Helpers::validateNumericNullSubstitution($rows, 1);
			Helpers::validatePositive($rows);
			$columns = (int) Helpers::validateNumericNullSubstitution($columns, 1);
			Helpers::validatePositive($columns);
			$start = Helpers::validateNumericNullSubstitution($start, 1);
			$step = Helpers::validateNumericNullSubstitution($step, 1);
		} catch (Exception $e) {
			return $e->getMessage();
		}

		if ($step === 0) {
			return array_chunk(
				array_fill(0, $rows * $columns, $start),
				max($columns, 1)
			);
		}

		return array_chunk(
			range($start, $start + (($rows * $columns - 1) * $step), $step),
			max($columns, 1)
		);
	}

	/**
	 * MDETERM.
	 *
	 * Returns the matrix determinant of an array.
	 *
	 * Excel Function:
	 *        MDETERM(array)
	 *
	 * @param mixed $matrixValues A matrix of values
	 *
	 * @return float|string The result, or a string containing an error
	 */
	public static function determinant($matrixValues)
	{
		try {
			$matrix = self::getMatrix($matrixValues);

			return $matrix->determinant();
		} catch (MatrixException $ex) {
			return ExcelError::VALUE();
		} catch (Exception $e) {
			return $e->getMessage();
		}
	}

	/**
	 * MINVERSE.
	 *
	 * Returns the inverse matrix for the matrix stored in an array.
	 *
	 * Excel Function:
	 *        MINVERSE(array)
	 *
	 * @param mixed $matrixValues A matrix of values
	 *
	 * @return array|string The result, or a string containing an error
	 */
	public static function inverse($matrixValues)
	{
		try {
			$matrix = self::getMatrix($matrixValues);

			return $matrix->inverse()->toArray();
		} catch (MatrixDiv0Exception $e) {
			return ExcelError::NAN();
		} catch (MatrixException $e) {
			return ExcelError::VALUE();
		} catch (Exception $e) {
			return $e->getMessage();
		}
	}

	/**
	 * MMULT.
	 *
	 * @param mixed $matrixData1 A matrix of values
	 * @param mixed $matrixData2 A matrix of values
	 *
	 * @return array|string The result, or a string containing an error
	 */
	public static function multiply($matrixData1, $matrixData2)
	{
		try {
			$matrixA = self::getMatrix($matrixData1);
			$matrixB = self::getMatrix($matrixData2);

			return $matrixA->multiply($matrixB)->toArray();
		} catch (MatrixException $ex) {
			return ExcelError::VALUE();
		} catch (Exception $e) {
			return $e->getMessage();
		}
	}

	/**
	 * MUnit.
	 *
	 * @param mixed $dimension Number of rows and columns
	 *
	 * @return array|string The result, or a string containing an error
	 */
	public static function identity($dimension)
	{
		try {
			$dimension = (int) Helpers::validateNumericNullBool($dimension);
			Helpers::validatePositive($dimension, ExcelError::VALUE());
			$matrix = Builder::createIdentityMatrix($dimension, 0)->toArray();

			return $matrix;
		} catch (Exception $e) {
			return $e->getMessage();
		}
	}
}
