Source: multiwindow.js

const cursor = require("./cursor.js");
const Container = require("./container.js");

/**
 * @typedef {Object} paddingOptions
 * @property {number} [top=0] - the padding from the top
 * @property {number} [bottom=0] - the padding from the bottom
 * @property {number} [left=0] - the padding from the left
 * @property {number} [right=0] - the padding from the right
 */

/**
 * @typedef {Object} borderOptions
 * @property {string} [junction = "+"] - the character to use for junctions
 * @property {string} [vertical = "|"] - the character to use for vertical lines
 * @property {string} [horizontal = "-"]- the character to use for horizontal lines
 */

/**
 * The core class
 */
class MultiWindow {
    /**
     * @param {number[]} columns - the sizes of the columns
     * @param {number[]} rows - the sizes of the rows
     * @param {string} colorOfBorder - the color to make the border, see color_codes.txt for more info
     * @param {Object} [options] - options for the instance
     * @param {paddingOptions} [options.padding] - global padding that will be set on all containers
     * @param {borderOptions} [options.border] - options for what to draw borders out of
     */
    constructor (columns, rows, colorOfBorder, options) {
        cursor.clear();

        if (options === undefined) {
            options = {};
        }

        if (options.border === undefined) {
            this.borderOptions = {
                junction: "+",
                horizontal: "—",
                vertical: "|"
            };
        } else {
            this.borderOptions = options.border;
        }
        if (options.padding === undefined) {
            this.paddingGlobal = {
                top: 0,
                bottom: 0,
                right: 0,
                left: 0
            }
        } else {
            this.paddingGlobal = options.padding;
        }
        this.columns = columns;
        this.rows = rows;
        this.colorOfBorder = colorOfBorder;

        this.containers = [];
        let offsetY = 1;
        let offsetX = 1;
        for (let iY = 0; iY < this.rows.length; iY++) {
            this.containers[this.containers.length] = [];
            for (let iX = 0; iX < this.columns.length; iX++) {
                this.containers[iY][iX] = new Container(this.columns[iX], this.rows[iY], offsetX, offsetY);
                this.containers[iY][iX].createPadding(this.paddingGlobal);
                this.containers[iY][iX].setFrameColor(this.colorOfBorder);
                offsetX += 1 + this.columns[iX];
            }
            offsetY += 1 + this.rows[iY];
            offsetX = 1;
        }

        this.clearAll();
        this.drawBorders();

        //exiting
        this.setupExit();

        this.inputEnabled = false;
        this.currentData = "";
    }

    /**
     * Internal function called for drawing the main borders.
     */
    drawBorders () {
        cursor.write(this.colorOfBorder);
        cursor.move(0,0);

        let junction = this.borderOptions.junction;
        let horizontal = this.borderOptions.horizontal;
        let vertical = this.borderOptions.vertical;

        //print horizontal lines
        let horizontalString = junction;
        this.columns.forEach((e) => {
            horizontalString += horizontal.repeat(e) + junction;
        });
        let offsetH = 0;
        //print first
        cursor.move(0,0);
        cursor.write(horizontalString);
        //print rest
        this.rows.forEach((e) => {
            offsetH++;
            offsetH += e;

            cursor.move(0, offsetH);
            cursor.write(horizontalString);
        });

        //print vertical lines
        let offsetFirst = 1;
        this.rows.forEach((eY) => {
            for (let i = 0; i< eY; i++) {
                cursor.move(0, offsetFirst);
                cursor.write(vertical);
                offsetFirst++;
            }
            offsetFirst++;
        });
        let erofs = 0;
        this.columns.forEach((eR) => {
            let offsetV = 1;
            erofs += eR + 1;
            this.rows.forEach((eY) => {
                for (let i = 0; i< eY; i++) {
                    cursor.move(erofs, offsetV);
                    cursor.write(vertical);
                    offsetV++;
                }
                offsetV++;
            });
        });

        cursor.resetColor();
        cursor.move(0,0);
    }

    /**
     * Clear all containers
     */
    clearAll () {
        this.containers.forEach((e) => {
            e.forEach((cont) => {
                cont.clearContainer();
            })
        })
    }

    /**
     * Get the container at the specified coordinates
     * @param {number} y - the container y position
     * @param {number} x - the container x position
     * @returns {Container} - the container
     */
    container (y, x) {
        return this.containers[y][x];
    }

    /**
     * Same as container
     * @see MultiWindow#container
     */
    c (x, y) {
        return this.container(y, x);
    }

    /**
     * Internally used function, moves cursor to bottom of console
     */
    gotoEnd () {
        let line = 1;
        this.rows.forEach((e) => {
            line += 1 + e;
        });
        cursor.move(0,line+1);
    }

    /**
     * Exits the process
     * Same as process.exit() but with correct formatting
     */
    exit () {
        this.gotoEnd();
        process.exit();
    }

    /**
     * Internally used function for setting up exit handling
     */
    setupExit () {
        const readline = require('readline');
        readline.emitKeypressEvents(process.stdin);
        if (process.stdin.setRawMode){
            process.stdin.setRawMode(true)
        }
        process.stdin.resume();

        let outer = this;
        process.stdin.on('keypress', function (ch, key) {
            if (key.ctrl && key.name === "c") {
                outer.exit();
            }
        });

    }

    /**
     * Return the cursor module
     * @static
     * @type {module:cursor}
     */
    static get cursor () {
        return cursor;
    }

    /**
     * Returns the container class
     * @static
     * @type {Container}
     */
    static get container_class () {
        return Container;
    }
}

module.exports = MultiWindow;