// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.

import {OutputChannelLogger} from "../log/OutputChannelLogger";
import {PromiseUtil} from "../../common/node/promise";
import {PlistBuddy} from "./plistBuddy";
import {SimulatorPlist} from "./simulatorPlist";
import * as nls from "vscode-nls";
nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone })();
const localize = nls.loadMessageBundle();

export class IOSDebugModeManager {
    public static WEBSOCKET_EXECUTOR_NAME = "RCTWebSocketExecutor";
    private static EXECUTOR_CLASS_SETTING_NAME = ":RCTDevMenu:executorClass";
    private static REMOTE_DEBUGGING_SETTING_NAME = ":RCTDevMenu:isDebuggingRemotely";
    private static MAX_RETRIES = 5;
    private static DELAY_UNTIL_RETRY = 2000;
    private logger: OutputChannelLogger = OutputChannelLogger.getMainChannel();

    private projectRoot: string;
    private iosProjectRoot: string;
    private simulatorPlist: SimulatorPlist;

    constructor(iosProjectRoot: string, projectRoot: string, scheme?: string) {
        this.projectRoot = projectRoot;
        this.iosProjectRoot = iosProjectRoot;
        this.simulatorPlist = new SimulatorPlist(this.iosProjectRoot, this.projectRoot, scheme);
    }

    public setSimulatorRemoteDebuggingSetting(enable: boolean, configuration?: string, productName?: string): Promise<void> {
        const plistBuddy = new PlistBuddy();

        // Find the plistFile with the configuration setting
        // There is a race here between us checking for the plist file, and the application starting up.
        return this.findPListFile(configuration, productName)
            .then((plistFile: string) => {
                // Set the executorClass to be RCTWebSocketExecutor so on the next startup it will default into debug mode
                // This is approximately equivalent to clicking the "Debug in Chrome" button

                return (enable
                    ? plistBuddy.setPlistProperty(plistFile, IOSDebugModeManager.EXECUTOR_CLASS_SETTING_NAME, IOSDebugModeManager.WEBSOCKET_EXECUTOR_NAME)
                    : plistBuddy.deletePlistProperty(plistFile, IOSDebugModeManager.EXECUTOR_CLASS_SETTING_NAME))
                    .then(() => plistBuddy.setPlistBooleanProperty(plistFile, IOSDebugModeManager.REMOTE_DEBUGGING_SETTING_NAME, enable));
            });
    }

    public getSimulatorRemoteDebuggingSetting(configuration?: string, productName?: string): Promise<boolean> {
        return this.findPListFile(configuration, productName)
            .then((plistFile: string) => {
                // Attempt to read from the file, but if the property is not defined then return the empty string
                return Promise.all([
                    new PlistBuddy().readPlistProperty(plistFile, IOSDebugModeManager.EXECUTOR_CLASS_SETTING_NAME),
                    new PlistBuddy().readPlistProperty(plistFile, IOSDebugModeManager.REMOTE_DEBUGGING_SETTING_NAME),
                ])
                    .then(([executorClassName, remoteDebugEnabled]) => {
                        return executorClassName === IOSDebugModeManager.WEBSOCKET_EXECUTOR_NAME
                            && remoteDebugEnabled === "true";
                    })
                    .catch(() => false);
            });
    }

    public findPListFile(configuration?: string, productName?: string): Promise<string> {
        const pu = new PromiseUtil();
        const failureString = localize("UnableToFindPlistFileToConfigureDebugging", "Unable to find plist file to configure debugging");

        return pu.retryAsync(
            () =>
                this.tryOneAttemptToFindPListFile(configuration, productName), // Operation to retry until successful
            (file: string) =>
                file !== "", // Condition to check if the operation was successful, and this logic is done
            IOSDebugModeManager.MAX_RETRIES,
            IOSDebugModeManager.DELAY_UNTIL_RETRY,
            failureString); // Error to show in case all retries fail
    }

    private tryOneAttemptToFindPListFile(configuration?: string, productName?: string): Promise<string> {
        return this.simulatorPlist.findPlistFile(configuration, productName).catch(reason => {
            this.logger.debug(`Failed one attempt to find plist file: ${reason}`);
            return "";
        });
    }
}
