#!/usr/bin/env node
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
const os = require("os");
const path = require("path");
const request = require("request");
const commander = require("commander");
const child_process = require("child_process");
const fs = require("fs-extra");
class HomebridgeServiceHelper {
    constructor() {
        this.serviceName = 'Homebridge';
        this.storagePath = path.resolve(os.homedir(), '.homebridge');
        this.uiPort = 8080;
        commander
            .allowUnknownOption()
            .option('-U, --user-storage-path [path]', '', (p) => this.storagePath = p)
            .option('-P, --plugin-path [path]', '', (p) => process.env.UIX_CUSTOM_PLUGIN_PATH = p)
            .option('-I, --insecure', '', () => process.env.UIX_INSECURE_MODE = '1')
            .option('-T, --no-timestamp', '', () => process.env.UIX_LOG_NO_TIMESTAMPS = '1')
            .option('-S, --service-name [service name]', '', (p) => this.serviceName = p)
            .arguments('<install|uninstall|start|stop|restart|run>')
            .action((cmd) => {
            this.action = cmd;
        })
            .parse(process.argv);
        this.osCheck();
        this.setEnv();
        switch (this.action) {
            case 'install': {
                this.logger(`Installing ${this.serviceName} Service`);
                this.install();
                break;
            }
            case 'uninstall': {
                this.logger(`Removing ${this.serviceName} Service`);
                this.uninstall();
                break;
            }
            case 'start': {
                this.logger(`Starting ${this.serviceName} Service`);
                this.start();
                break;
            }
            case 'stop': {
                this.logger(`Stopping ${this.serviceName} Service`);
                this.stop();
                break;
            }
            case 'restart': {
                this.logger(`Restart ${this.serviceName} Service`);
                this.restart();
                break;
            }
            case 'run': {
                this.launch();
                break;
            }
            default: {
                commander.outputHelp();
                console.log('\nThe hb-service command is provided by homebridge-config-ui-x\n');
                console.log('Please provide a command:');
                console.log('    install                          install homebridge as a service');
                console.log('    uninstall                        remove the homebridge service');
                console.log('    start                            start the homebridge service');
                console.log('    stop                             stop the homebridge service');
                console.log('    restart                          restart the homebridge service');
                console.log('    run                              run homebridge daemon');
                process.exit(1);
            }
        }
    }
    logger(msg) {
        msg = `\x1b[37m[${new Date().toLocaleString()}]\x1b[0m ` +
            '\x1b[36m[HB Supervisor]\x1b[0m ' + msg;
        if (this.log) {
            this.log.write(msg + '\n');
        }
        else {
            console.log(msg);
        }
    }
    osCheck() {
        if (os.platform() !== 'win32') {
            this.logger('ERROR: This command is only supported on Windows 10.');
            process.exit(1);
        }
    }
    setEnv() {
        process.env.UIX_STORAGE_PATH = this.storagePath;
        process.env.UIX_CONFIG_PATH = path.resolve(this.storagePath, 'config.json');
        process.env.UIX_BASE_PATH = path.resolve(__dirname, '../../');
        process.env.UIX_SERVICE_MODE = '1';
    }
    launch() {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.storagePathCheck();
            this.logPath = path.resolve(this.storagePath, 'homebridge.log');
            this.logger(`Logging to ${this.logPath}`);
            this.log = fs.createWriteStream(this.logPath, { flags: 'a' });
            process.stdout.write = process.stderr.write = this.log.write.bind(this.log);
            yield this.configCheck();
            const node_modules = path.resolve(process.env.UIX_BASE_PATH, '..');
            this.homebridgeBinary = path.resolve(node_modules, 'homebridge', 'bin', 'homebridge');
            this.logger(`Homebridge Path: ${this.homebridgeBinary}`);
            this.uiBinary = path.resolve(process.env.UIX_BASE_PATH, 'dist', 'bin', 'standalone.js');
            this.logger(`UI Path: ${this.uiBinary}`);
            this.runHomebridge();
            this.runUi();
        });
    }
    runHomebridge() {
        this.homebridge = child_process.spawn(process.execPath, [
            this.homebridgeBinary,
            '-I',
            '-C',
            '-U',
            this.storagePath,
        ], {
            env: process.env,
            windowsHide: true,
        });
        this.logger(`Started Homebridge with PID: ${this.homebridge.pid}`);
        this.homebridge.stdout.on('data', (data) => {
            this.log.write(data);
        });
        this.homebridge.stderr.on('data', (data) => {
            this.log.write(data);
        });
        this.homebridge.on('close', (code, signal) => {
            this.handleHomebridgeClose(code, signal);
        });
    }
    handleHomebridgeClose(code, signal) {
        this.logger(`Homebridge Process Ended. Code: ${code}, Signal: ${signal}`);
        setTimeout(() => {
            this.logger('Restarting Homebridge...');
            this.runHomebridge();
        }, 5000);
    }
    runUi() {
        return __awaiter(this, void 0, void 0, function* () {
            yield Promise.resolve().then(() => require('../main'));
        });
    }
    install() {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.storagePathCheck();
            yield this.configCheck();
            const nssmPath = yield this.downloadNssm();
            const installCmd = `${nssmPath} install ${this.serviceName} ` +
                `"${process.execPath}" "${__filename}" run -I -U ${this.storagePath}`;
            const setUserDirCmd = `${nssmPath} set ${this.serviceName} AppEnvironmentExtra ":UIX_STORAGE_PATH=${this.storagePath}"`;
            try {
                child_process.execSync(installCmd);
                child_process.execSync(setUserDirCmd);
                yield this.start();
                console.log(`\nManage Homebridge by going to http://localhost:${this.uiPort} in your browser`);
                console.log(`Default Username: admin`);
                console.log(`Default Password: admin\n`);
            }
            catch (e) {
                console.error(e.toString());
                this.logger(`ERROR: Failed Operation`);
            }
        });
    }
    uninstall() {
        return __awaiter(this, void 0, void 0, function* () {
            const nssmPath = yield this.downloadNssm();
            const uninstallCmd = `${nssmPath} remove ${this.serviceName} confirm`;
            yield this.stop();
            try {
                child_process.execSync(uninstallCmd);
                this.logger(`Removed ${this.serviceName} Service.`);
            }
            catch (e) {
                console.error(e.toString());
                this.logger(`ERROR: Failed Operation`);
            }
        });
    }
    start() {
        return __awaiter(this, void 0, void 0, function* () {
            const nssmPath = yield this.downloadNssm();
            const stopCmd = `${nssmPath} start ${this.serviceName}`;
            try {
                this.logger(`Starting ${this.serviceName} Service...`);
                child_process.execSync(stopCmd);
                this.logger(`${this.serviceName} Started`);
            }
            catch (e) {
                this.logger(`Failed to start ${this.serviceName}`);
            }
        });
    }
    stop() {
        return __awaiter(this, void 0, void 0, function* () {
            const nssmPath = yield this.downloadNssm();
            const stopCmd = `${nssmPath} stop ${this.serviceName}`;
            try {
                this.logger(`Stopping ${this.serviceName} Service...`);
                child_process.execSync(stopCmd);
                this.logger(`${this.serviceName} Stopped`);
            }
            catch (e) {
                this.logger(`Failed to stop ${this.serviceName}`);
            }
        });
    }
    restart() {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.stop();
            setTimeout(() => __awaiter(this, void 0, void 0, function* () {
                yield this.start();
            }), 3000);
        });
    }
    downloadNssm() {
        return __awaiter(this, void 0, void 0, function* () {
            const downloadUrl = `https://github.com/oznu/nssm/releases/download/2.24-101-g897c7ad/nssm_${os.arch()}.exe`;
            const nssmPath = path.resolve(this.storagePath, 'nssm.exe');
            if (yield fs.pathExists(nssmPath)) {
                return nssmPath;
            }
            const nssmFile = fs.createWriteStream(nssmPath);
            this.logger(`Downloading NSSM from ${downloadUrl}`);
            return new Promise((resolve, reject) => {
                request({
                    url: downloadUrl,
                    method: 'GET',
                    encoding: null,
                }).pipe(nssmFile)
                    .on('finish', () => {
                    return resolve(nssmPath);
                })
                    .on('error', (err) => {
                    return reject(err);
                });
            });
        });
    }
    storagePathCheck() {
        return __awaiter(this, void 0, void 0, function* () {
            if (!(yield fs.pathExists(this.storagePath))) {
                this.logger(`Creating Homebridge directory: ${this.storagePath}`);
                yield fs.mkdirp(this.storagePath);
            }
        });
    }
    configCheck() {
        return __awaiter(this, void 0, void 0, function* () {
            if (!(yield fs.pathExists(process.env.UIX_CONFIG_PATH))) {
                this.logger(`Creating default config.json: ${process.env.UIX_CONFIG_PATH}`);
                return yield this.createDefaultConfig();
            }
            try {
                yield fs.readJson(process.env.UIX_CONFIG_PATH);
            }
            catch (e) {
                const backupFile = path.resolve(this.storagePath, 'config.json.invalid.' + new Date().getTime().toString());
                this.logger(`${process.env.UIX_CONFIG_PATH} does not contain valid JSON.`);
                this.logger(`Invalid config.json file has been backed up to ${backupFile}.`);
                yield fs.rename(process.env.UIX_CONFIG_PATH, backupFile);
                yield this.createDefaultConfig();
            }
        });
    }
    createDefaultConfig() {
        return __awaiter(this, void 0, void 0, function* () {
            yield fs.writeJson(process.env.UIX_CONFIG_PATH, {
                bridge: {
                    name: this.serviceName,
                    username: this.generateUsername(),
                    port: Math.floor(Math.random() * (52000 - 51000 + 1) + 51000),
                    pin: this.generatePin(),
                },
                accessories: [],
                platforms: [
                    {
                        name: 'Config',
                        port: this.uiPort,
                        platform: 'config',
                    },
                ],
            }, { spaces: 4 });
        });
    }
    generatePin() {
        let code = Math.floor(10000000 + Math.random() * 90000000) + '';
        code = code.split('');
        code.splice(3, 0, '-');
        code.splice(6, 0, '-');
        code = code.join('');
        return code;
    }
    generateUsername() {
        const hexDigits = '0123456789ABCDEF';
        let username = '0E:';
        for (let i = 0; i < 5; i++) {
            username += hexDigits.charAt(Math.round(Math.random() * 15));
            username += hexDigits.charAt(Math.round(Math.random() * 15));
            if (i !== 4) {
                username += ':';
            }
        }
        return username;
    }
}
function bootstrap() {
    return new HomebridgeServiceHelper();
}
bootstrap();
//# sourceMappingURL=hb-service.js.map