<?php

declare(strict_types=1);

namespace mirzaev\huesos\models;

// Files of the project
use mirzaev\huesos\models\core,
	mirzaev\huesos\models\product,
	mirzaev\huesos\models\category,
	mirzaev\huesos\models\entry,
	mirzaev\huesos\models\traits\files,
	mirzaev\huesos\models\enumerations\language,
	mirzaev\huesos\models\enumerations\currency,
	mirzaev\huesos\models\traits\yandex\disk as yandex;

// Framework for PHP
use mirzaev\minimal\http\enumerations\content;

// Framework for ArangoDB
use mirzaev\arangodb\collection,
	mirzaev\arangodb\document;

// Framework for Excel
use avadim\FastExcelReader\Excel as excel;

// Built-in libraries
use exception;

// GD library
use GdImage as image;

/**
 * Model of the catalog
 *
 * @package mirzaev\huesos\models
 *
 * @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
 * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
 */
final class catalog extends core
{
	use yandex, files {
		yandex::download as yandex;
	}

	/**
	 * Collect parameter from all products
	 *
	 * @param string $documment Path to the EXCEL-document
	 * @param int &$categories_loaded Counter of loaded categories
	 * @param int &$categories_created Counter of created categories
	 * @param int &$categories_updated Counter of updated categories
	 * @param int &$categories_deleted Counter of deleted categories
	 * @param int &$categories_new Counter of new categories
	 * @param int &$categories_old Counter of old categories
	 * @param int &$products_loaded Counter of loaded products
	 * @param int &$products_created Counter of created products
	 * @param int &$products_updated Counter of updated products
	 * @param int &$products_deleted Counter of deleted products
	 * @param int &$products_new Counter of new products
	 * @param int &$products_old Counter of old products
	 * @param language $language Language
	 * @param array &$errors Registry of errors
	 *
	 * @return void
	 *
	 * @todo 
	 * 1. Сначала создать все категории и затем снова по циклу пройтись уже создавать entry между ними
	 * 2. Сжимать изображения
	 */
	public static function import(
		string $document,
		int &$categories_loaded = 0,
		int &$categories_created = 0,
		int &$categories_updated = 0,
		int &$categories_deleted = 0,
		int &$categories_old = 0,
		int &$categories_new = 0,
		int &$products_loaded = 0,
		int &$products_created = 0,
		int &$products_updated = 0,
		int &$products_deleted = 0,
		int &$products_old = 0,
		int &$products_new = 0,
		language $language = language::en,
		currency $currency = currency::usd,
		array &$errors = []
	): void {
		try {
			// Initializing the spreadsheet
			$spreadsheet = excel::open($document);

			// Inititalizing worksheets
			$categories = $spreadsheet->getSheet('Категории');
			$products = $spreadsheet->getSheet('Товары');

			// Counting old documents
			$categories_old = collection::count(category::COLLECTION, errors: $errors);
			$products_old = collection::count(product::COLLECTION, errors: $errors);

			// Initializing the buffer of handler categories and products
			$handled = [
				'categories' => [],
				'products' => []
			];

			foreach (
				$categories->nextRow(
					[
						'A' => 'identifier',
						'B' => 'name',
						'C' => 'category',
						'D' => 'images',
						'E' => 'position'
					],
					excel::KEYS_FIRST_ROW
				) as $number => $row
			) {
				// Iterate over categories

				try {
					if (!empty($row['identifier']) && !empty($row['name'])) {
						// Required cells are filled in

						// Incrementing the counter of loaded categories
						++$categories_loaded;

						// Declaring the variable with the status that a new category has been created
						$created = false;

						// Declaring the variable with the category
						$category = null;

						// Initializing the category
						$category = category::_read('d.identifier == @identifier', parameters: ['identifier' => (int) $row['identifier']], errors: $errors);

						if ($category instanceof category) {
							// Initialized the category

							// Initializing name of the category
							if (empty($category->name) || empty($category->name[$language->name]) || $category->name[$language->name] !== $row['name'])
								$category->name = [$language->name => $row['name']] + ($category->name ?? []);

							// Initializing position of the category
							if (empty($category->position) || $category->position === $row['position'])
								$category->position = $row['position'];
						} else {
							// Not initialized the category

							// Creating the category
							$_id = $created = category::write(
								identifier: (int) $row['identifier'],
								name: [$language->name => $row['name']],
								position: (int) $row['position'] ?? null,
								errors: $errors
							);

							// Initializing the category
							$category = category::_read(
								filter: 'd._id == @_id',
								parameters: ['_id' => $_id],
								errors: $errors
							);

							// Incrementing the counter of created categories
							if ($created) ++$categories_created;
						};

						if ($category instanceof category) {
							// Initialized the category

							if (!empty($row['category'])) {
								// Received the ascendant category

								// Initializing the ascendant category
								$ascendant = category::_read(
									filter: 'd.identifier == @identifier',
									parameters: ['identifier' => (int) $row['category']],
									errors: $errors
								);

								if ($ascendant instanceof category) {
									// Found the ascendant category

									// Deleting entries of the category in ArangoDB
									entry::banish($category, $errors);

									// Writing the category as an entry to the ascendant category in ArangoDB
									entry::write($category, $ascendant, $errors);
								}
							}

							if (!empty($row['images'])) {
								// Received images

								// Initializing new images of the category
								$images = explode(' ', mb_trim($row['images']));

								// Reinitialize images? (true, if no images found or their amount does not match)
								/* $reinitialize = !$category->images || count($category->images) !== count($images); */
								$reinitialize = true;

								// Checking the identity of existing images with new images (if reinitialization of images has not yet been requested)
								/* if (!$reinitialize)	foreach ($category->images as $key => $image) if ($reinitialize = $image['source'] !== $images[$key]) break; */

								if ($reinitialize) {
									// Requested reinitialization of images

									// Initializing the buffer of images
									$buffer = [];

									foreach ($images as $index => $file) {
										// Iterating over new images

										// Skipping empty URI`s
										if (empty($file = mb_trim($file))) continue;

										// Initializing path to directory of images in storage
										$directory = DIRECTORY_SEPARATOR . 'categories' . DIRECTORY_SEPARATOR . $row['identifier'] . DIRECTORY_SEPARATOR . $index;

										// Initializing URL of the image in storage
										$url = STORAGE . $directory;

										// Initializing the directory in storage
										if (!file_exists($url))	mkdir($url, 0775, true);

										if ($downloaded = static::yandex(
											uri: $file,
											destination: $url,
											name: 'source',
											errors: $errors
										)) {
											// The image is downloaded and initialized data of the image in storage

											// Initializing URI of the image
											$uri = $downloaded['destination'] . DIRECTORY_SEPARATOR . $downloaded['name'] . '.' . $downloaded['content']->extension();

											// Initializing size of the image
											$size = getimagesize($uri);

											if ($downloaded['content'] === content::png) {
												// PNG

												// Initializing implementator of the image
												$boba = imagecreatefrompng($uri);
											} else if ($downloaded['content'] === content::jpeg) {
												// JPEG

												// Initializing implementator of the image
												$boba = imagecreatefromjpeg($uri);
											}

											// Enabling better antialiasing
											imageantialias($boba, true);

											if ($boba instanceof image) {
												// Initialized implementator of the image

												// Initializing buffer of resized images
												$resized = [];

												foreach ([1400, 800, 400, 200] as $resize) {
													// Iterating over sizes for creating images

													if ($size[0] >= $size[1]) {
														// The width ($size[0]) is longer than the height ($size[1])

														$width = $resize;
														$height = (int) ($resize * $size[1] / $size[0]);
													} else {
														// The height ($size[1]) is longer than the width ($size[0])

														$width = (int) ($resize * $size[0] / $size[1]);
														$height = $resize;
													}

													// Resizing the image
													$biba = imagecreatetruecolor($width, $height);
													imagecopyresampled($biba, $boba, 0, 0, 0, 0, $width, $height, $size[0], $size[1]);

													// Initializing URI of the resized image
													$uri = $directory . DIRECTORY_SEPARATOR . "$resize." . $downloaded['content']->extension();

													// Saving the image
													imagePng($biba, STORAGE . $uri);

													// Writing the resized image to the buffer of resized images
													$resized[$resize] = $uri;
												}

												// Writing the image to the buffer if images
												$buffer[] = [
													'source' => $file,
													'storage' => [
														'source' => $directory .  DIRECTORY_SEPARATOR . $downloaded['name'] . '.' . $downloaded['content']->extension(),
													] + $resized
												];
											}
										}
									}

									// Initializing images of the category
									$category->images = $buffer;
								}
							}

							// Writing in ArangoDB
							$updated = document::update($category->__document(), errors: $errors);

							// Incrementing the counter of updated categories
							if ($updated && !$created) ++$categories_updated;
						} else throw new exception('Failed to initialize category: ' . $row['name'] . " ($number)");
					}

					// Writing to the registry of handled categories and products
					$handled['categories'][] = $row['identifier'];
				} catch (exception $e) {
					// Writing to the registry of errors
					$errors[] = [
						'text' => $e->getMessage(),
						'file' => $e->getFile(),
						'line' => $e->getLine(),
						'stack' => $e->getTrace()
					];
				}
			}

			foreach (
				$products->nextRow(
					[
						'A' => 'identifier',
						'B' => 'name',
						'C' => 'category',
						'D' => 'description',
						'E' => 'cost',
						'F' => 'weight',
						'G' => 'x',
						'H' => 'y',
						'I' => 'z',
						'J' => 'brand',
						'K' => 'compatibility',
						'L' => 'images',
						'M' => 'position'
					],
					excel::KEYS_FIRST_ROW
				) as $number => $row
			) {
				// Iterate over products

				try {
					if (!empty($row['identifier']) && !empty($row['name'])) {
						// Required cells are filled in

						// Incrementing the counter of loaded products
						++$products_loaded;

						// Declaring the variable with the status that a new product has been created
						$created = false;

						// Declaring the variable with the product
						$product = null;

						// Initializing the product
						$product = product::_read('d.identifier == @identifier', parameters: ['identifier' => (int) $row['identifier']], errors: $errors);

						if ($product instanceof product) {
							// Initialized the product

							// Initializing name of the product
							if (empty($product->name) || empty($product->name[$language->name]) || $product->name[$language->name] !== $row['name'])
								$product->name = [$language->name => $row['name']] + ($product->name ?? []);

							// Initializing description of the product
							if (empty($product->description) || empty($product->description[$language->name]) || $product->description[$language->name] !== $row['description'])
								$product->description = [$language->name => $row['description']] + ($product->description ?? []);

							// Initializing brand of the product
							if (empty($product->brand) || empty($product->brand[$language->name]) || $product->brand[$language->name] !== $row['brand'])
								$product->brand = [$language->name => $row['brand']] + ($product->brand ?? []);

							// Initializing compatibility of the product
							if (empty($product->compatibility) || empty($product->brand[$language->name]) || $product->compatibility[$language->name] !== $row['compatibility'])
								$product->compatibility = [$language->name => $row['compatibility']] + ($product->compatibility ?? []);

							// Initializing position of the product
							if (empty($product->position) || $product->position !== $row['position'])
								$product->position = $row['position'];
						} else {
							// Not initialized the product

							// Creating the product
							$_id = product::write(
								identifier: (int) $row['identifier'],
								name: [$language->name => $row['name']],
								description: [$language->name => $row['description']],
								cost: [$currency->name => (float) $row['cost']],
								weight: (float) $row['weight'],
								dimensions: ['x' => $row['x'], 'y' => $row['y'], 'z' => $row['z']],
								brand: [$language->name => $row['brand']],
								compatibility: [$language->name => $row['compatibility']],
								position: (int)	$row['position'] ?? null,
								errors: $errors
							);

							// Initializing the product
							$product = $created = product::_read('d._id == @_id', parameters: ['_id' => $_id], errors: $errors);

							// Incrementing the counter of created products
							if ($created) ++$products_created;
						}

						if ($product instanceof product) {
							// Initialized the product

							if (!empty($row['category'])) {
								// Received the category

								// Initializing the category
								$category = category::_read(
									filter: sprintf('d.identifier == %u', (int) $row['category']),
									errors: $errors
								);

								if ($category instanceof category) {
									// Found the ascendant category

									// Deleting entries of the product in ArangoDB
									entry::banish($product, $errors);

									// Writing the product as an entry to the ascendant category in ArangoDB
									entry::write($product, $category, $errors);
								}
							}

							if (!empty($row['images'])) {
								// Received images

								// Initializing new images of the category
								$images = explode(' ', mb_trim($row['images']));

								// Reinitialize images? (true, if no images found or their amount does not match)
								/* $reinitialize = !$product->images || count($product->images) !== count($images); */
								$reinitialize = true;

								// Checking the identity of existing images with new images (if reinitialization of images has not yet been requested)
								/* if (!$reinitialize)	foreach ($product->images as $key => $image) if ($reinitialize = $image['source'] !== $images[$key]) break; */

								if ($reinitialize) {
									// Requested reinitialization of images

									// Initializing the buffer of images
									$buffer = [];

									foreach ($images as $index => $file) {
										// Iterating over new images

										// Skipping empty URI`s
										if (empty($file = mb_trim($file))) continue;

										// Initializing path to directory of images in storage
										$directory = DIRECTORY_SEPARATOR . 'products' . DIRECTORY_SEPARATOR . $row['identifier'] . DIRECTORY_SEPARATOR . $index;

										// Initializing URL of the image in storage
										$url = STORAGE . $directory;

										// Initializing the directory in storage
										if (!file_exists($url))	mkdir($url, 0775, true);

										if ($downloaded = static::yandex(
											uri: $file,
											destination: $url,
											name: 'source',
											errors: $errors
										)) {
											// The image is downloaded and initialized data of the image in storage

											// Initializing URI of the image
											$uri = $downloaded['destination'] . DIRECTORY_SEPARATOR . $downloaded['name'] . '.' . $downloaded['content']->extension();

											// Initializing size of the image
											$size = getimagesize($uri);

											if ($downloaded['content'] === content::png) {
												// PNG

												// Initializing implementator of the image
												$boba = imagecreatefrompng($uri);
											} else if ($downloaded['content'] === content::jpeg) {
												// JPEG

												// Initializing implementator of the image
												$boba = imagecreatefromjpeg($uri);
											}

											// Enabling better antialiasing
											imageantialias($boba, true);

											if ($boba instanceof image) {
												// Initialized implementator of the image

												// Initializing buffer of resized images
												$resized = [];

												foreach ([1400, 800, 400, 200] as $resize) {
													// Iterating over sizes for creating images

													if ($size[0] >= $size[1]) {
														// The width ($size[0]) is longer than the height ($size[1])

														$width = $resize;
														$height = (int) ($resize * $size[1] / $size[0]);
													} else {
														// The height ($size[1]) is longer than the width ($size[0])

														$width = (int) ($resize * $size[0] / $size[1]);
														$height = $resize;
													}

													// Resizing the image
													$biba = imagecreatetruecolor($width, $height);
													imagecopyresampled($biba, $boba, 0, 0, 0, 0, $width, $height, $size[0], $size[1]);

													// Initializing URI of the resized image
													$uri = $directory . DIRECTORY_SEPARATOR . "$resize." . $downloaded['content']->extension();

													// Saving the image
													imagePng($biba, STORAGE . $uri);

													// Writing the resized image to the buffer of resized images
													$resized[$resize] = $uri;
												}

												// Writing the image to the buffer if images
												$buffer[] = [
													'source' => $file,
													'storage' => [
														'source' => $directory .  DIRECTORY_SEPARATOR . $downloaded['name'] . '.' . $downloaded['content']->extension(),
													] + $resized
												];
											}
										}
									}

									// Initializing images of the product
									$product->images = $buffer;
								}
							}

							// Writing in ArangoDB
							$updated = document::update($product->__document(), errors: $errors);

							// Incrementing the counter of updated products
							if ($updated && !$created) ++$products_updated;
						} else throw new exception('Failed to initialize product: ' . $row['name'] . " ($number)");
					}

					// Writing to the registry of handled categories and products
					$handled['products'][] = $row['identifier'];
				} catch (exception $e) {
					// Writing to the registry of errors
					$errors[] = [
						'text' => $e->getMessage(),
						'file' => $e->getFile(),
						'line' => $e->getLine(),
						'stack' => $e->getTrace()
					];
				}
			}

			// Deleting old categories
			foreach (
				category::_read(
					/* filter: sprintf('%s - d.updated > 3600', time()), */
					sort: 'd.updated DESC',
					amount: 100000,
					errors: $errors
				) ?? [] as $document
			) {
				// Iterating over categories

				// Initializing the category
				$category = new category(document: $document);

				if (
					$category instanceof category
					&& array_search($category->identifier, $handled['categories']) === false
				) {
					// Not found identifier of the category in the buffer of handled categories and products

					// Deleting images of the category from storage
					static::delete(
						directory: STORAGE . DIRECTORY_SEPARATOR . 'categories' . DIRECTORY_SEPARATOR . $category->identifier,
						errors: $errors
					);

					// Deleting entries of the category in ArangoDB
					entry::banish(
						document: $category,
						errors: $errors
					);

					// Deleting the category in ArangoDB
					document::delete(
						document: $category->__document(),
						errors: $errors
					);

					// Incrementing the counter of deleted categories
					++$categories_deleted;
				}
			}

			// Deleting old products
			foreach (
				product::_read(
					/* filter: sprintf('%s - d.updated > 3600', time()), */
					sort: 'd.updated DESC',
					amount: 100000,
					errors: $errors
				) ?? [] as $document
			) {
				// Iterating over products

				// Initializing the category
				$product = new product(document: $document);

				if (
					$product instanceof product
					&& array_search($product->identifier, $handled['products']) === false
				) {
					// Not found identifier of the product in the buffer of handled categories and products

					// Deleting images of the product from storage
					static::delete(
						directory: STORAGE . DIRECTORY_SEPARATOR . 'products' . DIRECTORY_SEPARATOR . $product->identifier,
						errors: $errors
					);

					// Deleting entries of the product in ArangoDB
					entry::banish(
						document: $product,
						errors: $errors
					);

					// Deleting the product in ArangoDB
					document::delete(
						document: $product->__document(),
						errors: $errors
					);

					// Incrementing the counter of deleted products
					++$products_deleted;
				}
			}

			// Counting new documents
			$categories_new = collection::count(collection: category::COLLECTION, errors: $errors);
			$products_new = collection::count(collection: product::COLLECTION, errors: $errors);
		} catch (exception $e) {
			// Writing to the registry of errors
			$errors[] = [
				'text' => $e->getMessage(),
				'file' => $e->getFile(),
				'line' => $e->getLine(),
				'stack' => $e->getTrace()
			];
		}
	}
}
