<?php

declare(strict_types=1);

namespace mirzaev\surikovlib\models;

use mirzaev\surikovlib\models\accounts_model as accounts;

use pdo;
use exception;

/**
 * Модель книг
 *
 * @package mirzaev\surikovlib\models
 * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
 */
final class books_model extends core
{
    /**
     * Запись в базу данных
     *
     * @param string $title Название
     * @param string|null $description Описание
     * @param int|null $account Аккаунт (идентификатор)
     * @param array &$errors Журнал ошибок
     *
     * @return int|null Идентификатор записанной книги
     */
    public static function write(string $title, ?string $description = null, ?int $account = null, array &$errors = []): ?int
    {
        // Инициализация журнала ошибок
        $errors['books'] ?? $errors['books'] = [];

        try {
            // Инициализация аккаунта
            $account = accounts::init($account, $errors);

            // Инициализация запроса
            $request = static::$db->prepare("INSERT INTO `books` (`account`, `title`, `description`) VALUES (:account, :title, :description)");

            // Инициализация параметров
            $params = [
                ':account' => $account->id,
                ':title' => $title,
                ':description' => $description
            ];

            // Отправка запроса
            $request->execute($params);

            if ($id = static::$db->lastInsertId()) {
                // Получен идентификатор загруженной книги (подразумевается)

                return (int) $id;
            }
        } catch (exception $e) {
            // Запись в журнал ошибок
            $errors['books'][] = [
                'text' => $e->getMessage(),
                'file' => $e->getFile(),
                'line' => $e->getLine(),
                'stack' => $e->getTrace()
            ];
        }

        return null;
    }

    /**
     * Чтение
     *
     * @param array $expression Выражение поиска
     * @param int $limit Ограничение по количеству
     * @param int $page Страница (для списка книг)
     * @param array &$errors Журнал ошибок
     *
     * @return array Книги
     */
    public static function read(array $expression = [], int $limit = 1, int $page = 1, array &$errors = []): array
    {
        // Инициализация журнала ошибок
        $errors['books'] ?? $errors['books'] = [];

        try {
            // Инициализация выражения поиска
            $where = 'WHERE ';

            // Инициализация параметров запроса
            $params = [];

            foreach ($expression as $parameter => $value) {
                // Перебор выражения поиска

                // Запись в строку запроса
                $where .= "`$parameter` = :$parameter &&";

                // Запись параметров запроса
                $params[":$parameter"] = $value;
            }

            // Очистка или реинициализация выражения поиска
            $where = empty($expression) ? '' : trim(trim($where, '&&'));

            // Инициализация страницы
            $page = $limit * --$page;

            // Инициализация запроса
            $request = static::$db->prepare("SELECT * FROM `books` $where LIMIT $page, $limit");

            // Отправка запроса
            $request->execute($params);

            return (array) $request->fetchAll(pdo::FETCH_ASSOC);
        } catch (exception $e) {
            // Запись в журнал ошибок
            $errors['books'][] = [
                'text' => $e->getMessage(),
                'file' => $e->getFile(),
                'line' => $e->getLine(),
                'stack' => $e->getTrace()
            ];
        }

        return [];
    }

    /**
     * Удаление из базы данных
     *
     * @param int $id Идентификатор
     * @param array &$errors Журнал ошибок
     *
     * @return bool Статус выполнения
     */
    public static function delete(int $id, array &$errors = []): bool
    {
        // Инициализация журнала ошибок
        $errors['books'] ?? $errors['books'] = [];

        try {
            // Инициализация запроса
            $request = static::$db->prepare("DELETE FROM `books` WHERE `id` = :id LIMIT 1");

            // Отправка запроса
            $request->execute([':id' => $id]);

            return true;
        } catch (exception $e) {
            // Запись в журнал ошибок
            $errors['books'][] = [
                'text' => $e->getMessage(),
                'file' => $e->getFile(),
                'line' => $e->getLine(),
                'stack' => $e->getTrace()
            ];
        }

        return false;
    }

    /**
     * Импорт
     *
     * @param array $books Книги (файлы)
     * @param int|null $account Аккаунт (идентификатор)
     * @param array &$errors Журнал ошибок
     *
     * @return array Записанные книги
     */
    public static function import(array $books, ?int $account = null, array &$errors = []): array
    {
        // Инициализация журнала ошибок
        $errors['books'] ?? $errors['books'] = [];

        try {
            if (empty($books)) {
                // Не найдены книги

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

            // Инициализация аккаунта
            $account = accounts::init($account, $errors);

            // Инициализация буфера инициализированных книг
            $initialized = [];

            for ($i = -1; count($books['name']) > ++$i;) {
                // Перебор загруженных книг

                // Генерация хеша файла
                $hash = hash_file('md5', $books['tmp_name'][$i]) ?? 0;

                if (move_uploaded_file($books['tmp_name'][$i], \STORAGE . DIRECTORY_SEPARATOR . 'temp' . DIRECTORY_SEPARATOR . $hash . '_' . $books['name'][$i])) {
                    // Загружен и перемещён из временной папки файл с книгой

                    // Извлечение имени файла

                    // Запись в буфер инициализированных книг
                    $initialized[] = [
                        'name' => preg_replace('/\.pdf/', '', $books['name'])[0],
                        'file' => $hash . '_' . $books['name'][$i]
                ];
                }
            }

            // Инициализация буфера записанных книг
            $writed = [];

            foreach ($initialized as $book) {
                // Перебор инициализированных книг

                try {
                    if ($id = static::write($book['name'], 'Без описания', $account->id ?? null, $errors)) {
                        // Записана в базу данных книга

                        // Инициализация пути до хранилища
                        $directory = \STORAGE . DIRECTORY_SEPARATOR . 'books' . DIRECTORY_SEPARATOR . $id . DIRECTORY_SEPARATOR;

                        // Инициализация директории
                        if (!file_exists($directory)) if (!mkdir($directory, 0755, true)) throw new exception('Не удалось записать директорию для книги');

                        // Инициализация пути до временного файла
                        $file = \STORAGE . DIRECTORY_SEPARATOR . 'temp' . DIRECTORY_SEPARATOR . $book['file'];

                        // Извлечение изображений из PDF-документа
                        exec("pdfimages -j '$file' '$directory'");

                        // Переименование файлов в необходимый формат
                        exec("echo 'export j=-1; for i in $directory*.jpg; do let j+=1; mv \$i $directory\$j.jpg; done' | bash");

                        // Запись в буфер записанных книг
                        $writed[] = $id;
                    } else {
                        // Не записана в базу данных книга

                        throw new exception('Не удалось записать книгу в базу данных', 500);
                    }
                } catch (exception $e) {
                    // Запись в журнал ошибок
                    $errors['books'][] = [
                        'text' => $e->getMessage(),
                        'file' => $e->getFile(),
                        'line' => $e->getLine(),
                        'stack' => $e->getTrace()
                    ];
                }
            }
        } catch (exception $e) {
            // Запись в журнал ошибок
            $errors['books'][] = [
                'text' => $e->getMessage(),
                'file' => $e->getFile(),
                'line' => $e->getLine(),
                'stack' => $e->getTrace()
            ];
        }

        return $writed ?? [];
    }

    /**
     * Чтение
     *
     * @param int $id Идентификатор
     * @param int $page Страница (сдвиг)
     * @param array &$errors Журнал ошибок
     *
     * @return bool Статус выполнения
     */
    public static function rotate(int $id, int $page = 1, array &$errors = []): bool
    {
        // Инициализация журнала ошибок
        $errors['books'] ?? $errors['books'] = [];

        try {
            // Инициализация запроса
            $request = static::$db->prepare("SELECT EXISTS (SELECT * FROM `books` WHERE `id` = :id LIMIT 1)");

            // Отправка запроса
            $request->execute([':id' => $id]);

            if ($request->fetch(pdo::FETCH_NUM)[0] === 1) {
                // Найдена книга

                // Инициализация пути книги
                $book = \STORAGE . DIRECTORY_SEPARATOR . 'books' . DIRECTORY_SEPARATOR . $id;

                // Инициализация пути до файла
                $file = $book . DIRECTORY_SEPARATOR . "$page.jpg";

                // Инициализация пути до нового местоположения файла
                $old = $book . DIRECTORY_SEPARATOR . 'old' . DIRECTORY_SEPARATOR . "$page.jpg";

                // Инициализация директории
                if (!file_exists($book)) throw new exception('Не удалось найти директорию книги');

                // Инициализация директории оригинальных изображений
                if (!file_exists($book . DIRECTORY_SEPARATOR . 'old')) if (!mkdir($book . DIRECTORY_SEPARATOR . 'old', 0755, true)) throw new exception('Не удалось записать директорию для оригинальных страниц книги');

                // Перемещение страницы в директорию оригинальных страниц
                exec("mv $file $old");

                // Переворачивание файла и возвращение на нужное местоположение
                exec("jpegtran -rotate 90 $old > $file");

                return true;
            }
        } catch (exception $e) {
            // Запись в журнал ошибок
            $errors['books'][] = [
                'text' => $e->getMessage(),
                'file' => $e->getFile(),
                'line' => $e->getLine(),
                'stack' => $e->getTrace()
            ];
        }

        return false;
    }

    /**
     * Подсчёт количества страниц
     *
     * @param int $id Идентификатор
     * @param array &$errors Журнал ошибок
     *
     * @return int|null Количество страниц
     */
    public static function amount(int $id, array &$errors = []): ?int
    {
        // Инициализация журнала ошибок
        $errors['books'] ?? $errors['books'] = [];

        try {
            // Инициализация счётчика
            $amount = -1;

            while (true) {
                // Перебор директорий (!!! Рекурсия !!!)

                // Перебор изображений по возрастанию (от 0.jpg до 999.jpg и т.д.)
                if (!file_exists(\STORAGE . DIRECTORY_SEPARATOR . 'books' . DIRECTORY_SEPARATOR . $id . DIRECTORY_SEPARATOR . ++$amount . '.jpg')) return $amount;
            }
        } catch (exception $e) {
            // Запись в журнал ошибок
            $errors['books'][] = [
                'text' => $e->getMessage(),
                'file' => $e->getFile(),
                'line' => $e->getLine(),
                'stack' => $e->getTrace()
            ];
        }

        return null;
    }
}
