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

import { chmod } from "fs/promises";
import * as os from "os";
import * as path from "path";
import * as vscode from "vscode";
import * as WinReg from "winreg";
import * as util from "../common/util";

import { resolveArduinoPath } from "../common/platform";

import * as Logger from "../logger/logger";
import { VscodeSettings } from "./vscodeSettings";

export interface IArduinoSettings {
    arduinoPath: string;
    commandPath: string;
    defaultExamplePath: string;
    packagePath: string;
    defaultPackagePath: string;
    defaultLibPath: string;
    sketchbookPath: string;
    preferencePath: string;
    preferences: Map<string, string>;
    useArduinoCli: boolean;
    usingBundledArduinoCli: boolean;
    analyzeOnSettingChange: boolean;
    reloadPreferences(): void;
}

export class ArduinoSettings implements IArduinoSettings {
    private _arduinoPath: string;

    private _commandPath: string;

    private _packagePath: string;

    private _sketchbookPath: string;

    private _preferences: Map<string, string>;

    private _useArduinoCli: boolean;

    private _usingBundledArduinoCli: boolean = false;

    private readonly bundledArduinoCliName: { [platform: string]: string } = {
        "darwin-arm64": "arduino-cli.app",
        "darwin-x64": "arduino-cli.app",
        "linux-arm64": "arduino-cli.app",
        "linux-armhf": "arduino-cli.app",
        "linux-x64": "arduino-cli.app",
        "win32-ia32": "arduino-cli.exe",
        "win32-x64": "arduino-cli.exe",
    };

    public constructor(private readonly _context: vscode.ExtensionContext) {
    }

    public async initialize() {
        const platform = os.platform();
        this._commandPath = VscodeSettings.getInstance().commandPath;
        this._useArduinoCli = VscodeSettings.getInstance().useArduinoCli;
        await this.tryResolveArduinoPath();
        if (platform === "win32") {
            await this.updateWindowsPath();
            if (this._commandPath === "") {
                this._useArduinoCli ? this._commandPath = "arduino-cli.exe" : this._commandPath = "arduino_debug.exe";
            }
        } else if (platform === "linux") {
            if (util.directoryExistsSync(path.join(this._arduinoPath, "portable"))) {
                this._packagePath = path.join(this._arduinoPath, "portable");
            } else {
                this._packagePath = path.join(process.env.HOME, ".arduino15");
            }

            if (this.preferences.get("sketchbook.path")) {
                if (util.directoryExistsSync(path.join(this._arduinoPath, "portable"))) {
                    this._sketchbookPath = path.join(this._arduinoPath, "portable", this.preferences.get("sketchbook.path"));
                } else {
                    this._sketchbookPath = this.preferences.get("sketchbook.path");
                }
            } else {
                this._sketchbookPath = path.join(process.env.HOME, "Arduino");
            }

            if (this._commandPath === "" && !this._useArduinoCli) {
                this._commandPath = "arduino";
            }
        } else if (platform === "darwin") {
            if (util.directoryExistsSync(path.join(this._arduinoPath, "portable"))) {
                this._packagePath = path.join(this._arduinoPath, "portable");
            } else {
                this._packagePath = path.join(process.env.HOME, "Library/Arduino15");
            }

            if (this.preferences.get("sketchbook.path")) {
                if (util.directoryExistsSync(path.join(this._arduinoPath, "portable"))) {
                    this._sketchbookPath = path.join(this._arduinoPath, "portable", this.preferences.get("sketchbook.path"));
                } else {
                    this._sketchbookPath = this.preferences.get("sketchbook.path");
                }
            } else {
                this._sketchbookPath = path.join(process.env.HOME, "Documents/Arduino");
            }

            if (this._commandPath === "" && !this._useArduinoCli) {
                this._commandPath = "/Contents/MacOS/Arduino";
            }
        }
    }

    public get arduinoPath(): string {
        return this._arduinoPath;
    }

    public get defaultExamplePath(): string {
        if (os.platform() === "darwin") {
            return path.join(util.resolveMacArduinoAppPath(this._arduinoPath, this._useArduinoCli), "/Contents/Java/examples");
        } else {
            return path.join(this._arduinoPath, "examples");
        }
    }

    public get packagePath(): string {
        return this._packagePath;
    }

    public get defaultPackagePath(): string {
        if (os.platform() === "darwin") {
            return path.join(util.resolveMacArduinoAppPath(this._arduinoPath, this._useArduinoCli), "/Contents/Java/hardware");
        } else { // linux and win32.
            return path.join(this._arduinoPath, "hardware");
        }
    }

    public get defaultLibPath(): string {
        if (os.platform() === "darwin") {
            return path.join(util.resolveMacArduinoAppPath(this._arduinoPath, this._useArduinoCli), "/Contents/Java/libraries");
        } else { // linux and win32
            return path.join(this._arduinoPath, "libraries");
        }
    }

    public get commandPath(): string {
        const platform = os.platform();
        if (platform === "darwin" && !this._usingBundledArduinoCli) {
            return path.join(util.resolveMacArduinoAppPath(this._arduinoPath, this._useArduinoCli), path.normalize(this._commandPath));
        } else {
            return path.join(this._arduinoPath, path.normalize(this._commandPath));
        }
    }

    public get sketchbookPath() {
        return this._sketchbookPath;
    }

    public get preferencePath() {
        return path.join(this.packagePath, "preferences.txt");
    }

    public get preferences() {
        if (!this._preferences) {
            this._preferences = util.parseConfigFile(this.preferencePath);
        }
        return this._preferences;
    }

    public get useArduinoCli() {
        return this._useArduinoCli;
    }

    public get usingBundledArduinoCli() {
        return this._usingBundledArduinoCli;
    }
    public get analyzeOnSettingChange(): boolean {
        return VscodeSettings.getInstance().analyzeOnSettingChange;
    }

    public reloadPreferences() {
        this._preferences = util.parseConfigFile(this.preferencePath);
        if (this.preferences.get("sketchbook.path")) {
            if (util.directoryExistsSync(path.join(this._arduinoPath, "portable"))) {
                this._sketchbookPath = path.join(this._arduinoPath, "portable", this.preferences.get("sketchbook.path"));
            } else {
                this._sketchbookPath = this.preferences.get("sketchbook.path");
            }
        }
    }

    /**
     * For Windows platform, there are two situations here:
     *  - User change the location of the default *Documents* folder.
     *  - Use the windows store Arduino app.
     */
    private async updateWindowsPath(): Promise<void> {
        let folder;
        try {
            folder = await util.getRegistryValues(WinReg.HKCU,
                "\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders",
                "Personal");
        } catch (ex) {
        }
        if (!folder) {
            folder = path.join(process.env.USERPROFILE, "Documents");
        }
        // For some case, docFolder parsed from win32 registry looks like "%USERPROFILE%\Documents,
        // Should replace the environment variables with actual value.
        folder = folder.replace(/%([^%]+)%/g, (match, p1) => {
            return process.env[p1];
        });
        if (util.directoryExistsSync(path.join(this._arduinoPath, "portable"))) {
            this._packagePath = path.join(this._arduinoPath, "portable");
        } else if (util.fileExistsSync(path.join(this._arduinoPath, "AppxManifest.xml"))) {
            this._packagePath = path.join(folder, "ArduinoData");
        } else {
            this._packagePath = path.join(process.env.LOCALAPPDATA, "Arduino15");
        }

        if (this.preferences.get("sketchbook.path")) {
            if (util.directoryExistsSync(path.join(this._arduinoPath, "portable"))) {
                this._sketchbookPath = path.join(this._arduinoPath, "portable", this.preferences.get("sketchbook.path"));
            } else {
                this._sketchbookPath = this.preferences.get("sketchbook.path");
            }
        } else {
            this._sketchbookPath = path.join(folder, "Arduino");
        }
    }

    private async bundledArduinoCliPath(): Promise<string | undefined> {
        const platform = await util.getPlatform();
        const name = this.bundledArduinoCliName[platform];
        if (!name) {
            return undefined;
        }
        return this._context.asAbsolutePath(path.join("assets", "platform", platform, "arduino-cli", name));
    }

    private async tryResolveArduinoPath(): Promise<void> {
        // Query arduino path sequentially from the following places such as "vscode user settings", "system environment variables",
        // "usual software installation directory for each os".
        // 1. Search vscode user settings first.
        const configValue = VscodeSettings.getInstance().arduinoPath;
        if (!configValue || !configValue.trim()) {
            // 2. Resolve arduino path from the bundled arduino-cli, if CLI support is enabled.
            const bundledPath = await this.bundledArduinoCliPath();
            if (bundledPath && this._useArduinoCli && !this._commandPath) {
                // The extension VSIX stripped the executable bit, so we need to set it.
                // 0x755 means rwxr-xr-x (read and execute for everyone, write for owner).
                await chmod(bundledPath, 0o755);
                this._usingBundledArduinoCli = true;
                Logger.traceUserData("using-bundled-arduino-cli");
                this._arduinoPath = path.dirname(bundledPath);
                this._commandPath = path.basename(bundledPath);
            } else {
                // 3 & 4. Resolve arduino path from system environment variables and usual software installation directory.
                this._arduinoPath = await Promise.resolve(resolveArduinoPath());
            }
        } else {
            this._arduinoPath = configValue;
        }
    }
}