#!/bin/pwsh
. "$PSScriptRoot/Entrypoints.ps1";
. "$PSScriptRoot/../../Common/Config/powershell/lib.ps1";

class Context {
    [string]$EntryPoint;
    [string]$RootDir;
    [string]$BackupName;
    [string[]]$UserNames;
    [string]$AdminName = "Admin";
    [string]$ConfigRoot = "HKLM:\Software\PortValhalla";
    [string]$RunOnceName = "PortValhalla";
    [string]$StagePropertyName = "Stage";

    Context() {
        try {
            $this.EntryPoint = Get-Entrypoint;
        }
        catch { }
    }

    [string] ProjectRoot() {
        return Resolve-Path (Join-Path $PSScriptRoot ".." ".." "..");
    }

    [string] BackupRoot() {
        if (-not $this.RootDir)
        {
            return Join-Path $this.ProjectRoot() "backup" $this.BackupName;
        }
        else
        {
            return $this.RootDir;
        }
    }

    [void] RemoveDesktopIcon($pattern) {
        Remove-Item "$env:PUBLIC/Desktop/$pattern";
        Remove-Item "~/Desktop/$pattern";
    }

    [void] RemoveTaskbarItem($pattern) {
        Import-Module -UseWindowsPowerShell PinnedItem;
        Get-PinnedItem -Type TaskBar | Where-Object { $_.Name -like "$pattern" } | Remove-PinnedItem;
    }

    [void] AddPowerShellProfileStatement([string] $statement) {
        $this.AddPowerShellProfileStatement($true, $statement);
    }

    [void] AddPowerShellProfileStatement([string] $category, [string] $statement) {
        $this.AddPowerShellProfileStatement($true, $category, $statement);
    }

    [void] AddPowerShellProfileStatement([bool] $system, [string] $statement) {
        $this.AddPowerShellProfileStatement($system, $null, $statement);
    }

    [void] AddPowerShellProfileStatement([bool] $system, [string] $category, [string] $statement) {
        if ($category) {
            $overwrite = $true;
        } else {
            $overwrite = $false;
        }

        $this.AddPowerShellProfileStatement($system, $category, $statement, $overwrite);
    }

    [void] AddPowerShellProfileStatement([bool] $system, [string] $category, [string] $statement, [bool] $overwrite) {
        if ($system) {
            Add-PowerShellProfileStatement -System -Category $category -Statement $statement -Overwrite $overwrite;
        } else {
            Add-PowerShellProfileStatement -Category $category -Statement $statement -Overwrite $overwrite;
        }
    }

    [Microsoft.Win32.RegistryKey] EnsureConfigKey() {
        if (-not (Test-Path $this.ConfigRoot)) {
            $null = New-Item $this.ConfigRoot;
            $acl = Get-Acl $this.ConfigRoot;

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

            Set-Acl $this.ConfigRoot $acl;
        }

        return Get-Item $this.ConfigRoot;
    }

    [object] Get([string] $key) {
        $configKey = $this.EnsureConfigKey();
        if ($configKey.GetValueNames().Contains($key)) {
            return $configKey.GetValue($key);
        } else {
            return $null;
        }
    }

    [void] Set([string] $key, $value) {
        $this.Set($key, $value, "ExpandString");
    }

    [void] Set([string] $key, $value, [Microsoft.Win32.RegistryValueKind] $type) {
        $configKey = $this.EnsureConfigKey();
        $null = Set-ItemProperty -Path $configKey.PSPath -Name $key -Value $value -Type $type;
    }

    [void] Remove([string] $key) {
        $configKey = $this.EnsureConfigKey();
        $null = Remove-ItemProperty -Path $configKey.PSPath -Name $key;
    }

    [void] SetStage([string] $name) {
        $this.Set($this.StagePropertyName, $name);
    }

    [string] GetStage() {
        return $this.Get($this.StagePropertyName);
    }

    [void] RemoveStage() {
        $this.Remove($this.StagePropertyName);
    }

    [string] ArchivePath($name) {
        return Join-Path $this.BackupRoot() "$name.7z";
    }

    [string] FileArchivePath($name) {
        return $this.ArchivePath($(Join-Path "Files" $name));
    }

    [string] SoftwareArchive([string]$softwareName) {
        return $this.ArchivePath($softwareName);
    }

    [void] Backup([string]$sourcePath, [string]$archivePath) {
        $this.Backup($sourcePath, $archivePath, @());
    }

    [void] Backup([string]$sourcePath, [string]$archivePath, [string[]]$arguments) {
        $this.Backup($sourcePath, $archivePath, $arguments, $true);
    }

    [void] Backup([string]$sourcePath, [string]$archivePath, [string[]]$arguments, [bool]$split) {
        if (Test-Path $archivePath) {
            Remove-Item -Recurse $archivePath;
        }

        if (Test-Path "$archivePath.*") {
            Remove-Item -Recurse "$archivePath.*";
        }

        Start-Process -WorkingDirectory "$sourcePath" `
            -FilePath "7z" `
            -ArgumentList (
                @(
                    "a",
                    "-xr!desktop.ini",
                    "-xr!thumbs.db",
                    "-xr!Thumbs.db",
                    "-slp",
                    $archivePath) + $arguments + $(
                        if ($split) {
                            @("-v2g");
                        } else {
                            @();
                        }
                    )) `
            -Wait `
            -NoNewWindow;
    }

    [void] Restore([string]$archivePath, [string]$destinationPath) {
        $this.Restore($archivePath, $destinationPath, @());
    }

    [void] Restore([string]$archivePath, [string]$destinationPath, [string[]] $arguments) {
        if (-not (Test-Path -PathType Leaf $archivePath)) {
            $archivePath = "$archivePath.001";
        }

        if (-not (Test-Path -PathType Leaf $archivePath)) {
            Write-Information (
                [string]::Join(
                    "`n",
                    @(
                        "An archive at the specified path $archivePath does not exist.",
                        "No restoration will be performed.")));
        }
        else {
            if (-not (Test-Path -PathType Container $destinationPath)) {
                New-Item -ItemType Directory "$destinationPath";
            }

            Start-Process -WorkingDirectory "$destinationPath" `
                -FilePath "7z" `
                -ArgumentList @("x", "$archivePath") + $arguments `
                -Wait `
                -NoNewWindow;
        }
    }

    [string] GetTempDirectory() {
        $tempDir = Join-Path $([System.IO.Path]::GetTempPath()) $([System.IO.Path]::GetRandomFileName());
        $null = New-Item -ItemType Directory $tempDir;
        return $tempDir;
    }

    [void] ProcessDefaultUserKey([System.Action[Microsoft.Win32.RegistryKey]] $action) {
        $rootPath = "HKLM:\DefaultUser";
        $regRootPath = $rootPath.Replace(":", "");
        $hivePath = "$env:SystemDrive\Users\Default\NTUSER.dat"
        $null = & reg load $regRootPath $hivePath;
        $root = Get-Item $rootPath;
        $action.Invoke($root);
        $root.Handle.Close();
        [System.GC]::Collect();
        & reg unload $regRootPath;
    }

    [void] ProcessLogonKey([System.Action[Microsoft.Win32.RegistryKey]] $action) {
        $key = Get-Item "HKLM:\Software\Microsoft\Windows NT\CurrentVersion\Winlogon";
        $action.Invoke($key);
    }

    [Microsoft.Win32.RegistryKey] GetSystemPolicyKey() {
        $keyPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System";
        return Get-Item "$keyPath";
    }

    [Microsoft.Win32.RegistryKey] GetRunOnceKey() {
        return $this.GetRunOnceKey($null);
    }

    [Microsoft.Win32.RegistryKey] GetRunOnceKey([Microsoft.Win32.RegistryKey] $userKey) {
        if (-not $userKey) {
            $userKey = Get-Item "HKCU:\";
        }

        Push-Location $userKey.PSPath;
        $runOncePath = "Software\Microsoft\Windows\CurrentVersion\RunOnce";

        if (-not (Test-Path $runOncePath)) {
            New-Item $runOncePath;
        }

        $result = Get-Item $runOncePath;
        Pop-Location;
        return $result;
    }

    [bool] GetUACState() {
        return [bool](Get-ItemPropertyValue -Path ($this.GetSystemPolicyKey().PSPath) -Name "EnableLUA");
    }

    [void] SetUACState([bool] $value) {
        $null = Set-ItemProperty -Path ($this.GetSystemPolicyKey().PSPath) -Name "EnableLUA" -Value ([int]$value);
    }

    [void] RegisterReboot() {
        $this.RegisterReboot($null);
    }

    [void] RegisterReboot([Microsoft.Win32.RegistryKey] $userKey) {
        $runOnceKey = $this.GetRunOnceKey($userKey);
        Set-ItemProperty -Path $runOnceKey.PSPath -Name $this.RunOnceName -Value "pwsh `"$($this.EntryPoint)`"" -Type "ExpandString";
        $runOnceKey.Handle.Close();
    }

    [void] RegisterNewUserReboot() {
        $this.ProcessDefaultUserKey({ param ($root) $this.RegisterReboot($root); });
    }

    [void] DeregisterNewUserReboot() {
        $this.ProcessDefaultUserKey({ param ($root) Remove-Item -Path $this.GetRunOnceKey($root).PSPath });
    }

    [void] SetAutologin($user, $pw) {
        $this.ProcessLogonKey(
            {
                param ($logon)
                $path = $logon.PSPath;
                Set-ItemProperty $path -Name "AutoAdminLogon" 1;
                Set-ItemProperty $path -Name "DefaultUserName" $user;

                if ($pw) {
                    Set-ItemProperty $path -Name "DefaultPassword" $pw;
                } else {
                    Remove-ItemProperty $path -Name "DefaultPassword";
                }
            });
    }

    [void] RemoveAutologin() {
        $this.ProcessLogonKey(
            {
                param ($logon)
                $path = $logon.PSPath;
                Set-ItemProperty $path -Name "AutoAdminLogon" 0;
                Remove-ItemProperty $path -Name "DefaultDomainName" -ErrorAction SilentlyContinue;
                Remove-ItemProperty $path -Name "DefaultUserName" -ErrorAction SilentlyContinue;
                Remove-ItemProperty $path -Name "DefaultPassword" -ErrorAction SilentlyContinue;
            });
    }

    [string] GetNextcloudConfigFile() {
        return "$env:APPDATA/Nextcloud/nextcloud.cfg";
    }

    [void] AddNextcloudSync([string] $localPath, [string] $targetPath) {
        $this.AddNextcloudSync($localPath, $targetPath, $false);
    }

    [void] AddNextcloudSync([string] $localPath, [string] $targetPath, [bool] $virtualFiles) {
        Write-Host "Adding a Nextcloud sync";
        Write-Information "$targetPath <=> $localPath";
        $pattern = "^\d+\\Folders(?:WithPlaceholders)?\\(\d+)";

        $folderID = (
            Get-Content $($this.GetNextcloudConfigFile()) | `
                Where-Object { $_ -match "$pattern" } | `
                ForEach-Object { $_ -replace "$pattern.*$","`$1" } | `
                Sort-Object -Unique | `
                Measure-Object -Maximum).Maximum + 1;

        $configName = "Folders";
        $localPath = $localPath.Replace("\", "/");
        $targetPath = $targetPath.Replace("\", "/");

        if ($virtualFiles) {
            $configName += "WithPlaceholders";
        }

        Write-Information "Stopping Nextcloud process";
        $nextcloudProcess = Get-Process nextcloud;
        $nextcloudPath = [string]$nextcloudProcess[0].Path;
        $nextcloudProcess | Stop-Process -Force;

        $accountSectionEntered = $false;
        $accountSectionLeft = $false;

        $newSettings = [string]::Join(
            "`n",
            @(
                "0\$configName\$folderID\localPath=$localPath",
                "0\$configName\$folderID\targetPath=$targetPath"));

        $oldContent = Get-Content ($this.GetNextcloudConfigFile());

        $(
            for ($i = 0; $i -lt $oldContent.Count; $i++) {
                $line = $oldContent[$i];

                if ($line -eq "[Accounts]") {
                    $accountSectionEntered = $true;
                }

                if ($line -eq "" -and $accountSectionEntered) {
                    $accountSectionLeft = $true;
                    $newSettings;
                }

                $line;

                if (
                    (-not $accountSectionLeft) -and
                    ($i -eq ($oldContent.Count - 1)))
                {
                    $newSettings;
                }
            }) | Set-Content ($this.GetNextcloudConfigFile());

        Write-Information "New nextcloud config:";
        Write-Information (Get-Content $($this.GetNextcloudConfigFile()) | Out-String);

        Write-Information "Restarting Nextcloud";
        Start-Process $nextcloudPath;
    }

    [void] Reboot() {
        Write-Host "Restarting Computer...";
        $this.RegisterReboot();
        Restart-Computer -Force;
        exit;
    }

    [void] PreventSleepMode() {
        $performanceScheme = "8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c";
        $currentScheme = [regex]::Match((powercfg /GETACTIVESCHEME), "Power Scheme GUID: ([0-9a-f-]+) ").Groups[1].Value;

        if ($currentScheme -ne $performanceScheme) {
            Write-Information "Disabling Power Save mode";
            $this.Set("Power Scheme", $currentScheme);
            powercfg /S 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c;
        }
    }

    [void] Cleanup() {
        $this.DeregisterNewUserReboot();
        $this.RemoveAutologin();
        $this.SetUACState($true);
        $originalScheme = $this.Get("Power Scheme");
        Remove-Item $($this.EnsureConfigKey().PSPath);

        if ($originalScheme) {
            Write-Information "Reset power plan to original state";
            powercfg /S $originalScheme;
        }
    }
}