const fs = require('fs-extra');
const sharp = require('sharp');
const path = require('path');
const readChunk = require('read-chunk');
const imageType = require('image-type');
const bmp = require('bmp-js');

module.exports = Self => {
    require('../methods/image/download')(Self);
    require('../methods/image/upload')(Self);

    // Function extracted from jimp package (utils)
    function scan(image, x, y, w, h, f) {
        // round input
        x = Math.round(x);
        y = Math.round(y);
        w = Math.round(w);
        h = Math.round(h);

        for (let _y = y; _y < y + h; _y++) {
            for (let _x = x; _x < x + w; _x++) {
                const idx = (image.bitmap.width * _y + _x) << 2;
                f.call(image, _x, _y, idx);
            }
        }

        return image;
    }

    // Function extracted from jimp package (type-bmp)
    function fromAGBR(bitmap) {
        return scan({bitmap}, 0, 0, bitmap.width, bitmap.height, function(
            x,
            y,
            index
        ) {
            const alpha = this.bitmap.data[index + 0];
            const blue = this.bitmap.data[index + 1];
            const green = this.bitmap.data[index + 2];
            const red = this.bitmap.data[index + 3];

            this.bitmap.data[index + 0] = red;
            this.bitmap.data[index + 1] = green;
            this.bitmap.data[index + 2] = blue;
            this.bitmap.data[index + 3] = bitmap.is_with_alpha ? alpha : 0xff;
        }).bitmap;
    }

    Self.registerImage = async(collectionName, srcFilePath, fileName, entityId) => {
        const models = Self.app.models;
        const tx = await Self.beginTransaction({});
        const myOptions = {transaction: tx};

        try {
            const collection = await models.ImageCollection.findOne({
                fields: [
                    'id',
                    'name',
                    'maxWidth',
                    'maxHeight',
                    'model',
                    'property'
                ],
                where: {name: collectionName},
                include: {
                    relation: 'sizes',
                    scope: {
                        fields: ['width', 'height', 'crop']
                    }
                }
            }, myOptions);

            const data = {
                name: fileName,
                collectionFk: collectionName
            };
            const newImage = await Self.upsertWithWhere(data, {
                name: fileName,
                collectionFk: collectionName,
                updated: (new Date).getTime()
            }, myOptions);

            // Resizes and saves the image
            const container = await models.ImageContainer.container(collectionName);
            const rootPath = container.client.root;
            const collectionDir = path.join(rootPath, collectionName);
            const file = `${fileName}.png`;
            const dstDir = path.join(collectionDir, 'full');
            const dstFile = path.join(dstDir, file);

            const buffer = readChunk.sync(srcFilePath, 0, 12);
            const type = imageType(buffer);

            let sharpOptions;
            let imgSrc = srcFilePath;
            if (type.mime == 'image/bmp') {
                const bmpBuffer = fs.readFileSync(srcFilePath);
                const bmpData = fromAGBR(bmp.decode(bmpBuffer));
                imgSrc = bmpData.data;
                sharpOptions = {
                    raw: {
                        width: bmpData.width,
                        height: bmpData.height,
                        channels: 4
                    }
                };
            }

            const resizeOpts = {
                withoutEnlargement: true,
                fit: 'inside'
            };

            await fs.mkdir(dstDir, {recursive: true});
            await sharp(imgSrc, sharpOptions)
                .resize(collection.maxWidth, collection.maxHeight, resizeOpts)
                .png()
                .toFile(dstFile);

            const sizes = collection.sizes();
            for (let size of sizes) {
                const dstDir = path.join(collectionDir, `${size.width}x${size.height}`);
                const dstFile = path.join(dstDir, file);
                const resizeOpts = {
                    withoutEnlargement: true,
                    fit: size.crop ? 'cover' : 'inside'
                };

                await fs.mkdir(dstDir, {recursive: true});
                await sharp(imgSrc, sharpOptions)
                    .resize(size.width, size.height, resizeOpts)
                    .png()
                    .toFile(dstFile);
            }

            const model = models[collection.model];

            if (!model)
                throw new Error('Matching model not found');

            const item = await model.findById(entityId, null, myOptions);
            if (item) {
                await item.updateAttribute(
                    collection.property,
                    fileName,
                    myOptions
                );
            }

            if (fs.existsSync(srcFilePath))
                await fs.unlink(srcFilePath);

            await tx.commit();

            return newImage;
        } catch (e) {
            await tx.rollback();
            throw e;
        }
    };
};