import decompress from "decompress";
import sharp from 'sharp';
sharp.cache({ files : 0 });

import util from 'node:util';
import { execFile as npExecFile } from 'child_process'; 
const execFile = util.promisify(npExecFile);

import fs from 'fs';
import path from 'path';

import Utils from "../Utils.js";

const collection = 'assets';

/**
 * Game objects manager, контролен клас за управление на игрови обекти
 */
class GameObjectsManager{
    name = 'gameObject';

    /**
     * Plugin initializer, инициализация на плъгин
     * @param {App} app The Application, обект приложение
     */
    init(app){
        const {db, config, am} = app;

        /**
         * Creates a game object, създаване на игрови обект
         * @param {Context} ctx Request context, контекст на заявката
         * @param {GameObject} data Asset data, данни за игровия обект
         */
        this.create = async function(ctx, data){
            data.id = await db.getId(collection);
            await db.create(collection, data);
            if (ctx.files?.file){
                await this.addFile(data, ctx.files.file)
                await fs.promises.unlink(ctx.files.file.path)
                await db.update(collection, {id: data.id}, data);
            }
            return data;
        }

        /**
         * Retrieves game object from database, прочитане на обект от базата от данни
         * @param {Number} id Game object ID, идентификатор на обекта
         * @returns {GameObject} The game object, игрови обект
         */
        this.read = async function(id){
            id = parseInt(id);
            return await db.get(collection, {id});
        }

        /**
         * Updates game object into the database, обновяване на игрови обект
         * @param {Context} ctx Request context, контекст на заявката
         * @param {GameObject} data Game object, данни за игровия обект
         */
        this.update = async function(ctx, data){
            data.id = parseInt(data.id);
            let object = await this.read(data.id);
            await am.addToHistory(object, collection, 'update');
            data = Object.assign(object, data);
            if (ctx.files?.file){
                await this.addFile(data, ctx.files.file)
                await fs.promises.unlink(ctx.files.file.path)
            }if (ctx.files?.thumb){
                await this.addThumb(data, ctx.files.thumb.path);
                await fs.promises.unlink(ctx.files.thumb.path)
            }
            await db.update(collection, {id: data.id}, data);
            return data;
        }

        /**
         * Removes game object from database, изтриване на игрови обект от базата от данни
         * @param {Number} id Game object ID, идентификатор на игровия обект
         */
        this.remove = async function(id){
            id = parseInt(id);
            await am.addToHistory(id, collection, 'delete');
            await db.remove(collection, {id});
        }

        /**
         * Assigns a file to a game object, закачване на файл към игрови обект
         * @param {GameObject} object Game object, игрови обект
         * @param {File} tmpFile A file, файл
         */
        this.addFile = async function(object, tmpFile){
            let i = tmpFile;
            let ofn = i.name;
            let ext = path.extname(ofn).toLowerCase();
            let src = `${config.fs.repo}/source/${object.id}${ext}`;
            let def = `${config.fs.repo}/default/${object.id}`;
            await fs.promises.copyFile(i.path, src);
            object.asset = {
                ofn,
                name: `${object.id}${ext}`
            }
            if (ext == '.zip'){
                let result = await decompress(src, def);
                object.asset.list = result.map(f=>f.path);
                object.asset.type = 'bundle';
                object.asset.name = `${object.id}/` + result.find(f=>f.path.endsWith('.gltf'))?.path;
            }else{
                object.asset.type = 'single';
                await fs.promises.copyFile(src, def + ext);
                if (['.jpg', '.png', '.webp', '.mp4', '.avi', '.webv', '.mp3'].includes(ext)){
                    await this.addThumb(object, src);
                }
            }
        }

        /**
         * Assigns a thumbnail to a game object, закачане на представително изображение към игрови обект
         * @param {GameObject} object Game object, игрови обект
         * @param {File} thumbSrc A thumbnail, представително изображение
         */
        this.addThumb = async function(object, thumbSrc){
            let ext = path.extname(thumbSrc).toLowerCase();
            //console.log(object, thumbSrc, ext);
            let dest = `${config.fs.repo}/thumb/${object.id}.webp`;
            object.asset.thumb = `${object.id}.webp`;
            if (['.jpg', '.png', '.webp'].includes(ext) || !ext){
                await sharp(thumbSrc).resize({height: 250}).toFile(dest);
            }else if (['.mp4', '.avi', '.webv'].includes(ext)){
                let frame = 1;
                await execFile('ffmpeg', [
                    '-i', thumbSrc, '-vf', `select=eq(n\\,${frame}),scale=-2:300`,
                     '-vframes', 1, '-f', 'image2', '-y', dest]);
            }else if (['.mp3'].includes(ext)){
                object.asset.thumb = `audio.webp`;
            }
        }

        /**
         * Returns list of GameObjects, търсене в игровите обекти по зададени критерии
         * @param {Object} query Query to DB, критерии - заявка към базата от данни
         * @returns {GameObject[]} Array of game objects, масив от игрови обекти
         */
        this.list = async function(query = {}){
            return await db.list(collection, {
                query,
                project: { name:1, id:1, type:1, asset:1},
                sort: { id: -1 }
            });
        }

        this.getTags = async function(q){
            let objects = await db.distinct(collection, 'tags', q ? {tags: {$regex: Utils.escapeRegExp(q), $options: 'i'}} : {});
            return objects;
        }
    }

    /**
     * Class starter, стартиране на класа
     * @param {App} app The application, базова апликация
     */
    async start(app){

    }
}

/**
 * GameObject entity, can be: panorama picture, 3d environment, 3d object, 2d object (picture), a player (3d), audio or video asset
 * Игрови обект, може да бъде панорамна снимка, триизмерна среда, обект или играч, двуизмерен обект, аудио или видео актив
 */
class GameObject {
    /**
     * Game object name, име на игровия обект
     * @type string
     */
    name = null;

    /**
     * Game object type, тип на игровия обект
     * @type string
     */
    type = null;

    /**
     * Associated file, асоцииран файл
     * @type File
     */
    file = null;
}

export { GameObjectsManager }