/**
 * @author n1474335 [n1474335@gmail.com]
 * @copyright Crown Copyright 2016
 * @license Apache-2.0
 */

import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";

/**
 * Parse UNIX file permissions operation
 */
class ParseUNIXFilePermissions extends Operation {

    /**
     * ParseUNIXFilePermissions constructor
     */
    constructor() {
        super();

        this.name = "Parse UNIX file permissions";
        this.module = "Default";
        this.description = "Given a UNIX/Linux file permission string in octal or textual format, this operation explains which permissions are granted to which user groups.<br><br>Input should be in either octal (e.g. <code>755</code>) or textual (e.g. <code>drwxr-xr-x</code>) format.";
        this.infoURL = "https://wikipedia.org/wiki/File_system_permissions#Traditional_Unix_permissions";
        this.inputType = "string";
        this.outputType = "string";
        this.args = [];
        this.checks = [
            {
                pattern:  "^\\s*d[rxw-]{9}\\s*$",
                flags:  "",
                args:   []
            }
        ];
    }

    /**
     * @param {string} input
     * @param {Object[]} args
     * @returns {string}
     */
    run(input, args) {
        const perms = {
            d:  false, // directory
            sl: false, // symbolic link
            np: false, // named pipe
            s:  false, // socket
            cd: false, // character device
            bd: false, // block device
            dr: false, // door
            sb: false, // sticky bit
            su: false, // setuid
            sg: false, // setgid
            ru: false, // read user
            wu: false, // write user
            eu: false, // execute user
            rg: false, // read group
            wg: false, // write group
            eg: false, // execute group
            ro: false, // read other
            wo: false, // write other
            eo: false  // execute other
        };
        let d = 0,
            u = 0,
            g = 0,
            o = 0,
            output = "",
            octal = null,
            textual = null;

        if (input.search(/\s*[0-7]{1,4}\s*/i) === 0) {
            // Input is octal
            octal = input.match(/\s*([0-7]{1,4})\s*/i)[1];

            if (octal.length === 4) {
                d = parseInt(octal[0], 8);
                u = parseInt(octal[1], 8);
                g = parseInt(octal[2], 8);
                o = parseInt(octal[3], 8);
            } else {
                if (octal.length > 0) u = parseInt(octal[0], 8);
                if (octal.length > 1) g = parseInt(octal[1], 8);
                if (octal.length > 2) o = parseInt(octal[2], 8);
            }

            perms.su = d >> 2 & 0x1;
            perms.sg = d >> 1 & 0x1;
            perms.sb = d & 0x1;

            perms.ru = u >> 2 & 0x1;
            perms.wu = u >> 1 & 0x1;
            perms.eu = u & 0x1;

            perms.rg = g >> 2 & 0x1;
            perms.wg = g >> 1 & 0x1;
            perms.eg = g & 0x1;

            perms.ro = o >> 2 & 0x1;
            perms.wo = o >> 1 & 0x1;
            perms.eo = o & 0x1;
        } else if (input.search(/\s*[dlpcbDrwxsStT-]{1,10}\s*/) === 0) {
            // Input is textual
            textual = input.match(/\s*([dlpcbDrwxsStT-]{1,10})\s*/)[1];

            switch (textual[0]) {
                case "d":
                    perms.d = true;
                    break;
                case "l":
                    perms.sl = true;
                    break;
                case "p":
                    perms.np = true;
                    break;
                case "s":
                    perms.s = true;
                    break;
                case "c":
                    perms.cd = true;
                    break;
                case "b":
                    perms.bd = true;
                    break;
                case "D":
                    perms.dr = true;
                    break;
            }

            if (textual.length > 1) perms.ru = textual[1] === "r";
            if (textual.length > 2) perms.wu = textual[2] === "w";
            if (textual.length > 3) {
                switch (textual[3]) {
                    case "x":
                        perms.eu = true;
                        break;
                    case "s":
                        perms.eu = true;
                        perms.su = true;
                        break;
                    case "S":
                        perms.su = true;
                        break;
                }
            }

            if (textual.length > 4) perms.rg = textual[4] === "r";
            if (textual.length > 5) perms.wg = textual[5] === "w";
            if (textual.length > 6) {
                switch (textual[6]) {
                    case "x":
                        perms.eg = true;
                        break;
                    case "s":
                        perms.eg = true;
                        perms.sg = true;
                        break;
                    case "S":
                        perms.sg = true;
                        break;
                }
            }

            if (textual.length > 7) perms.ro = textual[7] === "r";
            if (textual.length > 8) perms.wo = textual[8] === "w";
            if (textual.length > 9) {
                switch (textual[9]) {
                    case "x":
                        perms.eo = true;
                        break;
                    case "t":
                        perms.eo = true;
                        perms.sb = true;
                        break;
                    case "T":
                        perms.sb = true;
                        break;
                }
            }
        } else {
            throw new OperationError("Invalid input format.\nPlease enter the permissions in either octal (e.g. 755) or textual (e.g. drwxr-xr-x) format.");
        }

        output += "Textual representation: " + permsToStr(perms);
        output += "\nOctal representation:   " + permsToOctal(perms);

        // File type
        if (textual) {
            output += "\nFile type: " + ftFromPerms(perms);
        }

        // setuid, setgid
        if (perms.su) {
            output += "\nThe setuid flag is set";
        }
        if (perms.sg) {
            output += "\nThe setgid flag is set";
        }

        // sticky bit
        if (perms.sb) {
            output += "\nThe sticky bit is set";
        }

        // Permission matrix
        output += `

 +---------+-------+-------+-------+
 |         | User  | Group | Other |
 +---------+-------+-------+-------+
 |    Read |   ${perms.ru ? "X" : " "}   |   ${perms.rg ? "X" : " "}   |   ${perms.ro ? "X" : " "}   |
 +---------+-------+-------+-------+
 |   Write |   ${perms.wu ? "X" : " "}   |   ${perms.wg ? "X" : " "}   |   ${perms.wo ? "X" : " "}   |
 +---------+-------+-------+-------+
 | Execute |   ${perms.eu ? "X" : " "}   |   ${perms.eg ? "X" : " "}   |   ${perms.eo ? "X" : " "}   |
 +---------+-------+-------+-------+`;

        return output;
    }

}


/**
 * Given a permissions object dictionary, generates a textual permissions string.
 *
 * @param {Object} perms
 * @returns {string}
 */
function permsToStr(perms) {
    let str = "",
        type = "-";

    if (perms.d) type = "d";
    if (perms.sl) type = "l";
    if (perms.np) type = "p";
    if (perms.s) type = "s";
    if (perms.cd) type = "c";
    if (perms.bd) type = "b";
    if (perms.dr) type = "D";

    str = type;

    str += perms.ru ? "r" : "-";
    str += perms.wu ? "w" : "-";
    if (perms.eu && perms.su) {
        str += "s";
    } else if (perms.su) {
        str += "S";
    } else if (perms.eu) {
        str += "x";
    } else {
        str += "-";
    }

    str += perms.rg ? "r" : "-";
    str += perms.wg ? "w" : "-";
    if (perms.eg && perms.sg) {
        str += "s";
    } else if (perms.sg) {
        str += "S";
    } else if (perms.eg) {
        str += "x";
    } else {
        str += "-";
    }

    str += perms.ro ? "r" : "-";
    str += perms.wo ? "w" : "-";
    if (perms.eo && perms.sb) {
        str += "t";
    } else if (perms.sb) {
        str += "T";
    } else if (perms.eo) {
        str += "x";
    } else {
        str += "-";
    }

    return str;
}

/**
 * Given a permissions object dictionary, generates an octal permissions string.
 *
 * @param {Object} perms
 * @returns {string}
 */
function permsToOctal(perms) {
    let d = 0,
        u = 0,
        g = 0,
        o = 0;

    if (perms.su) d += 4;
    if (perms.sg) d += 2;
    if (perms.sb) d += 1;

    if (perms.ru) u += 4;
    if (perms.wu) u += 2;
    if (perms.eu) u += 1;

    if (perms.rg) g += 4;
    if (perms.wg) g += 2;
    if (perms.eg) g += 1;

    if (perms.ro) o += 4;
    if (perms.wo) o += 2;
    if (perms.eo) o += 1;

    return d.toString() + u.toString() + g.toString() + o.toString();
}


/**
 * Given a permissions object dictionary, returns the file type.
 *
 * @param {Object} perms
 * @returns {string}
 */
function ftFromPerms(perms) {
    if (perms.d) return "Directory";
    if (perms.sl) return "Symbolic link";
    if (perms.np) return "Named pipe";
    if (perms.s) return "Socket";
    if (perms.cd) return "Character device";
    if (perms.bd) return "Block device";
    if (perms.dr) return "Door";
    return "Regular file";
}

export default ParseUNIXFilePermissions;
