/* lib/terminal/server/services/fileSystem - Manages various file system services. */ import base64 from "../../commands/library/base64.js";
import common from "../../../common/common.js";
import directory from "../../commands/library/directory.js";
import error from "../../utilities/error.js";
import fileCopy from "./fileCopy.js";
import fileExecution from "./fileExecution.js";
import hash from "../../commands/library/hash.js";
import mkdir from "../../commands/library/mkdir.js";
import network from "../transmission/network.js";
import node from "../../utilities/node.js";
import remove from "../../commands/library/remove.js";
import vars from "../../utilities/vars.js";
import service from "../../test/application/service.js";
/**
 * Methods for managing file system actions other than copy/cut across a network and the security model.
 * ```typescript
 * interface module_fileSystem {
 *     actions: {
 *         destroy    : (data:service_fileSystem) => void; // Service handler to remove a file system artifact.
 *         directory  : (data:service_fileSystem) => void; // A service handler to read directory information, such as navigating a file system in the browser.
 *         error      : (error:node_error, agentRequest:fileAgent, agentSource:fileAgent) => void; // packages error messaging for transport
 *         execute    : (data:service_fileSystem) => void; // Tells the operating system to execute the given file system artifact using the default application for the resolved file type.
 *         newArtifact: (data:service_fileSystem) => void; // Creates new empty directories or files.
 *         read       : (data:service_fileSystem) => void; // Opens a file and responds with the file contents as a UTF8 string.
 *         rename     : (data:service_fileSystem) => void; // Service handler to rename a file system artifact.
 *         write      : (data:service_fileSystem) => void; // Writes a string to a file.
 *     };
 *     menu: (data:service_fileSystem) => void; // Resolves actions from *service_fileSystem* to methods in this object's action property.
 *     route: (socketData:socketData) => void;  // Sends the data and destination to network.fileRoute method.
 *     status: {
 *         generate : (data:service_fileSystem, dirs:directory_response) => void;              // Formulates a status message to display in the modal status bar of a File Navigate type modal for distribution using the *statusBroadcast* method.
 *         specified: (message:string, agentRequest:fileAgent, agentSource:fileAgent) => void; // Specifies an exact string to send to the File Navigate modal status bar.
 *     };
 * }
 * ``` */ const fileSystem = {
    actions: {
        destroy: function terminal_server_services_fileSystem_destroy(data) {
            let count = data.location.length;
            const callback = function terminal_server_services_fileSystem_destroy_callback() {
                count = count - 1;
                if (count > -1) {
                    remove(data.location[count], [], terminal_server_services_fileSystem_destroy_callback);
                } else {
                    fileSystem.status.generate(data, null);
                }
            };
            callback();
        },
        directory: function terminal_server_services_fileSystem_directory(data) {
            let count = 0, output = [], failures = [], store;
            const rootIndex = data.location.indexOf("**root**"), pathList = data.action === "fs-search" ? [
                data.location[0]
            ] : data.location, pathLength = pathList.length, complete = function terminal_server_services_fileSystem_directory_complete(result) {
                if (data.action === "fs-details") {
                    fileSystem.route({
                        data: {
                            agentRequest: data.agentRequest,
                            dirs: result,
                            id: data.name
                        },
                        service: "file-system-details"
                    });
                } else {
                    if (result === undefined) {
                        result = "missing";
                    }
                    fileSystem.status.generate(data, result);
                }
            }, callback = function terminal_server_services_fileSystem_directory_callback(title, text, dirs) {
                const result = dirs;
                count = count + 1;
                store = result;
                if (result.length > 0) {
                    failures = failures.concat(result.failures);
                    output = output.concat(result);
                }
                if (vars.test.type === "service") {
                    result.forEach(function terminal_server_services_fileSystem_directory_callback_each(item) {
                        item[5] = null;
                    });
                }
                if (count === pathLength) {
                    if (data.action === "fs-search") {
                        const resultLength = result.length, plural = result.length === 1 ? "" : "es";
                        data.name = `search-${title} "<em>${data.name}</em>" returned <strong>${common.commas(resultLength)}</strong> match${plural} from <em>${data.location[0]}</em>.`;
                    }
                    complete(result);
                }
            }, dirConfig = {
                callback: callback,
                depth: data.depth,
                exclusions: [],
                mode: data.action === "fs-search" ? "search" : "read",
                path: "",
                search: data.name,
                symbolic: true
            };
            if (rootIndex > -1) {
                data.location[rootIndex] = vars.path.sep;
            }
            pathList.forEach(function terminal_server_services_fileSystem_directory_pathEach(value) {
                const pathRead = function terminal_server_services_fileSystem_directory_pathEach_pathRead() {
                    if (/^\w:$/.test(value) === true) {
                        value = value + "\\";
                    }
                    dirConfig.path = value;
                    directory(dirConfig);
                };
                if (value === "\\" || value === "\\\\") {
                    pathRead();
                } else {
                    node.fs.stat(value, function terminal_server_services_fileSystem_directory_pathEach_stat(erp) {
                        if (erp === null) {
                            pathRead();
                        } else {
                            failures.push(value);
                            if (failures.length === data.location.length) {
                                complete(store);
                            }
                        }
                    });
                }
            });
        },
        error: function terminal_server_services_fileSystem_routeError(error, agentRequest, agentSource) {
            fileSystem.route({
                data: Object.assign({
                    agentRequest: agentRequest,
                    agentSource: agentSource,
                    agentWrite: null
                }, error),
                service: "error"
            });
        },
        execute: function terminal_server_services_fileSystem_execute(data) {
            if (data.agentRequest.user === vars.identity.hashUser && data.agentRequest.device === vars.identity.hashDevice) {
                // file on local device - execute without a file copy request
                let counter = 0, index = 0;
                const dirList = [], directoryCallback = function terminal_server_services_fileSystem_execute_directoryCallback(title, text, dir) {
                    if (typeof dir[0][0] === "string") {
                        const dirs = dir;
                        dirList.push([
                            dirs[0][0],
                            dirs[0][1]
                        ]);
                    }
                    counter = counter + 1;
                    // only actually execute the files if not in test automation
                    if (counter === data.location.length && vars.test.type === "") {
                        fileExecution(dirList, data.agentRequest, data.agentSource);
                    }
                }, len = data.location.length;
                do {
                    directory({
                        callback: directoryCallback,
                        depth: 1,
                        exclusions: [],
                        mode: "read",
                        path: data.location[index],
                        search: "",
                        symbolic: false
                    });
                    index = index + 1;
                }while (index < len)
            } else {
                // file on different agent - request file copy before execution
                const copyPayload = {
                    agentRequest: data.agentRequest,
                    agentSource: data.agentSource,
                    agentWrite: data.agentRequest,
                    cut: false,
                    execute: true,
                    location: data.location
                };
                fileSystem.status.specified(`Generating integrity hash for file copy to execute ${data.location[0]}`, data.agentRequest, data.agentSource);
                fileCopy.route({
                    data: copyPayload,
                    service: "copy"
                });
            }
        },
        newArtifact: function terminal_server_services_fileSystem_newArtifact(data) {
            if (data.name === "directory") {
                mkdir(data.location[0], function terminal_server_services_fileSystem_newArtifact_directory() {
                    fileSystem.status.generate(data, null);
                });
            } else if (data.name === "file") {
                node.fs.writeFile(data.location[0], "", "utf8", function terminal_server_services_fileSystem_newArtifact_file(erNewFile) {
                    if (erNewFile === null) {
                        fileSystem.status.generate(data, null);
                    } else {
                        error([
                            `Error writing file ${data.location[0]}`
                        ], erNewFile);
                        fileSystem.actions.error(erNewFile, data.agentRequest, data.agentRequest);
                    }
                });
            } else {
                fileSystem.actions.error(new Error(`unsupported type ${data.name}`), data.agentRequest, data.agentRequest);
            }
        },
        read: function terminal_server_services_fileSystem_read(data) {
            const length = data.location.length, type = data.action === "fs-read" ? "base64" : data.action.replace("fs-", ""), stringData = {
                agentRequest: data.agentRequest,
                files: [],
                type: data.action.replace("fs-", "")
            }, // this callback provides identical instructions for base64 and hash operations, but the output types differ in a single property
            callback = function terminal_server_services_fileSystem_read_callback(title, output) {
                const out = output, file = {
                    content: out[type],
                    id: output.id,
                    path: output.filePath
                };
                b = b + 1;
                stringData.files.push(file);
                if (b === length) {
                    fileSystem.route({
                        data: stringData,
                        service: "file-system-string"
                    });
                }
            }, fileReader = function terminal_server_services_fileSystem_read_fileReader(fileInput) {
                node.fs.readFile(fileInput.source, "utf8", function terminal_server_services_fileSystem_read_fileReader_readFile(readError, fileData) {
                    const inputConfig = {
                        base64: fileData,
                        id: fileInput.id,
                        filePath: fileInput.source
                    };
                    if (readError !== null) {
                        error([
                            `Error reading file ${fileInput.source}`
                        ], readError);
                        fileSystem.actions.error(readError, data.agentRequest, data.agentRequest);
                        return;
                    }
                    input.callback("", inputConfig);
                });
            }, input = {
                callback: callback,
                direction: "encode",
                id: "",
                source: ""
            }, hashInput = {
                algorithm: vars.settings.ui.hashType,
                callback: callback,
                digest: "hex",
                directInput: false,
                id: "",
                list: false,
                parent: null,
                source: "",
                stat: null
            };
            let a = 0, b = 0, index;
            if (data.location.length === 1 && data.location[0].indexOf(":export-settings") === data.location[0].length - ":export-settings".length) {
                const settingsItem = {
                    message: vars.settings.message,
                    queue: vars.settings.queue,
                    secure: null,
                    status: null,
                    ui: vars.settings.ui,
                    verbose: null
                }, exportData = {
                    agents: vars.agents,
                    identity: vars.identity,
                    settings: settingsItem
                };
                input.id = data.location[0].slice(0, data.location[0].indexOf(":"));
                input.source = `string:${JSON.stringify(exportData)}`;
                base64(input);
            } else {
                do {
                    if (data.action === "fs-base64") {
                        index = data.location[a].indexOf(":");
                        input.id = data.location[a].slice(0, index);
                        input.source = data.location[a].slice(index + 1);
                        base64(input);
                    } else if (data.action === "fs-hash") {
                        index = data.location[a].indexOf(":");
                        hashInput.id = data.location[a].slice(0, index);
                        hashInput.source = data.location[a].slice(index + 1);
                        hash(hashInput);
                    } else if (data.action === "fs-read") {
                        index = data.location[a].indexOf(":");
                        input.id = data.location[a].slice(0, index);
                        input.source = data.location[a].slice(index + 1);
                        fileReader(input);
                    }
                    a = a + 1;
                }while (a < length)
            }
        },
        rename: function terminal_server_services_fileSystem_rename(data) {
            const newPath = function terminal_server_services_fileSystem_rename_newPath() {
                const tempPath = data.location[0].split(vars.path.sep);
                tempPath.pop();
                tempPath.push(data.name);
                return tempPath.join(vars.path.sep);
            }();
            node.fs.stat(newPath, function terminal_server_services_fileSystem_rename_stat(statError) {
                if (statError === null) {
                    data.name = `File <em>${newPath}</em> already exists.`;
                    fileSystem.status.generate(data, null);
                } else if (statError.code === "ENOENT") {
                    node.fs.rename(data.location[0], newPath, function terminal_server_services_fileSystem_rename_callback(erRename) {
                        if (erRename === null) {
                            data.name = `Renamed ${data.name} from ${data.location[0]}`;
                            fileSystem.status.generate(data, null);
                        } else {
                            error([
                                `Error renaming artifact ${data.location[0]}`
                            ], erRename);
                            fileSystem.actions.error(erRename, data.agentRequest, data.agentRequest);
                        }
                    });
                } else {
                    fileSystem.actions.error(statError, data.agentRequest, data.agentSource);
                }
            });
        },
        write: function terminal_server_services_fileSystem_write(data) {
            node.fs.writeFile(data.location[0], data.name, "utf8", function terminal_server_services_fileSystem_write_callback(erw) {
                const dirs = data.location[0].split(vars.path.sep);
                dirs.pop();
                data.agentSource.modalAddress = dirs.join(vars.path.sep);
                if (erw !== null) {
                    fileSystem.actions.error(erw, data.agentRequest, data.agentRequest);
                } else if (vars.test.type === "service") {
                    const stringData = {
                        agentRequest: data.agentRequest,
                        files: [
                            {
                                content: "Saved to disk!",
                                id: data.name,
                                path: data.location[0]
                            }
                        ],
                        type: "read"
                    };
                    fileSystem.route({
                        data: stringData,
                        service: "file-system-string"
                    });
                }
            });
        }
    },
    menu: function terminal_server_services_fileSystem_menu(data) {
        const status = {
            agentRequest: data.agentRequest,
            agentSource: data.agentSource,
            agentWrite: null,
            fileList: null,
            message: `Security violation from file system action <em>${data.action.replace("fs-", "")}</em>.`
        };
        let methodName = null;
        if (data.action === "fs-base64" || data.action === "fs-hash" || data.action === "fs-read") {
            methodName = "read";
        } else if (data.action === "fs-destroy") {
            methodName = "destroy";
        } else if (data.action === "fs-details" || data.action === "fs-directory" || data.action === "fs-search") {
            methodName = "directory";
        } else if (data.action === "fs-execute") {
            methodName = "execute";
        } else if (data.action === "fs-new") {
            methodName = "newArtifact";
        } else if (data.action === "fs-rename") {
            methodName = "rename";
        } else if (data.action === "fs-write") {
            methodName = "write";
        }
        if (methodName !== null) {
            // security, same user
            if (data.agentRequest.user === vars.identity.hashUser) {
                if (vars.agents.device[data.agentRequest.device] !== undefined) {
                    fileSystem.actions[methodName](data);
                    return;
                }
            // security, external user
            } else if (vars.agents.user[data.agentRequest.user] !== undefined) {
                const self = vars.agents.device[vars.identity.hashDevice], shares = Object.keys(self.shares), item = data.location[0];
                let index = shares.length, shareIndex = null, share = null;
                // local device must have shares for the external user to access
                if (index > 0) {
                    do {
                        index = index - 1;
                        share = self.shares[shares[index]];
                        // item is in most precise share if item begins with share of longest matching share name
                        if (item.indexOf(share.name) === 0 && (shareIndex === null || share.name.length > self.shares[shares[shareIndex]].name.length)) {
                            shareIndex = index;
                        }
                    }while (index > 0)
                    if (shareIndex !== null) {
                        // share allowing modifications
                        if (self.shares[shares[shareIndex]].readOnly === false) {
                            fileSystem.actions[methodName](data);
                            return;
                        }
                        // share restricted to read only operations
                        if (self.shares[shares[shareIndex]].readOnly === true && (methodName === "read" || methodName === "directory" || methodName === "execute")) {
                            fileSystem.actions[methodName](data);
                            return;
                        }
                    }
                }
            }
        }
        network.fileRoute({
            callback: function terminal_server_services_fileSystem_menu_securityStatus(socketData) {
                network.send(socketData, "browser");
            },
            destination: "agentRequest",
            origination: "agentRequest",
            socketData: {
                data: status,
                service: "file-system-status"
            }
        });
    },
    route: function terminal_server_services_fileSystem_route(socketData) {
        const data = socketData.data;
        if (socketData.service === "file-system") {
            if (vars.test.type === "service") {
                fileSystem.menu(data);
            } else {
                network.fileRoute({
                    callback: function terminal_server_services_fileSystem_route_fileSystem(routeData) {
                        const fileData = routeData.data;
                        fileSystem.menu(fileData);
                    },
                    destination: "agentSource",
                    origination: "agentRequest",
                    socketData: socketData
                });
            }
        } else {
            const broadcast = function terminal_server_services_fileSystem_route_broadcast(routeData) {
                network.send(routeData, "browser");
            };
            if (vars.test.type === "service") {
                service.evaluation(socketData);
            } else {
                network.fileRoute({
                    callback: broadcast,
                    destination: "agentRequest",
                    origination: "agentSource",
                    socketData: socketData
                });
            }
        }
    },
    status: {
        generate: function terminal_server_services_fileSystem_statusGenerate(data, dirs) {
            const callback = function terminal_server_services_fileSystem_statusGenerate_callback(list) {
                const count = function terminal_server_services_fileSystem_statusGenerate_callback_count() {
                    let a = typeof list === "string" ? -1 : list.length;
                    const counts = [
                        0,
                        0,
                        0,
                        0
                    ], end = data.action === "fs-search" ? 0 : 1;
                    if (typeof list === "string") {
                        return counts;
                    }
                    if (a > end) {
                        do {
                            a = a - 1;
                            if (list[a][3] === 0) {
                                if (list[a][1] === "directory") {
                                    counts[0] = counts[0] + 1;
                                } else if (list[a][1] === "file") {
                                    counts[1] = counts[1] + 1;
                                } else if (list[a][1] === "link") {
                                    counts[2] = counts[2] + 1;
                                } else {
                                    counts[3] = counts[3] + 1;
                                }
                            }
                        }while (a > end)
                    }
                    return counts;
                }(), plural = function terminal_server_services_fileSystem_statusGenerate_callback_plural(input, quantity) {
                    if (quantity === 1) {
                        return input;
                    }
                    if (input === "directory") {
                        return "directories";
                    }
                    return `${input}s`;
                }, message = function terminal_server_services_fileSystem_statusGenerate_callback_message() {
                    if (data.action === "fs-rename" || data.action === "fs-search") {
                        return data.name;
                    }
                    if (dirs === "missing" || dirs === "noShare" || dirs === "readOnly") {
                        return "";
                    }
                    if (data.action === "fs-destroy") {
                        return `Destroyed ${data.location.length} file system ${plural("item", data.location.length)}`;
                    }
                    return data.agentSource.modalAddress === "\\" ? `${count[0]} ${plural("drive", list.length)}` : `${count[0]} ${plural("directory", count[0])}, ${count[1]} ${plural("file", count[1])}, ${count[2]} ${plural("symbolic link", count[2])}, ${count[3]} ${plural("error", count[3])}`;
                }(), status = {
                    agentRequest: data.agentRequest,
                    agentSource: data.agentSource,
                    agentWrite: null,
                    fileList: list,
                    message: data.name === "expand" ? `expand-${data.location[0]}` : message
                }, socketData = {
                    data: status,
                    service: "file-system-status"
                };
                if (vars.test.type === "service") {
                    service.evaluation(socketData);
                } else if (data.agentRequest.device === vars.identity.hashDevice && data.agentRequest.user === vars.identity.hashUser) {
                    network.send(socketData, "browser");
                } else {
                    fileSystem.route(socketData);
                }
            };
            if (dirs === null) {
                const dirConfig = {
                    callback: function terminal_server_services_fileSystem_statusGenerate_dirCallback(title, text, list) {
                        const dirs = list;
                        callback(dirs);
                    },
                    depth: 2,
                    exclusions: [],
                    mode: "read",
                    path: data.agentSource.modalAddress,
                    search: "",
                    symbolic: true
                };
                directory(dirConfig);
            } else {
                callback(dirs);
            }
        },
        specified: function terminal_server_services_fileSystem_statusSpecified(message, agentRequest, agentSource) {
            const status = {
                agentRequest: agentRequest,
                agentSource: agentSource,
                agentWrite: null,
                fileList: null,
                message: message
            };
            fileSystem.route({
                data: status,
                service: "file-system-status"
            });
        }
    }
};
export default fileSystem;
