Source: structures.js

const Settings = require("./settings.js");

/**
 * Terms, standard accepted for the Quizzler
 */
class Term {

    constructor(term, example = "", description = "", prompt = "Use the term", { priority = 5, tags = [], category = "", references = "", attachment = "", auto_newline = true } = {}) {
        /**
         * REMEMBER: To add the new item into asJson!!
         */

        this.auto_newline = auto_newline;

        this.term = term;
        this.description = description;
        this.example = example;

        if (this.auto_newline) {
            this.example = this.example.replace(/(\s{2,}|\n)(?=\S)/g, "\n");
            this.description = this.description.replace(/(\s{2,}|\n)(?=\S)/g, "\n");
        }

        this.references = references;
        this.attachment = attachment;
        this.category = category;
        this.prompt = prompt;
        this.priority = priority;
        this.slug = this.slugify(this.term);
        this.formula_name = this.slug;
        this.attachment_is_url = this.isOnlineResource(attachment);
    }

    /**
     *  Slugify the term 
     */
    slugify = (term) => {
        return term.toString().normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase().trim().replace(/\s+/g, '-').replace(/[^\w-]+/g, '').replace(/--+/g, '-')
    }

    isOnlineResource = (url) => {
        return /^https?:\/\//i.test(url);
    }

    pushCategory = (subcategory) => {
        this.category += this.category == "" ? subcategory : ` > ${subcategory}`;
    }

    get asJson() {
        return {
            term: this.term, example: this.example, description: this.description, references: this.references,
            category: this.category, prompt: this.prompt,
            formula_name: this.slug, attachment: this.attachment,
            attachment_is_url: this.attachment_is_url
        };
    }

};


class Terminology extends Term {
    /**
     * 
     * @param {string} term Terminology or title
     * @param {String} description Description  which should appear or the definition
     * @param {Optional Arguments} param2 {example: If there is an example, auto_image: bool: If to autoamtically fetch an image from the web.}
     */
    constructor(term, description = "", { example = "", autom_image = false, H = "Use this on an example", attachment = "" } = {}) {

        super(term, example, description, prompt, { attachment: attachment });


    }
}


/**
 * Follows Composition Pattern, it should be able to store other Term Storages, turn them on and off
 */
class TermStorage {

    /**
     * Initialization, by default TermStorage is acitve.
     * @param {List[JsonText]} terms Terms to be added to this deck
     * @param {string} deck_name The deckname, optional if is the parent deckname
     * @param {List[TermStorage]} decks The decks required for the Storages
     * @param {boolean} is_active If the deck is active or not; by default is false
     */
    constructor(terms = [], deck_name = "", { decks = [], is_active = false } = {}) {
        this.terms = terms;
        this.deck_name = deck_name;
        this.is_active = is_active;
        this.decks = decks;
        this.priority = 5; //By default
    }

    /**
     * 
     * @param {TermStorage} deck the deck to append to the storage, by default is active usually
     */
    addDeck(deck) {
        this.decks.push(deck);
    }

    addDecks(decks) {
        for (const deck of decks) {
            // console.log("Adding deck: ", deck);
            this.addDeck(deck);
        }
    }


    /**
     * 
     * @param {[DeckMask]} masks List of masks to apply to the deck
     */
    applyMasks(masks) {
        for (const mask of masks) {
            if (mask.decksToEnable.includes(this.deck_name)) {
                this.is_active = mask.enabled;
            }
        }
        for (const deck of this.decks) {
            deck.applyMasks(masks);
        }
    }


    /**
     * Returns list of deck title. e.g.
     * [kotlin, java, javascript...]
     */
    get deck_titles() {
        const deck_names = [this.deck_name];
        for (const deck of this.decks) {
            deck_names.push(...deck.deck_titles);
        }
        return deck_names;
    }

    /**
     * Returns dict of deck titles with the count of cards inside: deckname
     *  e.g.:
     * {
     *      kotlin - 3: {count: 3, name: kotlin}, 
     *      java - 5: {count: 5, name: java}
     *      javascript - 10: {count: 10, name: javascript}
     * }
     */
    get deck_titles_with_count() {
        const deck_names = {
            [`${this.deck_name} - ${this.terms.length} cards`]: { name: this.deck_name, count: this.terms.length }
        };
        for (const deck of this.decks) {
            Object.assign(deck_names, deck.deck_titles_with_count);
        }
        return deck_names;
    }


    /**
     * Follows the design of array.push, easier to memorize
     * @param {Term} term Pushes this term into the terms of the storage
     */
    push(term) {
        // Check if term at least has a term and description
        if (term?.term == null) {

            return;
        }
        if (term.term == "") {
            return;
        }


        this.terms.push(term);
    }

    /**
     * Appends all decks that are active + its current cards.
     * @returns {List<Json>} Gets the terminologies as a List<Json>
     */
    get jsonTerms() {
        const safeguard_bad_terms = true;
        const res = [];
        // Add own cards
        for (const term of this.terms) {

            res.push(term)
        }

        //Add cards of the decks that are active
        for (const deck of this.decks) {
            if (deck.is_active) {
                res.push(...deck.jsonTerms);
            }
        }

        return res;
    }


    /**
     * 
     * @param {get_only} get only certain decks (with x categories.) 
     * @returns 
     */
    listTerms({ get_only = [] } = {}) {
        const termsList = [];
        termsList.push(...this.terms.map(
            obj => {
                const newterm = new Term(
                    obj?.term ?? "", obj?.example ?? "", obj?.description ?? "", obj?.prompt ?? "",
                    {
                        references: obj?.references ?? "", attachment: obj?.attachment,
                        priority: this.priority
                    }
                )
                newterm.pushCategory(this.deck_name ?? "");
                return newterm;
            }
        ));


        for (const deck of this.decks) {
            //Regardless of it is active or not.
            if ((get_only.length == 0 && deck.is_active) || get_only.includes(deck.deck_name)) {
                termsList.push(...deck.listTerms());
            }
        }

        // Do the same recursive for each of the internal res 
        return termsList
    }



    /**
     * 
     * @param is_active_settings {deck_name,is_active} settings Takes in the settings in key:true/false format to turn on or off of the decks inside.
     */
    changeIsActiveSettingsFromDecks(is_active_settings) {
        for (const deck_name of Object.keys(is_active_settings)) {
            this.decks[deck_name].is_active = is_active_settings[deck_name];
        }
    }

    /**
     * Simply explains the insides as well as the name of the deck
     */
    explain() {
        console.log("termGenerator content:");
        console.log(`From deck: ${this.deck_name} contains decks: ${this.decks.length}`);
        console.log(this.jsonTerms);
    }



};


class Queue {

    constructor() {
        this.queue = []
    }

    enqueue(element) {
        this.queue.push(element)
    }

    dequeue() {
        return this.queue.shift()
    }

    front() {
        return this.queue[0]
    }

    size() {
        return this.queue.length
    }

    isEmpty() {
        return this.size() === 0
    }

}

class DeckMask {
    /**
     * 
     * @param {string} mask_name Name of this mask, used as a sharable identifier
     * @optionalparam {string[]} decksToEnableStrings 
     * @optionalparam {boolean} enabled; defaults to true
     * @optionalparam {int} account_id; defaults to Settings.account_id
     * @returns {DeckMask}
     */
    constructor(mask_name, { decksToEnableStrings = [], enabled = true, account_id = Settings.account_id } = {}) {
        this.mask_name = mask_name;
        this.decksToEnable = decksToEnableStrings;
        this.enabled = enabled;
        this.account_id = account_id;
    }



    get asJson() {
        return {
            mask_name: this.mask_name,
            decks: this.decksToEnable,
            enabled: this.enabled,
            account_id: this.account_id
        }
    }
}


module.exports = { Term, Terminology, TermStorage, Queue, DeckMask };