action = $action;
        $this->params = $params;
        $this->level = $level;
        $this->handler = $handler;
        $this->resp_type = $resp_type;
        $this->hint = $hint;
        $this->rh = &$rh; // assign be reference
    }
    function dump() : string {
        return "" . $this->action . "(" . join(", ", $this->params) . ") " . $this->hint . " [" . $this->level . "]";
    }
}
// Request Handler class
class ReqHandler
{
    private array $mapping; // action-handler mapping
    private array $request; // reference to $_REQUEST
    private array $files; // reference to $_FILES
    private string $level; // max privilege level
    // Constructor
    function __construct()
    {
        // create reference bindings
        $this->request = &$_REQUEST;
        $this->files = &$_FILES;
        $this->mapping = [PRIVILEGE_NONE => [], PRIVILEGE_PLAYER => [], PRIVILEGE_CREATOR => [], PRIVILEGE_QUIZMASTER => []]; // create assignment categories
        $this->level = PRIVILEGE_NONE;
    }
    // Add new handler
    function add(string|array $actions, array $params, string $level, string $handler, string $resp_type, string $hint): bool
    {
        // convert single actions to array
        $actions = is_string($actions) ? [$actions] : $actions;
        // cannot assign more than one handler to the same action
        foreach ($actions as $action) {
            if (in_array($action, $this->mapping[$level])) {
                return false;
            }
            // create the assignment object
            $assignment = new ReqHandlerAssignment($action, $params, $level, $handler, $resp_type, $hint, $this);
            $this->mapping[$level][$action] = $assignment;
        }
        return true;
    }
    const ACTION_KEY = "action";
    // Process possible pending request. Also check access privilege.
    function process(string $level = null, string $action_key = self::ACTION_KEY) : array {
        // get action
        $action = $this->request[$action_key] ?? "";
        // don't process empty actions
        if (trim($action) === "") {
            return ["", false];
        }
        // if privilege level is specified, then store
        $this->level = $level ?? $this->level;
        $resp = "";
        $success = false;
        // look up action
        $categories = array_keys($this->mapping);
        $highest_category_index = array_search($this->level, $categories);
        for ($i = 0; ($i < count($categories)) && ($i <= $highest_category_index); $i++) {
            // get category
            $category = &$this->mapping[$categories[$i]];
            // find the command level category that includes this action
            if (array_key_exists($action, $category)) {
                // get assignment
                $assignment = &$category[$action];
                // check that all required parameters were passed
                $all_params_passed = true;
                foreach ($assignment->params as $param) {
                    $all_params_passed &= array_key_exists($param, $this->request);
                }
                // if params were provided, then invoke the callback
                if ($all_params_passed) {
                    $handler = $assignment->handler;
                    $raw_resp = $handler($this, $this->request);
                    // convert response to preset response type
                    if ($assignment->resp_type !== RESP_NONE) { // if command returns with anything at all
                        if ($assignment->resp_type === RESP_JSON) { // JSON
                            $resp = json_encode($raw_resp);
                        } else if ($assignment->resp_type === RESP_PLAIN) { // PLAIN
                            $resp = $raw_resp;
                        }
                    }
                    // processing successful
                    $success = true;
                    break; // break the loop
                }
            }
        }
        return [$resp, $success];
    }
    // Set access privilege level
    function set_privilege_level(string $level) {
        $this->level = $level;
    }
    // Dump commands
    function dump_actions() : string {
        $dump = "";
        foreach ($this->mapping as $level => $actions) {
            foreach ($actions as $action) {
                $dump .= $action->dump() . "
";
            }
        }
        return $dump;
    }
}