<?php

declare(strict_types=1);

namespace kodorvan\surikov\models;

// Files of the project
use kodorvan\surikov\models\account;

// Built-in libraries
use pdo,
exception;

/**
 * Book model
 *
 * @package kodorvan\surikov\models
 *
 * @param
 *
 * @method static int|null write(string $title, ?string $description, ?int $account, array &$errors) Write
 *
 * @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
 * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
 */
final class book extends core
{
	/**
	 * Write
	 *
	 * Write into the database
	 *
	 * @param string $title Title
	 * @param string|null $description Description
	 * @param int|null $account Identifier of the account
	 * @param array &$errors Registry of errors
	 *
	 * @return int|null The book identifier
	 */
	public static function write(string $title, ?string $description = null, ?int $account = null, array &$errors = []): ?int
	{
		try {
			// Initializing the account
			$account = account::initialize($account, $errors);

			// Initializing the request
			$request = static::$database->prepare("INSERT INTO `books` (`account`, `title`, `description`) VALUES (:account, :title, :description)");

			// Initializing the request parameters
			$parameters = [
				':account' => $account->identifier,
				':title' => $title,
				':description' => $description
			];

			// Sending the request
			$request->execute($parameters);

			if ($id = static::$database->lastInsertId()) {
				// Received the created book identifier

				// Exit (success)
				return (int) $id;
			}
		} catch (exception $e) {
			// Exception

			// Writing into the errors registry
			$errors[] = [
				'text' => $e->getMessage(),
				'file' => $e->getFile(),
				'line' => $e->getLine(),
				'stack' => $e->getTrace()
			];
		}

		// Exit (fail)
		return null;
	}

	/**
	 * Read
	 *
	 * Read books from the database
	 *
	 * @param array $expressions Request expressions
	 * @param int $limit Amount
	 * @param int $page Page (offset by $limit)
	 * @param array &$errors Registry of errors
	 *
	 * @return array|false Books
	 */
	public static function read(array $expressions = [], int $limit = 1, int $page = 1, array &$errors = []): array|false
	{
		try {
			// Initializing the request row
			$row = 'WHERE ';

			// Initializing the request parameters
			$parameters = [];

			foreach ($expressions as $name => $value) {
				// Iterating over the request expressions

				// Generating the request row
				$row .= "`$name` = :$name &&";

				// Generating the request parameters
				$parameters[":$name"] = $value;
			}

			// Cleaning the request row
			$row = empty($expressions) ? '' : trim(trim($row, '&&'));

			// Initializing the page
			$page = $limit * --$page;

			// Initializing the request
			$request = static::$database->prepare("SELECT * FROM `books` $row LIMIT $page, $limit");

			// Sending the request
			$request->execute($parameters);

			// Exit (success)
			return (array) $request->fetchAll(pdo::FETCH_ASSOC);
		} catch (exception $e) {
			// Exception

			// Writing into the errors registry
			$errors[] = [
				'text' => $e->getMessage(),
				'file' => $e->getFile(),
				'line' => $e->getLine(),
				'stack' => $e->getTrace()
			];
		}

		// Exit (fail)
		return false;
	}

	/**
	 * Delete
	 *
	 * Delete the book from the database
	 *
	 * @param int $identifier Identifier
	 * @param array &$errors Registry of errors
	 *
	 * @return bool Is the book was deleted?
	 */
	public static function delete(int $identifier, int $active, array &$errors = []): bool
	{
		try {
			// Initializing the request
			$request = static::$database->prepare("UPDATE `books` SET `active` = :active WHERE `identifier` = :identifier LIMIT 1");
			// Sending the request
			$request->execute([
				':identifier' => $identifier,
				':active' => $active,
			]);

			// Exit (success)
			return true;
		} catch (exception $e) {
			// Exception

			// Writing into the errors registry
			$errors[] = [
				'text' => $e->getMessage(),
				'file' => $e->getFile(),
				'line' => $e->getLine(),
				'stack' => $e->getTrace()
			];
		}

		// Exit (fail)
		return false;
	}

	/**
	 * Import
	 *
	 * Import books into the storage and database
	 *
	 * @param array $files Books
	 * @param int|null $account Identifier of the account
	 * @param array &$errors Registry of errors
	 *
	 * @return array|false Imported books
	 */
	public static function import(array $files, ?int $account = null, array &$errors = []): array|false
	{
		try {

			if (empty($files)) {
				// Not received books

				// Exit (fail)
				throw new exception('Не найдены книги для записи');
			}

			// Initializing the account
			$account = account::initialize($account, $errors);

			// Declaring the buffer of saved books
			$saved = [];

			for ($i = -1; count($files['name']) > ++$i; ) {
				// Iterating over books

				// Generating the book file hash
				$hash = hash_file('md5', $files['tmp_name'][$i]) ?? 0;

				if (move_uploaded_file($files['tmp_name'][$i], \STORAGE . DIRECTORY_SEPARATOR . 'temp' . DIRECTORY_SEPARATOR . $hash . '_' . $files['name'][$i])) {
					// Saved the book file

					// Writing into the buffer of saved books
					$saved[] = [
						'name' => preg_replace('/\.pdf/', '', $files['name'])[0],
						'file' => $hash . '_' . $files['name'][$i]
					];
				}
			}

			// Declaring buffer of writed books
			$writed = [];

			foreach ($saved as $book) {
				// Iterating over saved books

				try {
					if ($identifier = static::write(title: $book['name'], description: 'Без описания', account: $account->identifier ?? null, errors: $errors)) {
						// Writed book into the database

						// Initializing the book directory path
						$directory = \STORAGE . DIRECTORY_SEPARATOR . 'books' . DIRECTORY_SEPARATOR . $identifier . DIRECTORY_SEPARATOR;

						if (!file_exists($directory)) {
							// Not found the book directory

							if (!mkdir($directory, 0755, true)) {
								// Failed to create the book directory

								// Skipping (fail)
								throw new exception('Не удалось записать директорию для книги');
							}
						}

						// Initializing the book file path
						$file = \STORAGE . DIRECTORY_SEPARATOR . 'temp' . DIRECTORY_SEPARATOR . $book['file'];

						// Extracting images from the PDF document
						exec("pdfimages -j '$file' '$directory'");

						// Renaming files
						exec("echo 'export j=0; for i in $directory*.jpg; do let j+=1; mv \$i $directory\$j.jpg; done' | bash");

						// Writing into the buffer of writed books
						$writed[] = $identifier;
					} else {
						// Not writed into the database

						// Skipping (fail)
						throw new exception('Не удалось записать книгу в базу данных', 500);
					}
				} catch (exception $e) {
					// Exception

					// Writing into the errors registry
					$errors[] = [
						'text' => $e->getMessage(),
						'file' => $e->getFile(),
						'line' => $e->getLine(),
						'stack' => $e->getTrace()
					];
				}
			}

			// Exit (success)
			return $writed;
		} catch (exception $e) {
			// Exception

			// Writing into the errors registry
			$errors[] = [
				'text' => $e->getMessage(),
				'file' => $e->getFile(),
				'line' => $e->getLine(),
				'stack' => $e->getTrace()
			];
		}

		// Exit (fail)
		return false;
	}

	/**
	 * Rotate
	 *
	 * Rotate the book page image
	 *
	 * @param int $identifier Identifier
	 * @param int $page Page (offset)
	 * @param array &$errors Registry of errors
	 *
	 * @return bool Is the book page was rotated?
	 */
	public static function rotate(int $identifier, int $page = 1, array &$errors = []): bool
	{
		try {
			// Initializing the request
			$request = static::$database->prepare("SELECT EXISTS (SELECT * FROM `books` WHERE `identifier` = :identifier LIMIT 1)");

			// Sending the request
			$request->execute([':identifier' => $identifier]);

			if ($request->fetch(pdo::FETCH_NUM)[0] === 1) {
				// Found the book

				// Initializing the book directory path
				$directory = \STORAGE . DIRECTORY_SEPARATOR . 'books' . DIRECTORY_SEPARATOR . $identifier;

				// Initializing the book file path
				$file = $directory . DIRECTORY_SEPARATOR . "$page.jpg";

				// Initializing the book file new location path
				$old = $directory . DIRECTORY_SEPARATOR . 'old' . DIRECTORY_SEPARATOR . "$page.jpg";

				if (!file_exists($directory)) {
					// Not found the book directory

					// Exit (fail)
					throw new exception('Не удалось найти директорию книги');
				}

				// Инициализация директории оригинальных изображений
				if (!file_exists($directory . DIRECTORY_SEPARATOR . 'old')) {
					// Not found the directory for deprecated files

					if (!mkdir($directory . DIRECTORY_SEPARATOR . 'old', 0755, true)) {
						// Failed to create the directory for deprecated files

						// Exit (fail)
						throw new exception('Failed to create the directory for deprecated files');
					}
				}

				// Moving the deprecated file
				exec("mv $file $old");

				// Rotating the deprecated file and moving back
				exec("jpegtran -rotate 90 $old > $file");

				// Exit (success)
				return true;
			}
		} catch (exception $e) {
			// Exception

			// Writing into the errors registry
			$errors[] = [
				'text' => $e->getMessage(),
				'file' => $e->getFile(),
				'line' => $e->getLine(),
				'stack' => $e->getTrace()
			];
		}
		// Exit (fail)
		return false;
	}


	/**
	 * Amount
	 *
	 * Calculate amount of pages in the book storage
	 *
	 * @param int $identifier Identifier
	 * @param array &$errors Registry of errors
	 *
	 * @return int|false Amount of pages
	 */
	public static function amount(int $id, array &$errors = []): int|false
	{
		try {
			// Initializing the iterator
			$page = 0;

			while (true) {
				// Iterating over the book pages files in ascending order (from 0.jpg to 999.jpg and more) (!!! recursion !!!)

				if (!file_exists(\STORAGE . DIRECTORY_SEPARATOR . 'books' . DIRECTORY_SEPARATOR . $id . DIRECTORY_SEPARATOR . $page++ . '.jpg')) {
					// Not found the book page by the iterator

					// Exit (success) (!!! exit from the recursion !!!)
					return $page;
				}
			}
		} catch (exception $e) {
			// Exception

			// Writing into the errors registry
			$errors[] = [
				'text' => $e->getMessage(),
				'file' => $e->getFile(),
				'line' => $e->getLine(),
				'stack' => $e->getTrace()
			];
		}

		// Exit (fail)
		return false;
	}
}
