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

enum WindowsInstallerStage {
    Initialize
    Run
    Cleanup
    Completed
}

enum SetupStage {
    Initialize
    Configure
    Install
    CreateUser
}

enum BackupStage {
    Initialize
    Backup
    BackupUsers
}

enum UserStage {
    Create
    Configure
    Cleanup
    Completed
}

$null = New-Module {
    [string] $configRoot = "HKLM:\Software\PortValhalla";
    [string] $stageOption = "Stage";
    [string] $setupStageOption = "SetupStage";
    [string] $backupStageOption = "BackupStage";
    [string] $userOption = "SetupUser";
    [string] $userStageOption = "UserStage";
    [string] $accountOption = "MSAccount";
    [string] $finishedOption = "Finished";
    [RegistryKey] $key = $null;

    <#
        .SYNOPSIS
        Edits the `HKEY_CURRENT_USER` key of the default user using the specified action.

        .PARAMETER Action
        The action to execute on the key.
    #>
    function Edit-DefaultUserKey {
        param(
            [scriptblock] $Action
        )

        $rootPath = "HKLM:\DefaultUser";
        $regRootPath = $rootPath -replace "^(\w+):([\\/]|$)", "`$1`$2";
        $hivePath = "$env:SystemDrive\Users\Default\NTUSER.dat";
        $null = & reg load $regRootPath $hivePath;
        [RegistryKey] $root = Get-Item $rootPath;
        & $Action -Key $root;
        $root.Handle.Close();
        [System.GC]::Collect();
        & reg unload $regRootPath;
    }

    <#
        .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
        Sets a message to show on the login screen.

        .PARAMETER Caption
        The title of the message.

        .PARAMETER Message
        The text of the message.
    #>
    function Set-BootMessage {
        param(
            [string] $Caption,
            [string] $Message
        )

        $options = @{
            legalnoticecaption = $Caption;
            legalnoticetext    = $Message;
        };

        foreach ($key in $options.Keys) {
            Set-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" `
                -Name $key `
                -Type "String" `
                -Value ($options[$key]);
        }
    }

    <#
        .SYNOPSIS
        Disables the boot message.
    #>
    function Disable-BootMessage {
        Set-BootMessage;
    }

    <#
        .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).PSPath;

        if ($null -eq $Value) {
            Remove-ItemProperty $key -Name $Name;
        }
        else {
            $null = Set-ItemProperty $key -Name $Name -Value $Value;
        }
    }

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

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

        return $stage;
    }

    <#
        .SYNOPSIS
        Sets the name of the current stage of the Windows install script action.

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

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

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

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

        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-SetupStage {
        param(
            $Name
        )

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

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

    <#
        .SYNOPSIS
        Gets the name of the current stage of the backup.
    #>
    function Get-BackupStage {
        $stage = Get-SetupOption $backupStageOption;

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

        return $stage;
    }

    <#
        .SYNOPSIS
        Sets the current stage of the backup.

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

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

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

    <#
        .SYNOPSIS
        Gets the current user to set up.
    #>
    function Get-CurrentUser {
        return (Get-SetupOption $userOption) ?? 0;
    }

    <#
        .SYNOPSIS
        Sets the index of the current user to set up.

        .PARAMETER Value
        The index of the user to set up.
    #>
    function Set-CurrentUser {
        param(
            [int] $Value
        )

        Set-SetupOption $userOption $value;
    }

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

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

        return $stage;
    }

    <#
        .SYNOPSIS
        Sets the current stage of the user setup.

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

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

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

    <#
        .SYNOPSIS
        Gets the name of the microsoft account to create.
    #>
    function Get-MSAccountName {
        return Get-SetupOption $accountOption;
    }

    <#
        .SYNOPSIS
        Sets the name of the microsoft account to create.

        .PARAMETER Name
        The name of the microsoft account to create.
    #>
    function Set-MSAccountName {
        param(
            [string] $Name
        )

        Set-SetupOption $accountOption $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.

        .PARAMETER Value
        The value to set the finished state to.
    #>
    function Set-IsFinished {
        param(
            $Value
        )

        Set-SetupOption $finishedOption $true;
    }
};