using namespace Microsoft.Win32;
using namespace System.Security.AccessControl;
using namespace System.Security.Principal;

enum SetupStage {
    Initialize
    Configure
    Install
}

$null = New-Module {
    [string] $configRoot = "HKLM:\Software\PortValhalla";
    [string] $stageOption = "Stage";
    [string] $finishedOption = "Finished";
    [RegistryKey] $key = $null;

    <#
        .SYNOPSIS
        Converts the specified path to linux and escapes it for the use in a script.

        .PARAMETER Path
        The path to convert.
    #>
    function ConvertTo-LinuxPath {
        param(
            [string] $Path
        )

        $job = Start-Job {
            $env:Value = Resolve-Path $Using:Path;
            $env:WSLENV = "Value/p";
            $result = wsl -- bash -c 'echo "$Value"';
            wsl -e printf "%q" "$result";
        };

        Receive-Job -Wait $job;
    }

    <#
        .SYNOPSIS
        Gets the registry key containing options related to the setup.
    #>
    function Get-SetupConfigKey {
        if (-not (Test-Path $configRoot)) {
            $key = New-Item $configRoot;
            $acl = Get-Acl $configRoot;

            $acl.AddAccessRule(
                [RegistryAccessRule]::new(
                    [SecurityIdentifier]::new([WellKnownSidType]::BuiltinUsersSid, $null),
                    [RegistryRights]::FullControl,
                    [InheritanceFlags]::ObjectInherit -bor [InheritanceFlags]::ContainerInherit,
                    [PropagationFlags]::None,
                    [AccessControlType]::Allow));

            Set-Acl $configRoot $acl;
        } else {
            $key = Get-Item $configRoot;
        }

        return $key;
    }

    <#
        .SYNOPSIS
        Gets a configuration option.

        .PARAMETER Name
        The name of the option to get.
    #>
    function Get-Config {
        param(
            [string] $Name
        )

        $scriptPath = "$PSScriptRoot/../../Common/Scripts/config.fish";

        function fish {
            wsl -- nix --extra-experimental-features "nix-command flakes" nixpkgs`#fish $args
        }

        fish -c ". $(ConvertTo-LinuxPath $scriptPath); getConfig $Name --json" | ConvertFrom-Json;
    }

    <#
        .SYNOPSIS
        Gets the value of an option related to the setup.

        .PARAMETER Name
        The name of the option value to get.
    #>
    function Get-SetupOption {
        param(
            [string] $Name
        )

        $key = Get-SetupConfigKey;

        if ($key.GetValueNames().Contains($Name)) {
            return $key.GetValue($Name);
        } else {
            return $null;
        }
    }

    <#
        .SYNOPSIS
        Sets the value of an option related to the setup.

        .PARAMETER Name
        The name of the option to set.

        .PARAMETER Value
        The value to set the option to.
    #>
    function Set-SetupOption {
        param(
            [string] $Name,
            $Value
        )

        $key = Get-SetupConfigKey;
        $null = Set-ItemProperty ($key.PSPath) -Name $Name -Value $Value;
    }

    <#
        .SYNOPSIS
        Gets the name of the current setup stage.
    #>
    function Get-Stage {
        $stage = Get-SetupOption $stageOption;

        if ($null -ne $stage) {
            $stage = [SetupStage]$stage;
        }

        return $stage;
    }

    <#
        .SYNOPSIS
        Sets the current stage.

        .PARAMETER Name
        The name to set the current stage to.
    #>
    function Set-Stage {
        param(
            $Name
        )

        if (-not (($null -eq $Name) -or ($Name -is [string]))) {
            $Name = ([SetupStage]$Name).ToString();
        }

        $null = Set-SetupOption $stageOption $Name;
    }

    <#
        .SYNOPSIS
        Gets a value indicating whether the setup has finished.
    #>
    function Get-IsFinished {
        return [bool] (Get-SetupOption $finishedOption);
    }

    <#
        .SYNOPSIS
        Sets a value indicating whether the setup has finished.
    #>
    function Set-IsFinished {
        param(
            $Value
        )

        Set-SetupOption $finishedOption $true;
    }

    <#
        .SYNOPSIS
        Checks whether the active session is executed with admin rights.
    #>
    function Test-Admin {
        net session 2> $null | Out-Null;
        return $?;
    }
}