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

enum SetupStage {
    Initialize
    Configure
    Install
    CreateUser
}

$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
        Runs a script based on the `config.fish` script.

        .PARAMETER Script
        The script to run.
    #>
    function Invoke-ConfigScript {
        param(
            [string] $Script
        )

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

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

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

    <#
        .SYNOPSIS
        Gets a configuration option.

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

        Invoke-ConfigScript "getConfig $Name --json $ArgumentList";
    }

    <#
        .SYNOPSIS
        Gets the attributes of a configuration object.

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

        Invoke-ConfigScript "getAttributes $Name";
    }

    <#
        .SYNOPSIS
        Gets the names of the users to create.
    #>
    function Get-Users {
        Get-Attributes "valhalla.windows.users";
    }

    <#
        .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 specified software collection is enabled.

        .PARAMETER Name
        The name of the collection to check.
    #>
    function Test-Collection {
        param(
            [string] $Name
        )

        Get-Config "valhalla.software.$Name";
    }

    <#
        .SYNOPSIS
        Checks whether the current user is the setup user.
    #>
    function Test-SetupUser {
        $env:UserName -eq (Get-Config "valhalla.windows.setupUser");
    }

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