<?php

namespace TablePress\PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting;

use TablePress\PhpOffice\PhpSpreadsheet\Calculation\Statistical\Percentiles;
use TablePress\PhpOffice\PhpSpreadsheet\Style\Color;

class ConditionalColorScale
{
	private ?ConditionalFormatValueObject $minimumConditionalFormatValueObject = null;

	private ?ConditionalFormatValueObject $midpointConditionalFormatValueObject = null;

	private ?ConditionalFormatValueObject $maximumConditionalFormatValueObject = null;

	private ?Color $minimumColor = null;

	private ?Color $midpointColor = null;

	private ?Color $maximumColor = null;

	private ?string $sqref = null;

	/** @var mixed[] */
	private array $valueArray = [];

	private float $minValue = 0;

	private float $maxValue = 0;

	private float $midValue = 0;

	private ?\TablePress\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $worksheet = null;

	public function getMinimumConditionalFormatValueObject(): ?ConditionalFormatValueObject
	{
		return $this->minimumConditionalFormatValueObject;
	}

	public function setMinimumConditionalFormatValueObject(ConditionalFormatValueObject $minimumConditionalFormatValueObject): self
	{
		$this->minimumConditionalFormatValueObject = $minimumConditionalFormatValueObject;

		return $this;
	}

	public function getMidpointConditionalFormatValueObject(): ?ConditionalFormatValueObject
	{
		return $this->midpointConditionalFormatValueObject;
	}

	public function setMidpointConditionalFormatValueObject(ConditionalFormatValueObject $midpointConditionalFormatValueObject): self
	{
		$this->midpointConditionalFormatValueObject = $midpointConditionalFormatValueObject;

		return $this;
	}

	public function getMaximumConditionalFormatValueObject(): ?ConditionalFormatValueObject
	{
		return $this->maximumConditionalFormatValueObject;
	}

	public function setMaximumConditionalFormatValueObject(ConditionalFormatValueObject $maximumConditionalFormatValueObject): self
	{
		$this->maximumConditionalFormatValueObject = $maximumConditionalFormatValueObject;

		return $this;
	}

	public function getMinimumColor(): ?Color
	{
		return $this->minimumColor;
	}

	public function setMinimumColor(Color $minimumColor): self
	{
		$this->minimumColor = $minimumColor;

		return $this;
	}

	public function getMidpointColor(): ?Color
	{
		return $this->midpointColor;
	}

	public function setMidpointColor(Color $midpointColor): self
	{
		$this->midpointColor = $midpointColor;

		return $this;
	}

	public function getMaximumColor(): ?Color
	{
		return $this->maximumColor;
	}

	public function setMaximumColor(Color $maximumColor): self
	{
		$this->maximumColor = $maximumColor;

		return $this;
	}

	public function getSqRef(): ?string
	{
		return $this->sqref;
	}

	public function setSqRef(string $sqref, \TablePress\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $worksheet): self
	{
		$this->sqref = $sqref;
		$this->worksheet = $worksheet;

		return $this;
	}

	public function setScaleArray(): self
	{
		if ($this->sqref !== null && $this->worksheet !== null) {
			$values = $this->worksheet->rangesToArray($this->sqref, null, true, false, true);
			$this->valueArray = [];
			foreach ($values as $key => $value) {
				/** @var array<float|int|string> $value */
				foreach ($value as $k => $v) {
					$this->valueArray[] = (float) $v;
				}
			}
			$this->prepareColorScale();
		}

		return $this;
	}

	public function getColorForValue(float $value): string
	{
		if ($this->minimumColor === null || $this->midpointColor === null || $this->maximumColor === null) {
			return 'FF000000';
		}
		$minColor = $this->minimumColor->getARGB();
		$midColor = $this->midpointColor->getARGB();
		$maxColor = $this->maximumColor->getARGB();

		if ($minColor === null || $midColor === null || $maxColor === null) {
			return 'FF000000';
		}

		if ($value <= $this->minValue) {
			return $minColor;
		}
		if ($value >= $this->maxValue) {
			return $maxColor;
		}
		if ($value == $this->midValue) {
			return $midColor;
		}
		if ($value < $this->midValue) {
			$blend = ($value - $this->minValue) / ($this->midValue - $this->minValue);
			$alpha1 = hexdec(substr($minColor, 0, 2));
			$alpha2 = hexdec(substr($midColor, 0, 2));
			$red1 = hexdec((string) substr($minColor, 2, 2));
			$red2 = hexdec((string) substr($midColor, 2, 2));
			$green1 = hexdec((string) substr($minColor, 4, 2));
			$green2 = hexdec((string) substr($midColor, 4, 2));
			$blue1 = hexdec((string) substr($minColor, 6, 2));
			$blue2 = hexdec((string) substr($midColor, 6, 2));
		} else {
			$blend = ($value - $this->midValue) / ($this->maxValue - $this->midValue);
			$alpha1 = hexdec(substr($midColor, 0, 2));
			$alpha2 = hexdec(substr($maxColor, 0, 2));
			$red1 = hexdec((string) substr($midColor, 2, 2));
			$red2 = hexdec((string) substr($maxColor, 2, 2));
			$green1 = hexdec((string) substr($midColor, 4, 2));
			$green2 = hexdec((string) substr($maxColor, 4, 2));
			$blue1 = hexdec((string) substr($midColor, 6, 2));
			$blue2 = hexdec((string) substr($maxColor, 6, 2));
		}
		$alpha = (int) ($alpha2 * $blend + $alpha1 * (1 - $blend));
		$red = (int) ($red2 * $blend + $red1 * (1 - $blend));
		$green = (int) ($green2 * $blend + $green1 * (1 - $blend));
		$blue = (int) ($blue2 * $blend + $blue1 * (1 - $blend));

		return sprintf('%02X%02X%02X%02X', $alpha, $red, $green, $blue);
	}

	private function getLimitValue(string $type, float $value = 0, float $formula = 0): float
	{
		if (count($this->valueArray) === 0) {
			return 0;
		}
		switch ($type) {
			case 'min':
				/** @var float|int */
				$temp = min($this->valueArray);

				return (float) $temp;
			case 'max':
				/** @var float|int */
				$temp = max($this->valueArray);

				return (float) $temp;
			case 'percentile':
				return (float) Percentiles::PERCENTILE($this->valueArray, (float) ($value / 100));
			case 'formula':
				return $formula;
			case 'percent':
				/** @var float|int */
				$min = min($this->valueArray);
				$min = (float) $min;
				/** @var float|int */
				$max = max($this->valueArray);
				$max = (float) $max;

				return $min + (float) ($value / 100) * ($max - $min);
			default:
				return 0;
		}
	}

	/**
	 * Prepares color scale for execution, see the first if for variables that must be set beforehand.
	 */
	public function prepareColorScale(): self
	{
		if ($this->minimumConditionalFormatValueObject !== null && $this->maximumConditionalFormatValueObject !== null && $this->minimumColor !== null && $this->maximumColor !== null) {
			if ($this->midpointConditionalFormatValueObject !== null && $this->midpointConditionalFormatValueObject->getType() !== 'None') {
				$this->minValue = $this->getLimitValue($this->minimumConditionalFormatValueObject->getType(), (float) $this->minimumConditionalFormatValueObject->getValue(), (float) $this->minimumConditionalFormatValueObject->getCellFormula());
				$this->midValue = $this->getLimitValue($this->midpointConditionalFormatValueObject->getType(), (float) $this->midpointConditionalFormatValueObject->getValue(), (float) $this->midpointConditionalFormatValueObject->getCellFormula());
				$this->maxValue = $this->getLimitValue($this->maximumConditionalFormatValueObject->getType(), (float) $this->maximumConditionalFormatValueObject->getValue(), (float) $this->maximumConditionalFormatValueObject->getCellFormula());
			} else {
				$this->minValue = $this->getLimitValue($this->minimumConditionalFormatValueObject->getType(), (float) $this->minimumConditionalFormatValueObject->getValue(), (float) $this->minimumConditionalFormatValueObject->getCellFormula());
				$this->maxValue = $this->getLimitValue($this->maximumConditionalFormatValueObject->getType(), (float) $this->maximumConditionalFormatValueObject->getValue(), (float) $this->maximumConditionalFormatValueObject->getCellFormula());
				$this->midValue = (float) ($this->minValue + $this->maxValue) / 2;
				$blend = 0.5;

				$minColor = $this->minimumColor->getARGB();
				$maxColor = $this->maximumColor->getARGB();

				if ($minColor !== null && $maxColor !== null) {
					$alpha1 = hexdec(substr($minColor, 0, 2));
					$alpha2 = hexdec(substr($maxColor, 0, 2));
					$red1 = hexdec((string) substr($minColor, 2, 2));
					$red2 = hexdec((string) substr($maxColor, 2, 2));
					$green1 = hexdec((string) substr($minColor, 4, 2));
					$green2 = hexdec((string) substr($maxColor, 4, 2));
					$blue1 = hexdec((string) substr($minColor, 6, 2));
					$blue2 = hexdec((string) substr($maxColor, 6, 2));
					$alpha = (int) ($alpha2 * $blend + $alpha1 * (1 - $blend));
					$red = (int) ($red2 * $blend + $red1 * (1 - $blend));
					$green = (int) ($green2 * $blend + $green1 * (1 - $blend));
					$blue = (int) ($blue2 * $blend + $blue1 * (1 - $blend));

					$this->midpointColor = new Color(sprintf('%02X%02X%02X%02X', $alpha, $red, $green, $blue));
				} else {
					$this->midpointColor = null;
				}
			}
		}

		return $this;
	}

	/**
	 * Checks that all needed color scale data is in place.
	 */
	public function colorScaleReadyForUse(): bool
	{
		if ($this->minimumColor === null || $this->midpointColor === null || $this->maximumColor === null) {
			return false;
		}

		return true;
	}
}
