<?php

/*
 *
 *  ____            _        _   __  __ _                  __  __ ____
 * |  _ \ ___   ___| | _____| |_|  \/  (_)_ __   ___      |  \/  |  _ \
 * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
 * |  __/ (_) | (__|   <  __/ |_| |  | | | | | |  __/_____| |  | |  __/
 * |_|   \___/ \___|_|\_\___|\__|_|  |_|_|_| |_|\___|     |_|  |_|_|
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * @author PocketMine Team
 * @link http://www.pocketmine.net/
 *
 *
 */

declare(strict_types=1);

namespace pocketmine\item;

use pocketmine\block\utils\DyeColor;
use pocketmine\color\Color;
use pocketmine\data\bedrock\DyeColorIdMap;
use pocketmine\data\bedrock\FireworkRocketTypeIdMap;
use pocketmine\data\SavedDataLoadingException;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\utils\Utils;
use function array_key_first;
use function chr;
use function count;
use function ord;
use function strlen;

class FireworkRocketExplosion{

	protected const TAG_TYPE = "FireworkType"; //TAG_Byte
	protected const TAG_COLORS = "FireworkColor"; //TAG_ByteArray
	protected const TAG_FADE_COLORS = "FireworkFade"; //TAG_ByteArray
	protected const TAG_TWINKLE = "FireworkFlicker"; //TAG_Byte
	protected const TAG_TRAIL = "FireworkTrail"; //TAG_Byte

	/**
	 * @throws SavedDataLoadingException
	 */
	public static function fromCompoundTag(CompoundTag $tag) : self{
		$colors = self::decodeColors($tag->getByteArray(self::TAG_COLORS));
		if(count($colors) === 0){
			throw new SavedDataLoadingException("Colors list cannot be empty");
		}

		return new self(
			FireworkRocketTypeIdMap::getInstance()->fromId($tag->getByte(self::TAG_TYPE)) ?? throw new SavedDataLoadingException("Invalid firework type"),
			$colors,
			self::decodeColors($tag->getByteArray(self::TAG_FADE_COLORS)),
			$tag->getByte(self::TAG_TWINKLE, 0) !== 0,
			$tag->getByte(self::TAG_TRAIL, 0) !== 0
		);
	}

	/**
	 * @return DyeColor[]
	 * @phpstan-return list<DyeColor>
	 * @throws SavedDataLoadingException
	 */
	protected static function decodeColors(string $colorsBytes) : array{
		$colors = [];

		$dyeColorIdMap = DyeColorIdMap::getInstance();
		for($i = 0, $len = strlen($colorsBytes); $i < $len; $i++){
			$colorByte = ord($colorsBytes[$i]);
			$color = $dyeColorIdMap->fromInvertedId($colorByte);
			if($color !== null){
				$colors[] = $color;
			}else{
				throw new SavedDataLoadingException("Unknown color $colorByte");
			}
		}

		return $colors;
	}

	/**
	 * @param DyeColor[] $colors
	 */
	protected static function encodeColors(array $colors) : string{
		$colorsBytes = "";

		$dyeColorIdMap = DyeColorIdMap::getInstance();
		foreach($colors as $color){
			$colorsBytes .= chr($dyeColorIdMap->toInvertedId($color) & 0xff);
		}

		return $colorsBytes;
	}

	/**
	 * @param DyeColor[] $colors
	 * @param DyeColor[] $fadeColors
	 * @phpstan-param non-empty-list<DyeColor> $colors
	 * @phpstan-param list<DyeColor> $fadeColors
	 */
	public function __construct(
		protected FireworkRocketType $type,
		protected array $colors,
		protected array $fadeColors = [],
		protected bool $twinkle = false,
		protected bool $trail = false
	){
		if(count($colors) === 0){
			throw new \InvalidArgumentException("Colors list cannot be empty");
		}

		$colorsValidator = function(DyeColor $_) : void{};

		Utils::validateArrayValueType($colors, $colorsValidator);
		Utils::validateArrayValueType($fadeColors, $colorsValidator);
	}

	public function getType() : FireworkRocketType{
		return $this->type;
	}

	/**
	 * Returns the colors of the particles.
	 *
	 * @return DyeColor[]
	 * @phpstan-return non-empty-list<DyeColor>
	 */
	public function getColors() : array{
		return $this->colors;
	}

	/**
	 * Returns the flash color of the explosion.
	 */
	public function getFlashColor() : DyeColor{
		return $this->colors[array_key_first($this->colors)];
	}

	/**
	 * Returns the mixure of colors from {@link FireworkRocketExplosion::getColors()})
	 */
	public function getColorMix() : Color{
		/** @var Color[] $colors */
		$colors = [];
		foreach($this->colors as $dyeColor){
			$colors[] = $dyeColor->getRgbValue();
		}
		return Color::mix(...$colors);
	}

	/**
	 * Returns the colors to which the particles will change their color after a few seconds.
	 * If it is empty, there will be no color change in the particles.
	 *
	 * @return DyeColor[]
	 * @phpstan-return list<DyeColor>
	 */
	public function getFadeColors() : array{
		return $this->fadeColors;
	}

	/**
	 * Returns whether the explosion has a flickering effect.
	 */
	public function willTwinkle() : bool{
		return $this->twinkle;
	}

	/**
	 * Returns whether the particles have a trail effect.
	 */
	public function getTrail() : bool{
		return $this->trail;
	}

	public function toCompoundTag() : CompoundTag{
		return CompoundTag::create()
			->setByte(self::TAG_TYPE, FireworkRocketTypeIdMap::getInstance()->toId($this->type))
			->setByteArray(self::TAG_COLORS, self::encodeColors($this->colors))
			->setByteArray(self::TAG_FADE_COLORS, self::encodeColors($this->fadeColors))
			->setByte(self::TAG_TWINKLE, $this->twinkle ? 1 : 0)
			->setByte(self::TAG_TRAIL, $this->trail ? 1 : 0);
	}
}