<?php
function fail($app_id, $message) {
    file_put_contents("php://stderr", $app_id . ": " . $message . "\n");
    exit(1);
}

function path_join($left, $right) {
    if ($left === "") {
        return $right;
    }
    if ($right === "") {
        return $left;
    }
    return rtrim($left, "/") . "/" . ltrim($right, "/");
}

function default_home($app_id) {
    $home = getenv("HOME");
    if ($home === false || $home === "") {
        fail($app_id, "HOME is not set");
    }
    return $home;
}

function app_id_from_argv($argv) {
    $raw = "";
    if (count($argv) > 0) {
        $raw = $argv[0];
    }
    if ($raw === "") {
        return "pw-wine-prefix-delta-startwrapper";
    }
    return basename($raw);
}

function ensure_dir($app_id, $path) {
    if ($path === "" || is_dir($path)) {
        return;
    }
    if (!mkdir($path, 0775, true) && !is_dir($path)) {
        fail($app_id, "cannot create directory: " . $path);
    }
}

function sha256_file_checked($app_id, $path) {
    $digest = hash_file("sha256", $path);
    if ($digest === false) {
        fail($app_id, "cannot hash file: " . $path);
    }
    return $digest;
}

function manifest_digest($manifest, $relpath) {
    if (!is_file($manifest)) {
        return "";
    }
    $lines = file($manifest, 2);
    if ($lines === false) {
        return "";
    }
    foreach ($lines as $raw_line) {
        $line = trim($raw_line);
        if ($line === "" || substr($line, 0, 1) === "#") {
            continue;
        }
        $parts = explode("\t", $line);
        if (count($parts) >= 2 && $parts[1] === $relpath) {
            return $parts[0];
        }
    }
    return "";
}

function append_manifest_entry($app_id, $manifest, $digest, $relpath) {
    ensure_dir($app_id, dirname($manifest));
    file_put_contents($manifest, $digest . "\t" . $relpath . "\n", 8);
}

function shell_env_assignment($name, $value) {
    return $name . "=" . escapeshellarg($value) . " ";
}

function run_shell($command, $cwd, $quiet) {
    if ($cwd !== "") {
        $command = "cd " . escapeshellarg($cwd) . " && " . $command;
    }
    $code = 0;
    if ($quiet) {
        passthru($command . " >/dev/null 2>&1", $code);
    } else {
        passthru($command, $code);
    }
    return $code;
}

function config_value($config, $key) {
    $lines = file($config, 2);
    if ($lines === false) {
        return "";
    }
    foreach ($lines as $raw_line) {
        $line = trim($raw_line);
        if ($line === "" || substr($line, 0, 1) === "#") {
            continue;
        }
        $parts = explode("\t", $line);
        if (count($parts) >= 2 && $parts[0] === $key) {
            return $parts[1];
        }
    }
    return "";
}

function config_values_text($config, $key) {
    $values = "";
    $lines = file($config, 2);
    if ($lines === false) {
        return "";
    }
    foreach ($lines as $raw_line) {
        $line = trim($raw_line);
        if ($line === "" || substr($line, 0, 1) === "#") {
            continue;
        }
        $parts = explode("\t", $line);
        if (count($parts) >= 2 && $parts[0] === $key) {
            if ($values !== "") {
                $values = $values . "\n";
            }
            $values = $values . $parts[1];
        }
    }
    return $values;
}

function ensure_wine_prefix($app_id, $user_prefix) {
    if (is_dir($user_prefix)) {
        return;
    }
    ensure_dir($app_id, dirname($user_prefix));
    $code = run_shell(shell_env_assignment("WINEPREFIX", $user_prefix) . "wineboot -u", "", false);
    if ($code !== 0) {
        fail($app_id, "wineboot failed for prefix: " . $user_prefix);
    }
}

function copy_file_preserving_parent($app_id, $source, $target) {
    ensure_dir($app_id, dirname($target));
    if (!copy($source, $target)) {
        fail($app_id, "cannot copy " . $source . " to " . $target);
    }
}

function apply_registry_patches($app_id, $opt_root, $user_prefix, $state_manifest) {
    $manifest = path_join($opt_root, "registry-patches.tsv");
    if (!is_file($manifest)) {
        return;
    }
    $lines = file($manifest, 2);
    if ($lines === false) {
        return;
    }
    $patch_root = path_join($opt_root, "registry-patches");
    $next = $state_manifest . ".next";
    file_put_contents($next, "");
    foreach ($lines as $raw_line) {
        $line = trim($raw_line);
        if ($line === "" || substr($line, 0, 1) === "#") {
            continue;
        }
        $parts = explode("\t", $line);
        if (count($parts) < 5) {
            continue;
        }
        $relpath = $parts[0];
        $after_digest = $parts[2];
        $patch_digest = $parts[3];
        $patch_file = path_join($patch_root, $parts[4]);
        $target = path_join($user_prefix, $relpath);
        $previous = manifest_digest($state_manifest, $relpath);

        if ($previous === $patch_digest) {
            append_manifest_entry($app_id, $next, $patch_digest, $relpath);
            continue;
        }
        if (is_file($target) && sha256_file_checked($app_id, $target) === $after_digest) {
            append_manifest_entry($app_id, $next, $patch_digest, $relpath);
            continue;
        }

        $dry_run = "patch --forward --silent --dry-run -p0 -i " . escapeshellarg($patch_file);
        if (run_shell($dry_run, $user_prefix, true) === 0) {
            $apply = "patch --forward --silent -p0 -i " . escapeshellarg($patch_file);
            $code = run_shell($apply, $user_prefix, false);
            if ($code === 0) {
                append_manifest_entry($app_id, $next, $patch_digest, $relpath);
            }
        } else {
            file_put_contents("php://stderr", $app_id . ": keeping user-modified registry file unchanged: " . $relpath . "\n");
            if ($previous !== "") {
                append_manifest_entry($app_id, $next, $previous, $relpath);
            }
        }
    }
    rename($next, $state_manifest);
}

function sync_user_prefix($app_id, $opt_root, $user_prefix, $state_manifest) {
    $manifest = path_join($opt_root, "manifest.tsv");
    if (!is_file($manifest)) {
        fail($app_id, "missing package manifest: " . $manifest);
    }
    $lines = file($manifest, 2);
    if ($lines === false) {
        fail($app_id, "cannot read package manifest: " . $manifest);
    }
    ensure_wine_prefix($app_id, $user_prefix);
    ensure_dir($app_id, dirname($state_manifest));

    $delta_root = path_join($opt_root, "prefix-delta");
    $next = $state_manifest . ".next";
    file_put_contents($next, "");
    foreach ($lines as $raw_line) {
        $line = trim($raw_line);
        if ($line === "" || substr($line, 0, 1) === "#") {
            continue;
        }
        $parts = explode("\t", $line);
        if (count($parts) < 2) {
            continue;
        }
        $new_digest = $parts[0];
        $relpath = $parts[1];
        $source = path_join($delta_root, $relpath);
        $target = path_join($user_prefix, $relpath);
        $old_digest = manifest_digest($state_manifest, $relpath);
        $copy_needed = false;

        if (!file_exists($target)) {
            $copy_needed = true;
        } elseif (is_file($target) && $old_digest === "") {
            $copy_needed = true;
        } elseif (is_file($target) && $old_digest !== "" && sha256_file_checked($app_id, $target) === $old_digest) {
            $copy_needed = true;
        }

        if ($copy_needed) {
            copy_file_preserving_parent($app_id, $source, $target);
            append_manifest_entry($app_id, $next, $new_digest, $relpath);
        } elseif (is_file($target) && sha256_file_checked($app_id, $target) === $new_digest) {
            append_manifest_entry($app_id, $next, $new_digest, $relpath);
        } elseif ($old_digest !== "") {
            append_manifest_entry($app_id, $next, $old_digest, $relpath);
        } else {
            append_manifest_entry($app_id, $next, $new_digest, $relpath);
        }
    }
    rename($next, $state_manifest);
}

function line_matches_patterns($line, $patterns_text) {
    $lower = strtolower($line);
    $patterns = explode("\n", $patterns_text);
    foreach ($patterns as $raw_pattern) {
        $pattern = trim($raw_pattern);
        if ($pattern === "") {
            continue;
        }
        if (strpos($lower, strtolower($pattern)) !== false) {
            return true;
        }
    }
    return false;
}

function unhide_matching_windows($patterns_text) {
    if ($patterns_text === "") {
        return;
    }
    $output = shell_exec("wmctrl -lx 2>/dev/null");
    if ($output === null || $output === false) {
        return;
    }
    $lines = explode("\n", $output);
    foreach ($lines as $line) {
        if (!line_matches_patterns($line, $patterns_text)) {
            continue;
        }
        $parts = explode(" ", trim($line));
        if (count($parts) < 1 || $parts[0] === "") {
            continue;
        }
        run_shell("wmctrl -i -r " . escapeshellarg($parts[0]) . " -b remove,skip_taskbar,skip_pager", "", true);
    }
}

function run_wine_program($user_prefix, $exe, $argv, $patterns_text) {
    $command = shell_env_assignment("WINEPREFIX", $user_prefix) . "wine " . escapeshellarg($exe);
    $i = 1;
    while ($i < count($argv)) {
        $command = $command . " " . escapeshellarg($argv[$i]);
        $i++;
    }
    unhide_matching_windows($patterns_text);
    return run_shell($command, dirname($exe), false);
}

function app_main($argv) {
    $app_id = app_id_from_argv($argv);
    $home = default_home($app_id);
    $opt_root = path_join("/usr/share/pw-wine-delta", $app_id);
    $config = path_join($opt_root, "startwrapper.conf");
    if (!is_file($config)) {
        fail($app_id, "missing startwrapper config: " . $config);
    }
    $program_exe = config_value($config, "program_exe");
    if ($program_exe === "") {
        fail($app_id, "missing program_exe in " . $config);
    }
    $patterns_text = config_values_text($config, "window_pattern");

    $user_prefix = path_join(path_join($home, ".wine-pwdelta"), $app_id);
    $state_dir = path_join($user_prefix, ".pw-delta-state");
    $state_manifest = path_join($state_dir, "managed.tsv");
    $registry_state_manifest = path_join($state_dir, "registry-patches.tsv");

    ensure_wine_prefix($app_id, $user_prefix);
    apply_registry_patches($app_id, $opt_root, $user_prefix, $registry_state_manifest);
    sync_user_prefix($app_id, $opt_root, $user_prefix, $state_manifest);

    $exe = path_join($user_prefix, $program_exe);
    if (!is_file($exe)) {
        fail($app_id, "installed program not found: " . $exe);
    }
    return run_wine_program($user_prefix, $exe, $argv, $patterns_text);
}

exit(app_main($argv));
