// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

import * as vscode from "vscode";
import { dispose as disposeTelemetryWrapper, initializeFromJsonFile, instrumentOperation,
    instrumentOperationAsVsCodeCommand } from "vscode-extension-telemetry-wrapper";
import * as commands from "./commands";
import { JavaDebugConfigurationProvider } from "./configurationProvider";
import { HCR_EVENT, JAVA_LANGID, USER_NOTIFICATION_EVENT } from "./constants";
import { initializeCodeLensProvider, startDebugging } from "./debugCodeLensProvider";
import { handleHotCodeReplaceCustomEvent, initializeHotCodeReplace } from "./hotCodeReplace";
import { IMainMethod, resolveMainMethod } from "./languageServerPlugin";
import { logger, Type } from "./logger";
import * as utility from "./utility";

export async function activate(context: vscode.ExtensionContext) {
    await initializeFromJsonFile(context.asAbsolutePath("./package.json"));
    await instrumentOperation("activation", initializeExtension)(context);
}

function initializeExtension(operationId: string, context: vscode.ExtensionContext) {
    logger.initialize(context);
    logger.log(Type.ACTIVATEEXTENSION, {}); // TODO: Activation belongs to usage data, remove this line.
    logger.log(Type.USAGEDATA, {
        description: "activateExtension",
    });

    registerDebugEventListener(context);
    context.subscriptions.push(logger);
    context.subscriptions.push(vscode.debug.registerDebugConfigurationProvider("java", new JavaDebugConfigurationProvider()));
    context.subscriptions.push(instrumentOperationAsVsCodeCommand("JavaDebug.SpecifyProgramArgs", async () => {
        return specifyProgramArguments(context);
    }));
    context.subscriptions.push(instrumentOperationAsVsCodeCommand("java.debug.hotCodeReplace", applyHCR));
    context.subscriptions.push(instrumentOperationAsVsCodeCommand("java.debug.runJavaFile", async (uri: vscode.Uri) => {
        await runJavaFile(uri, true);
    }));
    context.subscriptions.push(instrumentOperationAsVsCodeCommand("java.debug.debugJavaFile", async (uri: vscode.Uri) => {
        await runJavaFile(uri, false);
    }));
    initializeHotCodeReplace(context);
    initializeCodeLensProvider(context);
}

// this method is called when your extension is deactivated
export async function deactivate() {
    await disposeTelemetryWrapper();
}

function registerDebugEventListener(context: vscode.ExtensionContext) {
    const measureKeys = ["duration"];
    context.subscriptions.push(vscode.debug.onDidTerminateDebugSession((e) => {
        if (e.type !== "java") {
            return;
        }
        fetchUsageData().then((ret) => {
            if (Array.isArray(ret) && ret.length) {
                ret.forEach((entry) => {
                    const commonProperties: any = {};
                    const measureProperties: any = {};
                    for (const key of Object.keys(entry)) {
                        if (measureKeys.indexOf(key) >= 0) {
                            measureProperties[key] = entry[key];
                        } else {
                            commonProperties[key] = String(entry[key]);
                        }
                    }
                    logger.log(entry.scope === "exception" ? Type.EXCEPTION : Type.USAGEDATA, commonProperties, measureProperties);
                });
            }
        });
    }));

    context.subscriptions.push(vscode.debug.onDidReceiveDebugSessionCustomEvent((customEvent) => {
        const t = customEvent.session ? customEvent.session.type : undefined;
        if (t !== JAVA_LANGID) {
            return;
        }
        if (customEvent.event === HCR_EVENT) {
            handleHotCodeReplaceCustomEvent(customEvent);
        } else if (customEvent.event === USER_NOTIFICATION_EVENT) {
            handleUserNotification(customEvent);
        }
    }));
}

function handleUserNotification(customEvent) {
    if (customEvent.body.notificationType === "ERROR") {
        utility.showErrorMessageWithTroubleshooting({
            message: customEvent.body.message,
        });
    } else if (customEvent.body.notificationType === "WARNING") {
        utility.showWarningMessageWithTroubleshooting({
            message: customEvent.body.message,
        });
    } else {
        vscode.window.showInformationMessage(customEvent.body.message);
    }
}

function fetchUsageData() {
    return commands.executeJavaLanguageServerCommand(commands.JAVA_FETCH_USAGE_DATA);
}

function specifyProgramArguments(context: vscode.ExtensionContext): Thenable<string> {
    const javaDebugProgramArgsKey = "JavaDebugProgramArgs";

    const options: vscode.InputBoxOptions = {
        ignoreFocusOut: true,
        placeHolder: "Enter program arguments or leave empty to pass no args",
    };

    const prevArgs = context.workspaceState.get(javaDebugProgramArgsKey, "");
    if (prevArgs.length > 0) {
        options.value = prevArgs;
    }

    return vscode.window.showInputBox(options).then((text) => {
        // When user cancels the input box (by pressing Esc), the text value is undefined.
        if (text !== undefined) {
            context.workspaceState.update(javaDebugProgramArgsKey, text);
        }

        return text || " ";
    });
}

async function applyHCR() {
    const autobuildConfig: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration("java.autobuild");
    if (!autobuildConfig.enabled) {
        const ans = await vscode.window.showWarningMessage(
            "The hot code replace feature requires you to enable the autobuild flag, do you want to enable it?",
            "Yes", "No");
        if (ans === "Yes") {
            await autobuildConfig.update("enabled", true);
            // Force an incremental build to avoid auto build is not finishing during HCR.
            try {
                await commands.executeJavaExtensionCommand(commands.JAVA_BUILD_WORKSPACE, false);
            } catch (err) {
                // do nothing.
            }
        }
    }

    const debugSession: vscode.DebugSession = vscode.debug.activeDebugSession;
    if (!debugSession) {
        return;
    }

    return vscode.window.withProgress({ location: vscode.ProgressLocation.Window }, async (progress) => {
        progress.report({ message: "Applying code changes..." });

        const response = await debugSession.customRequest("redefineClasses");
        if (!response || !response.changedClasses || !response.changedClasses.length) {
            vscode.window.showWarningMessage("Cannot find any changed classes for hot replace!");
        }
    });
}

async function runJavaFile(uri: vscode.Uri, noDebug: boolean) {
    try {
        // Wait for Java Language Support extension being activated.
        await utility.getJavaExtensionAPI();
    } catch (ex) {
        if (ex instanceof utility.JavaExtensionNotActivatedError) {
            utility.guideToInstallJavaExtension();
            return;
        }

        throw ex;
    }

    const mainMethods: IMainMethod[] = await resolveMainMethod(uri);
    if (!mainMethods || !mainMethods.length) {
        vscode.window.showErrorMessage(
            "Error: Main method not found in the file, please define the main method as: public static void main(String[] args)");
        return;
    }

    const projectName = mainMethods[0].projectName;
    let mainClass = mainMethods[0].mainClass;
    if (mainMethods.length > 1) {
        mainClass = await vscode.window.showQuickPick(mainMethods.map((mainMethod) => mainMethod.mainClass), {
            placeHolder: "Select the main class to launch.",
        });
    }

    if (!mainClass) {
        return;
    }

    await startDebugging(mainClass, projectName, uri, noDebug);
}
