const find = require('findit');
const path = require('path');
const mongoose = require('mongoose');
const crypto = require('crypto');

module.exports.Modules = class Modules 
{
    /**
     * 
     * @param {String} name Scanlator Name - Same as in the Module. 
     */
    constructor (name=null)
    {
        this.name = name;
        this.Modules;
    }
    /**
     * @returns {Array<Modules>}
     * @typedef {Class} Modules
     */
    getAll()
    {
            let finder = find(path.resolve('Modules'));
            finder.on('file', file => 
            {
                let Module = require(file);
                if (Module.constructor) 
                {
                    this.Modules.push(Module);
                }
            });
            finder.on('end', () => 
            {
                return {Modules:this.Modules}
            });
    }
    /**
     * 
     * @returns {Boolean}
     */
    async exists()
    {
        if (!this.name) return false;

    return new Promise((resolve, reject) => 
    {
        const finder = find(path.resolve('Modules'));
        finder.on('file', async file => 
        {
            let Module = require(file);
            if (Module.constructor) 
            {
                const test = new Module();
                if (this.name == test.scanlator) 
                {
                    finder.removeAllListeners(); 
                    resolve(true);
                }
            }
        });

        finder.on('error', err => 
        {
            reject(false); 
        });

        finder.on('end', () => 
        {
            resolve(false);
        });
    });
      
    }
    /**
     * 
     * @param {String} title 
     * @returns {Boolean}
     */
    async titleExists(title)
    {
        if(!this.name) return false;
        const module = require(`./Modules/${this.name.split('-')[0]+'Module'}`);
        return await new module().hasTitle(title);
    }
}

module.exports.Search = class Search
{
    /**
     * 
     * @param {String} searchString - String to search by.
     * @param {Scanlator} scanlator - Scanlator Name
     * @typedef {String} Scanlator
     * @pattern /^[\w-]+-scans$/
     */
    constructor(searchString, scanlator=null)
    {
        this.ss = searchString;
        this.scanlator = scanlator;
        this.Modules = [];
        this.results = [];
        this.initialized = new Promise((resolve) => 
        {
            let finder = find(path.resolve('Modules'));
            finder.on('file', file => 
            {
                let Module = require(file);
                if (Module.constructor) 
                {
                    this.Modules.push(Module);
                }
            });
            finder.on('end', () => 
            {
                resolve(); 
            });
        }) 
    }
    /**
     * 
     * @returns {Array<Results>}
     * @typedef {Object} Results
     * @property {Array<Manga>} Results
     * @property {Scanlator} scanlator
     * @typedef {Object} Manga
     * @property {String} link - Manga Link
     * @property {String} title - Manga Title
     * @property {String} img - Image Link
     * @typedef {String} Scanlator
     * @pattern /^[\w-]+-scans$/
     */
    async search()
    {
        await this.initialized;
        if(this.Modules.length==0) return
        for(const module of this.Modules)
        {
            const auxModule = new module()
            if(!this.scanlator || this.scanlator == auxModule.scanlator )
            try 
            {
                var results = await auxModule.Search(this.ss);    
                //TODO: Defaults for missing info from the modules;
                // console.log('Lib: Search: search method: result:', result);
                results.sort((a, b) => {
                    if (a.title.toUpperCase() <  b.title.toUpperCase()) return -1;
                    else return 1;
                });
                for(var i = 0; i< results.length; i++)
                {
                    results[i]['Status'] = results[i].status;
                }
                this.results.push({
                    Results:results,
                    scanlator:auxModule.scanlator
                });
            }
            catch (error) 
            {
                console.error('Lib: Module Errored:', auxModule.scanlator, 'Error:', error)
            }
        }
        return this.results
    }

}
module.exports.SearchByTag = class SearchByTag
{
    /**
     * 
     * @param {String} tag 
     */
    constructor(tag)
    {
        this.ss = tag.toLowerCase();
        this.Modules = [];
        this.results = [];
        this.initialized = new Promise((resolve) => 
        {
            let finder = find(path.resolve('Modules'));
            finder.on('file', file => 
            {
                let Module = require(file);
                if (Module.constructor) 
                {
                    this.Modules.push(Module);
                }
            });
            finder.on('end', () => 
            {
                resolve(); 
            });
        }) 
    }
    /**
     * @param {Number} ammount - 5 by default
     * @returns {Array<Results>}
     * @typedef {Object} Results
     * @property {Array<Manga>} Results
     * @property {Scanlator} scanlator
     * @typedef {Object} Manga
     * @property {String} link - Manga Link
     * @property {String} title - Manga Title
     * @property {String} img - Image Link
     * @typedef {String} Scanlator
     * @pattern /^[\w-]+-scans$/
     */
    async search(ammount=5)
    {
        await this.initialized;
        if(this.Modules.length==0) return
        for(const module of this.Modules)
        {
            const auxModule = new module()
            try 
            {
                let results = await auxModule.SearchByTag(this.ss, ammount); 
                //TODO: Defaults for missing info from the modules;
                if(results)
                {
                    results.sort((a, b) => 
                    {
                        if (a.title.toUpperCase() <  b.title.toUpperCase()) return -1;
                        else return 1;
                    });
                    for(let i = 0; i< results.length; i++)
                    {
                        let manga = await auxModule.GetManga(results[i].link, results[i].title);
                        results[i] = {...manga};
                    }
                    this.results.push({
                        Results:results,
                        scanlator:auxModule.scanlator
                    });
                }
            }
            catch (error) 
            {
                console.error('Lib: Module Errored:', auxModule.scanlator, 'Error:', error)
            }
        }
        return this.results
    }
}

module.exports.Manga = class Manga
{
    /**
     * 
     * @param {Scanlator} scanlator 
     * @param {Link} link Manga Link
     * @param {String} title 
     * @typedef {String} Link 
     * @pattern ^(https?:\/\/)?([\w\d.-]+)\.([a-z]{2,})(:[0-9]+)?(\/\S*)?$
     * @typedef {String} Scanlator
     * @pattern /^[\w-]+-scans$/ 
     */
    constructor(scanlator, link, title)
    {
        this.scanlator = scanlator;
        this.link = link;
        this.title = title;
        this.Engine;
        this.initialized = new Promise((resolve) => 
        {
            let finder = find(path.resolve('Modules'));
            finder.on('file', file => 
            {
                let Module = require(file);
                if (Module.constructor) 
                {
                    if(this.scanlator == new Module().scanlator) 
                    {
                        this.Engine = Module;
                        resolve();
                    }
                }
            });
            }) 
   }
    /**
     * 
     * @returns {Manga}
     * @typedef {Object} Manga
     * @property {String} link - Manga Link
     * @property {String} title - Manga Title
     * @property {String} img - Image Link
     * @property {Array<String>} tags - Manga Tags
     * @property {Array<Chapter>} List - Chapter List
     * @typedef {Object} Chapter
     * @property {Number} num - Chapter Number
     * @property {Link} link - Chapter Link
     * @property {Date_} date - Chapter uploaded date
     * @typedef {String} Link
     * @typedef {String} Date_
     */
    async get()
    {
        await this.initialized;
        try {
            const Scanlator = new this.Engine();
            const Manga = await Scanlator.GetManga(this.link, this.title); 
            let List = await Scanlator.ChapterList(this.link, this.title); 
            List = List.sort((a,b)=>
            {
                return a.num-b.num;
            })
            const Status = Manga.Status?Manga.Status:Manga.status;
            //TODO: Defaults for missing info from the modules;
            return {...Manga, Status, link:this.link, scanlator:this.scanlator, List, title:this.title}
        } catch (error) {
            console.error('Lib: Manga: Error:' ,error)
        }
    }
}

module.exports.Chapter = class Chapter
{
    /**
     * 
     * @param {Scanlator} scanlator - Scanlator Name
     * @param {Link} Link - Chapter Link
     * @param {String} title  - Manga Title
     * @param {Number} chNum  - Chapter Number
     * @typedef {String} Link
     * @pattern ^(https?:\/\/)?([\w\d.-]+)\.([a-z]{2,})(:[0-9]+)?(\/\S*)?$
     * @typedef {String} Scanlator
     * @pattern /^[\w-]+-scans$/
     */
    constructor(scanlator, Link,title, chNum)
    {
        this.scanlator = scanlator;
        this.title = title;
        this.Link = Link;
        this.chNum = chNum;
        this.Engine;
        this.initialized = new Promise((resolve) => 
        {
            let finder = find(path.resolve('Modules'));
            finder.on('file', file => 
            {
                let Module = require(file);
                if (Module.constructor) 
                {
                    if(this.scanlator == new Module().scanlator) 
                    {
                        this.Engine = Module;
                        resolve();
                    }
                }
            });
        }) 
    }
    /**
     * 
     * @returns {Chapter} 
     * @typedef {Object} Chapter
     * @property {Manga} Manga - Manga Link
     * @property {Array<Img>} List - Chapter List of images
     * @property {Number} chNum - Chapter number
     * @typedef {Object} Manga
     * @property {String} link - Manga Link
     * @property {String} title - Manga Title
     * @property {String} img - Image Link
     * @typedef {String} Link 
     * @typedef {Link} Img
     */
    async get()
    {
        await this.initialized;
        try 
        {
            const Scanlator = new this.Engine();
            const {List, mangaLink} = await Scanlator.Chapter(this.Link);
            const {latestChap } = await Scanlator.GetManga(mangaLink);
            //TODO: Defaults for missing info from the modules;
            return {List, latestChap,mangaLink, chNum:parseInt(this.chNum), link:this.Link,title:this.title, scanlator:this.scanlator}            
        }
        catch (error) 
        {
            console.error('Lib: Chapter: Error:', error)
        }
    }
    async getMangaLink()
    {
        await this.initialized;
        try
        {
            const Scanlator = new this.Engine();
            const {mangaLink} = await Scanlator.Chapter(this.Link);
            return mangaLink;
        }
        catch (error) 
        {
            console.error('Lib: Chapter: Error:', error);
        }
    }
};
exports.decodeHTML = (encodedString)=>
{
    return encodedString.replace(/&nbsp;|&#(\d+);|\n/g, function(match, dec) 
    {
        if (dec) 
        {
            return String.fromCharCode(dec); // Replace HTML character codes
        }
        else
        {
            return ''; // Remove newline characters
        }
    });
}

exports.isValidID = (id) =>
{
    return mongoose.Types.ObjectId.isValid(id);
}
/**
 * 
 * @param {String} cookie 
 * @param {String} name 
 * @returns 
 */
exports.cookieParser = (cookie, name)=>
{
    if(!cookie.includes(name)) return null
    return cookie.split(name+'=')[1].split(';')[0];
}
/**
 * @argument {Response} res - Response: express response object.
 * @argument {String} name - Name of the cookie.
 * @argument {*} value  - Value of the cookie.
 */
exports.Baker = async (res, name, value, maxAge =2) =>
{
    await res.cookie(name, '',{maxAge:0});
    await res.cookie(name, value, {maxAge:hours(2),path:'/',secure:false,httpOnly:false});
    return;
}
/**
 * Use it to transform an hour value into miliseconds.
 * @param {Number} Hours - Hours to transform
 */
function hours(Hours)
{
    return Hours*60*60*1000;
}   
exports.Crypto = class Crypto
{
    /**
     * 
     * @param {String} username 
     * @param {String} password 
     */
    constructor(username, password)
    {
        this.salt = '4$3H6Q9B8d4VXcE3Rft4';
        this.secret = '7xJYrX@KF7oSNr9Zm4UH';
        this.pepper = String.fromCharCode(this.getRandomInt(65, 90));
        this.password = password;
        this.user = username;
        this.algorithm = 'aes-192-cbc';

    }
    get Hash() // to use on register
    {
        return this.hash()
    }
    hash(p)
    {
        if(!p)
        {
            return crypto.createHmac('sha256', this.secret).update(this.user+this.password+this.salt+this.pepper).digest('hex');  
        }
        else
        {
            return crypto.createHmac('sha256', this.secret).update(this.user+this.password+this.salt+p).digest('hex');  
        }
    }
    getRandomInt(min, max)
    {
        min = Math.ceil(min);
        max = Math.floor(max);
        return Math.floor(Math.random() * (max - min)) + min;
    }
}