#!/bin/php
<?php
try {
    $VERSION = pw_build_meson_version();
    $PW_BUILD_MESON_GENERATOR_PATH = realpath($argv[0] ?? "") ?: "";
    if (isset($argv[1]) && file_exists($argv[1])) {
        parse_args($argv);
        echo "Starting pw-build-meson v" . $VERSION . " for directory " . $argv[1] . "...\r\n";
        chdir($argv[1]);
        echo "\tEntered root directory: " . realpath(".") . "\r\n";
        if(tree_needs_legacy_pwphpbytecode(".")){
            load_dependencies();
        }
        scandir4pw();
        chdir("..");
    } else {
        echo "Usage: pw-build-meson directory [phpslot abiver]\r\n";
        echo "--no-doxygen: skips generation of manpages\r\n";
        exit(1);
    }
}
catch(Exception|Error $ex){
    echo $ex->getMessage();
    echo "\r\n";
    echo $ex->getTraceAsString();
    echo "\r\n";
    exit(1);
}

function pw_build_meson_version(){
    $version = "__PW_BUILD_MESON_VERSION__";
    $placeholder = "__PW_BUILD_MESON_" . "VERSION__";
    if($version !== "" && $version !== $placeholder){
        return($version);
    }

    $env_version = getenv("PW_BUILD_MESON_VERSION");
    if($env_version !== false && $env_version !== ""){
        return($env_version);
    }

    return("unknown");
}

function parse_args($argv){
    global $_DOXYGEN;
    $_DOXYGEN=true;
    if(in_array("--no-doxygen", $argv)){
        $_DOXYGEN=false;
    }
}

function load_dependencies(){
    global $_PHP_SLOT,$_ABI_VER,$_SERVER;
    if(isset($_SERVER["argv"][3])) {
        $_ABI_VER = $_SERVER["argv"][3];
        $_PHP_SLOT = $_SERVER["argv"][2];
        return;
    }

    $files = scandir("/lib64");
    foreach($files as $file){
        if(str_starts_with($file,"libpwphpbytecode-") && str_ends_with($file,".so")){
            $exp = explode("-",$file);
            $exp2 = explode(".",$exp[2]);
            if(isset($_ABI_VER) && $_ABI_VER>$exp2[0]) continue;
            if(isset($_PHP_SLOT) && $_PHP_SLOT>$exp[1]) continue;
            $_ABI_VER=$exp2[0];
            $_PHP_SLOT=$exp[1];
        }
    }
    echo "\tAutomatically detected PHP Slot: ".$_PHP_SLOT." and ABIVER: ".$_ABI_VER."\r\n";
}

function tree_needs_legacy_pwphpbytecode($dir){
    $meson_pw = $dir . "/meson.build.pw";
    if(file_exists($meson_pw) && pw_params_need_legacy_pwphpbytecode(file($meson_pw))){
        return(true);
    }

    $opendir = opendir($dir);
    while($file = readdir($opendir)){
        if($file=="." || $file=="..") continue;
        $path = $dir . "/" . $file;
        if(is_dir($path) && tree_needs_legacy_pwphpbytecode($path)){
            closedir($opendir);
            return(true);
        }
    }
    closedir($opendir);
    return(false);
}


function scandir4pw(){
    $opendir=opendir(".");
    $subdirs=array();
    while($file=readdir($opendir)){
        if($file=="." || $file=="..") continue;
        if(is_dir($file)){
            $subdirs[]=$file;
        }
    }
    closedir($opendir);

    // Ordering...
    if(file_exists("meson.build")){ // if not exists, we dont have ordering informations
        $relevant_lines=array();
        $load = file("meson.build");
        foreach($load as $line){
            $line = trim($line);
            if(str_starts_with($line,"subdir")){
                $relevant_lines[]=$line;
            }
        }

        $subdirs_old=$subdirs;
        $subdirs=array();
        foreach($relevant_lines as $line){
            foreach($subdirs_old as $subdir_o){
                if(str_contains($line,$subdir_o)){
                    if(in_array($subdir_o,$subdirs))continue;
                    $subdirs[]=$subdir_o;
                }
            }
        }
        foreach($subdirs_old as $subdir_o){
            if(!in_array($subdir_o,$subdirs)){
                $subdirs[]=$subdir_o;
            }
        }
    }


    foreach($subdirs as $subdir) {
        echo "\tEntering " . realpath($subdir) . "\r\n";
        chdir($subdir);
        scandir4pw();
        chdir("..");
        echo "\tLeaving " . realpath($subdir) . "\r\n";
    }


    if(file_exists("meson.build.pw")){
        echo "\tProcessing ".realpath("meson.build.pw")."\r\n";
        process_pw("meson.build.pw");
    }
}

function load_pwphpwrapper_suffix_fine(){
    $ret = "_".load_pwphpwrapper_suffix();
    $ret = str_replace(".","_",$ret);
    $ret = str_replace("-","_",$ret);
    return($ret);
}
function load_pwphpwrapper_suffix(){
    global $_PHP_SLOT,$_ABI_VER;
    return($_PHP_SLOT."-".$_ABI_VER);
}
function process_pw($file){
    global $_PHP_SLOT,$_ABI_VER,$_DOXYGEN;
    $params = file($file);
    $mesonfile=pw_build_meson_generated_header($file);

    foreach(scan_for_subs() as $subdir){
        $mesonfile.="subdir('".$subdir."')\n";
    }
    if(pw_params_need_legacy_pwphpbytecode($params)){
        $mesonfile.="pwphpbytecode_dependency = dependency('pwphpbytecode-".$_PHP_SLOT."-".$_ABI_VER."')\n";
        $mesonfile.="pwphpbytecode_dependency_raw = dependency('pwphpbytecode_raw-".$_PHP_SLOT."-".$_ABI_VER."')\n";
    }

    // First Loop -> Generate Dependencies
    $done_embedfile=false;
    $done_embedfilestrip=false;
    $done_cat=false;
    $done_makeheaders=false;
    $done_doxygen=false;
    $done_etcautoupdate=false;
    $done_pccphpext=false;
    $done_echo=false;
    $done_valaphp=false;
    foreach($params as $param){
        $param=trim($param);
        if(empty($param))continue;
        if(str_starts_with($param, "#")){
            //its a comment
            continue;
        }
        $param = explode("=",$param,2);
        $param_key = trim($param[0]);
        if(is_valaphp_rule_key($param_key)){
            if(!$done_valaphp){
                $mesonfile.=valaphp_meson_prelude();
                $done_valaphp=true;
            }
        }
        elseif(trim($param[0])=="lib"){
            if(!$done_cat){
                $mesonfile.="cat = find_program('cat')\n";
                $done_cat=true;
            }
        }
        elseif(trim($param[0])=="fpmext"){
            if(!$done_pccphpext){
                $mesonfile.="phpfpmextbuilder = find_program('pcc_phpextensionbuilder')\n";
                $done_pccphpext=true;
            }
            if(!$done_echo){
                $mesonfile.="echo = find_program('echo')\n";
                $done_echo=true;
            }
        }
        elseif(trim($param[0])=="bin" || trim($param[0])=="bin-jit" || str_starts_with(trim($param[0]), "bin(")|| str_starts_with(trim($param[0]), "bin-jit(")){
            if(!$done_embedfile){
                $mesonfile.="mycompiler = find_program('pcc_embedfile_bytecode_".$_PHP_SLOT."')\n";
                $done_embedfile=true;
            }
        }
        elseif(trim($param[0])=="strip" || str_starts_with(trim($param[0]), "strip(")){
            if(!$done_embedfilestrip){
                $mesonfile.="mycompiler_strip = find_program('pcc_stripfile')\n";
                $done_embedfilestrip=true;
            }
        }
        elseif(str_starts_with(trim($param[0]), "etc(")){
            if(!$done_embedfilestrip){
                $mesonfile.="mycompiler_strip = find_program('pcc_stripfile')\n";
                $done_embedfilestrip=true;
            }
            if (!$done_embedfile) {
                $mesonfile .= "mycompiler = find_program('pcc_embedfile_bytecode_".$_PHP_SLOT."')\n";
                $done_embedfile = true;
            }
            if(!$done_etcautoupdate){
                $mesonfile.="mycompiler_etcstub = find_program('etc-autoupdate_createstub')\n";
                $done_etcautoupdate=true;
            }
        }
        elseif(trim($param[0])=="class" || trim($param[0])=="class-jit"){
            if (!$done_embedfile) {
                $mesonfile .= "mycompiler = find_program('pcc_embedfile_bytecode_".$_PHP_SLOT."')\n";
                $done_embedfile = true;
            }
            if (!$done_makeheaders) {
                $mesonfile .= "makeheaders = find_program('makeheaders')\n";
                $done_makeheaders = true;
            }

            if(!$done_doxygen && $_DOXYGEN){
                // Build Doxyfile
                $mesonfile.="doxyconf_data = configuration_data()\n";
                $mesonfile.="doxyconf_data.set('SOURCECODE',meson.current_source_dir())\n";
                $mesonfile.="doxyconf_data.set('OUTPUTDIR',meson.current_build_dir())\n";
                $mesonfile.= "doxyconfig = configure_file(input:'/usr/share/pw-build-meson/Doxyfile',\n";
                $mesonfile.="output: 'Doxyfile',\n";
                $mesonfile.="configuration: doxyconf_data)\n";
                $mesonfile.="doxygen = find_program('doxygen')\n";
                $mesonfile.="doxygen_output_dir='man'\n";
                $mesonfile.="complete_man = custom_target(\n";
                $mesonfile.="   doxygen_output_dir,\n";
                $mesonfile.="   input: doxyconfig,\n";
                $mesonfile.="   output: doxygen_output_dir,\n";
                $mesonfile.="   build_always_stale: true,\n";
                $mesonfile.="   command: [doxygen,'-q','@INPUT@'],\n";
                $mesonfile.=")\n";
                $done_doxygen=true;
            }
            if(!$done_cat){
                $mesonfile.="cat = find_program('cat')\n";
                $done_cat=true;
            }
        }
    }

    // Second Loop -> Build code
    foreach($params as $param){
        $param=trim($param);
        if(empty($param))continue;
        if(str_starts_with($param, "#")){
            //its a comment
            continue;
        }
        $param = explode("=",$param,2);
        $param_key = trim($param[0]);
        if(is_valaphp_rule_key($param_key)){
            $mesonfile.=add_valaphp_rule($param);
        }
        elseif(trim($param[0])=="lib"){
            $mesonfile.=add_lib($param);
        }
        elseif(trim($param[0])=="fpmext"){
            $mesonfile.=add_phpfpm($param);
        }
        elseif(trim($param[0])=="bin" || trim($param[0])=="bin-jit" || str_starts_with(trim($param[0]), "bin(") || str_starts_with(trim($param[0]), "bin-jit(")){
            $mesonfile.=add_bin($param);
        }
        elseif(trim($param[0])=="strip" || str_starts_with(trim($param[0]), "strip(")){
            $mesonfile.=add_strip($param);
        }
        elseif(trim($param[0])=="class" || trim($param[0])=="class-jit"){
            $mesonfile.=add_class($param);
        }
        elseif(str_starts_with(trim($param[0]), "etc(")){
            $mesonfile.=add_etc($param);
        }
        else{
            echo "pw-build-meson2: "."Unknown param ".$param[0]."\r\n";
            exit(1);
        }
    }


    if($done_doxygen){
        // Use Doxyfile
    }

    $fp = fopen("meson.build","w");
    fwrite($fp,$mesonfile);
    fclose($fp);
    echo "\tmeson.build written!\r\n";
}

function pw_build_meson_generated_header($source_file){
    $marker = "# This file is automatically generated by dev-php/pw-build-meson, DO NOT EDIT! DONT COMMIT INTO SUBVERSION! - sourced by " . $source_file;
    $generator_path = pw_build_meson_generator_path();
    $generator_mtime = pw_build_meson_generator_mtime();

    $txt = "";
    $txt .= $marker . "\n";
    $txt .= "# pw-build-meson-generator-path: " . $generator_path . "\n";
    $txt .= "# pw-build-meson-generator-mtime: " . $generator_mtime . "\n";
    $txt .= "_pw_build_meson_source = meson.current_source_dir() / " . valaphp_meson_quote($source_file) . "\n";
    $txt .= "_pw_build_meson_generated = meson.current_source_dir() / 'meson.build'\n";
    $txt .= "_pw_build_meson_generator_path = " . valaphp_meson_quote($generator_path) . "\n";
    $txt .= "_pw_build_meson_expected_generator_mtime = " . $generator_mtime . "\n";
    $txt .= "if _pw_build_meson_generator_path != '' and _pw_build_meson_expected_generator_mtime > 0 and run_command('test', '-f', _pw_build_meson_generator_path, check: false).returncode() == 0\n";
    $txt .= "\tconfigure_file(input: _pw_build_meson_generator_path, output: 'pw-build-meson.watch', copy: true)\n";
    $txt .= "\t_pw_build_meson_current_generator_mtime = run_command('stat', '-c', '%Y', _pw_build_meson_generator_path, check: true).stdout().strip().to_int()\n";
    $txt .= "\tif _pw_build_meson_current_generator_mtime != _pw_build_meson_expected_generator_mtime\n";
    $txt .= "\t\t_pw_build_meson_marker = run_command('grep', '-F', " . valaphp_meson_quote($marker) . ", _pw_build_meson_generated, check: false)\n";
    $txt .= "\t\tif _pw_build_meson_marker.returncode() == 0\n";
    $txt .= "\t\t\trun_command('rm', '-f', _pw_build_meson_generated, check: true)\n";
    $txt .= "\t\t\terror('pw-build-meson generator mtime changed; removed generated meson.build. Run pw-build-meson before the next Meson configure/compile.')\n";
    $txt .= "\t\telse\n";
    $txt .= "\t\t\terror('pw-build-meson generator mtime changed, but meson.build has no pw-build-meson marker; refusing to remove it.')\n";
    $txt .= "\t\tendif\n";
    $txt .= "\tendif\n";
    $txt .= "endif\n";
    $txt .= "if run_command('test', '-f', _pw_build_meson_source, check: false).returncode() == 0\n";
    $txt .= "\tconfigure_file(input: " . valaphp_meson_quote($source_file) . ", output: 'meson.build.pw.watch', copy: true)\n";
    $txt .= "\t_pw_build_meson_source_mtime = run_command('stat', '-c', '%Y', _pw_build_meson_source, check: true).stdout().strip().to_int()\n";
    $txt .= "\t_pw_build_meson_generated_mtime = run_command('stat', '-c', '%Y', _pw_build_meson_generated, check: true).stdout().strip().to_int()\n";
    $txt .= "\tif _pw_build_meson_source_mtime > _pw_build_meson_generated_mtime\n";
    $txt .= "\t\t_pw_build_meson_marker = run_command('grep', '-F', " . valaphp_meson_quote($marker) . ", _pw_build_meson_generated, check: false)\n";
    $txt .= "\t\tif _pw_build_meson_marker.returncode() == 0\n";
    $txt .= "\t\t\trun_command('rm', '-f', _pw_build_meson_generated, check: true)\n";
    $txt .= "\t\t\terror('meson.build.pw is newer than generated meson.build; removed generated meson.build. Run pw-build-meson before the next Meson configure/compile.')\n";
    $txt .= "\t\telse\n";
    $txt .= "\t\t\terror('meson.build.pw is newer than meson.build, but meson.build has no pw-build-meson marker; refusing to remove it.')\n";
    $txt .= "\t\tendif\n";
    $txt .= "\tendif\n";
    $txt .= "endif\n";
    return($txt);
}

function pw_build_meson_generator_path(){
    global $PW_BUILD_MESON_GENERATOR_PATH;
    $path = $PW_BUILD_MESON_GENERATOR_PATH ?? "";
    if($path === "" || !file_exists($path)){
        return("");
    }
    return($path);
}

function pw_build_meson_generator_mtime(){
    $path = pw_build_meson_generator_path();
    if($path === ""){
        return(0);
    }
    $mtime = filemtime($path);
    if($mtime === false){
        return(0);
    }
    return($mtime);
}

function pw_param_key($param){
    $parts = explode("=", trim($param), 2);
    return(trim($parts[0] ?? ""));
}

function pw_params_need_legacy_pwphpbytecode($params){
    foreach($params as $param){
        $param = trim($param);
        if(empty($param) || str_starts_with($param, "#"))continue;
        $key = pw_param_key($param);
        if(
            $key === "lib" ||
            $key === "fpmext" ||
            $key === "bin" ||
            $key === "bin-jit" ||
            str_starts_with($key, "bin(") ||
            str_starts_with($key, "bin-jit(") ||
            $key === "class" ||
            $key === "class-jit" ||
            str_starts_with($key, "etc(")
        ){
            return(true);
        }
    }
    return(false);
}

function is_valaphp_rule_key($key){
    $base_key = valaphp_rule_base_key($key);
    return(in_array($base_key, array(
        "vlib",
        "vlib-native",
        "vlib-embed",
        "vlib-pwphp",
        "vbin",
        "vbin-native",
        "vbin-embed",
        "vbin-pwphp",
    )));
}

function valaphp_rule_base_key($key){
    $key = trim($key);
    if(str_contains($key, "(")){
        $parts = explode("(", $key, 2);
        $key = trim($parts[0]);
    }
    return($key);
}

function valaphp_rule_install_dir($key){
    $key = trim($key);
    if(!str_contains($key, "(") || !str_contains($key, ")")){
        return("");
    }
    $parts = explode("(", $key, 2);
    $inner = explode(")", $parts[1], 2);
    return(trim($inner[0]));
}

function valaphp_meson_prelude(){
    $txt = "";
    $txt .= "valaphp_php2vala = find_program('php2vala')\n";
    $txt .= "valaphp_pkg = import('pkgconfig')\n";
    $txt .= "valaphp_cc = meson.get_compiler('c')\n";
    $txt .= "valaphp_c_args = valaphp_cc.get_supported_arguments([\n";
    $txt .= "\t'-Wno-discarded-qualifiers',\n";
    $txt .= "\t'-Wno-unused-but-set-variable',\n";
    $txt .= "\t'-Wno-unused-parameter',\n";
    $txt .= "\t'-Wno-unused-variable',\n";
    $txt .= "])\n";
    $txt .= "valaphp_vapi_dir = join_paths(get_option('datadir'), 'vala', 'vapi')\n";
    $txt .= "if not is_variable('project_description')\n";
    $txt .= "\tproject_description = meson.project_name()\n";
    $txt .= "endif\n";
    return($txt);
}

function add_valaphp_rule($param){
    $key = valaphp_rule_base_key($param[0]);
    $rule = parse_valaphp_rule($param);
    if($key === "vlib" || str_starts_with($key, "vlib-")){
        return(add_valaphp_lib($rule));
    }
    if($key === "vbin" || str_starts_with($key, "vbin-")){
        return(add_valaphp_bin($rule));
    }
    echo "pw-build-meson: Unknown valaphp rule ".$key."\r\n";
    exit(1);
}

function parse_valaphp_rule($param){
    if(!isset($param[1])){
        echo "pw-build-meson: Missing right side in param:\r\n";
        print_r($param);
        exit(1);
    }

    $key = valaphp_rule_base_key($param[0]);
    $install_dir = valaphp_rule_install_dir($param[0]);
    $parts = explode("-", $key, 2);
    $kind = $parts[0] ?? "";
    $mode = $parts[1] ?? "both";
    if(!in_array($kind, array("vlib", "vbin")) || !in_array($mode, array("native", "embed", "pwphp", "both"))){
        echo "pw-build-meson: Invalid valaphp rule ".$key."\r\n";
        exit(1);
    }

    $tmp1 = explode("->", $param[1], 2);
    if(count($tmp1) !== 2){
        echo "pw-build-meson: Missing -> in param:\r\n";
        print_r($param);
        exit(1);
    }

    $src = trim($tmp1[0]);
    $dst_and_deps = trim($tmp1[1]);
    $options = valaphp_parse_options($dst_and_deps);
    $tmp2 = explode("(", $dst_and_deps, 2);
    $dst = trim($tmp2[0]);
    if(empty($src) || empty($dst)){
        echo "pw-build-meson: Missing source or destination in param:\r\n";
        print_r($param);
        exit(1);
    }

    $deps = array();
    if(isset($tmp2[1])){
        $tmp3 = explode(")", $tmp2[1], 2);
        foreach(explode(",", $tmp3[0]) as $dep){
            $dep = trim($dep);
            if(!empty($dep)){
                $deps[] = $dep;
            }
        }
    }

    $deps = valaphp_apply_default_dependencies($kind, $mode, $deps);

    return(array(
        "kind" => $kind,
        "mode" => $mode,
        "src" => $src,
        "dst" => $dst,
        "deps" => $deps,
        "optimizer" => $options["optimizer"],
        "install_dir" => $install_dir,
    ));
}

function valaphp_parse_options(&$value){
    $options = array(
        "optimizer" => 4,
    );

    if(!preg_match('/\s*\[([^\]]*)\]\s*$/', $value, $match)){
        return($options);
    }

    $value = trim(substr($value, 0, -strlen($match[0])));
    foreach(explode(",", $match[1]) as $raw_option){
        $raw_option = trim($raw_option);
        if($raw_option === ""){
            continue;
        }

        $parts = explode("=", $raw_option, 2);
        $name = trim($parts[0]);
        $option_value = isset($parts[1]) ? trim($parts[1]) : "";
        if($name !== "opt"){
            echo "pw-build-meson: Unknown valaphp option ".$name."\r\n";
            exit(1);
        }
        if(!preg_match('/^[1-4]$/', $option_value)){
            echo "pw-build-meson: Invalid valaphp optimizer level ".$option_value." (expected opt=1..4)\r\n";
            exit(1);
        }
        $options["optimizer"] = intval($option_value);
    }

    return($options);
}

function valaphp_apply_default_dependencies($kind, $mode, $deps){
    $defaults = array();
    if($kind === "vbin" || $kind === "vlib"){
        $defaults[] = "gee-0.8";
        $defaults[] = "gio-2.0";
        $defaults[] = "valaphp-runtime-core";
        if($mode === "native"){
            $defaults[] = "valaphp-runtime-std";
        } elseif($mode === "both"){
            $defaults[] = "valaphp-runtime-std";
            $defaults[] = "valaphp-runtime-embed";
        } else {
            $defaults[] = "valaphp-runtime-embed";
        }
    }

    if($mode === "pwphp"){
        $defaults[] = "valaphp-runtime-embed-pwphpbytecode";
    }

    foreach($defaults as $dep){
        if(!in_array($dep, $deps)){
            $deps[] = $dep;
        }
    }
    $normalized_deps = array();
    $for_binary = ($kind === "vbin");
    foreach($deps as $dep){
        $normalized_dep = valaphp_dependency_name($dep, $for_binary);
        if(!in_array($normalized_dep, $normalized_deps)){
            $normalized_deps[] = $normalized_dep;
        }
    }
    return($normalized_deps);
}

function valaphp_runtime_arg($mode){
    if($mode === "native"){
        return("--native-runtime");
    }
    if($mode === "both"){
        return("--both-runtimes");
    }
    return("--embed-runtime");
}

function valaphp_optimizer_command_lines($level){
    switch($level){
        case 1:
            return("\t\t'--no-optimize-local-vars',\n");
        case 2:
            return("\t\t'--optimize-local-vars',\n");
        case 3:
            return("\t\t'--optimize-file-func-vars',\n");
        case 4:
            return("\t\t'--optimize-cross-file-func-vars',\n");
    }

    echo "pw-build-meson: Invalid valaphp optimizer level ".$level." (expected 1..4)\r\n";
    exit(1);
}

function valaphp_identifier($value){
    $value = preg_replace('/[^A-Za-z0-9_]/', '_', $value);
    if($value === "" || preg_match('/^[0-9]/', $value)){
        $value = "valaphp_" . $value;
    }
    return($value);
}

function valaphp_meson_quote($value){
    return("'" . str_replace("'", "\\'", $value) . "'");
}

function valaphp_pkg_config_exists($package_name){
    static $cache = array();
    if(isset($cache[$package_name])){
        return($cache[$package_name]);
    }

    $output = array();
    $exit_code = 1;
    exec("pkg-config --exists " . escapeshellarg($package_name), $output, $exit_code);
    $cache[$package_name] = ($exit_code === 0);
    return($cache[$package_name]);
}

function valaphp_runtime_dependency_alias($dep){
    $dep = trim($dep);
    if($dep === "" || str_starts_with($dep, "valaphp-runtime-")){
        return($dep);
    }

    $runtime_dep = "valaphp-runtime-" . $dep;
    if(valaphp_pkg_config_exists($runtime_dep)){
        return($runtime_dep);
    }
    return($dep);
}

function valaphp_dependency_name($dep, $for_binary){
    $dep = trim($dep);
    if($for_binary && str_starts_with($dep, "lib")){
        return("valaphp-" . $dep);
    }
    return(valaphp_runtime_dependency_alias($dep));
}

function valaphp_dependency_expr($dep, $for_binary){
    $dep = trim($dep);
    if($for_binary && preg_match('/^pw[A-Za-z0-9_]*$/', $dep)){
        return("valaphp_cc.find_library(" . valaphp_meson_quote($dep . "-") . " + phpslot + '-' + abiver, required: true)");
    }
    return("dependency(" . valaphp_meson_quote(valaphp_dependency_name($dep, $for_binary)) . ")");
}

function valaphp_dependency_array($deps, $for_binary){
    $exprs = array();
    foreach($deps as $dep){
        $exprs[] = valaphp_dependency_expr($dep, $for_binary);
    }
    return("[" . join(", ", $exprs) . "]");
}

function valaphp_string_array($values, $for_binary = false){
    $exprs = array();
    foreach($values as $value){
        $exprs[] = valaphp_meson_quote(valaphp_dependency_name($value, $for_binary));
    }
    return("[" . join(", ", $exprs) . "]");
}

function add_valaphp_lib($rule){
    $id = valaphp_identifier($rule["dst"]);
    $vala_output = $id . ".vala";
    $stub_output = $rule["dst"] . ".stub.php";
    $deps = valaphp_dependency_array($rule["deps"], false);
    $dep_names = valaphp_string_array($rule["deps"]);
    $runtime_arg = valaphp_runtime_arg($rule["mode"]);
    $optimizer_args = valaphp_optimizer_command_lines($rule["optimizer"]);

    $txt = "";
    $txt .= $id . "_api_name = " . valaphp_meson_quote($rule["dst"]) . "\n";
    $txt .= $id . "_public_vapi_pkg_names = " . $dep_names . "\n";
    $txt .= $id . "_deps = " . $deps . "\n";
    $txt .= $id . "_include_dir = join_paths(get_option('includedir'), " . $id . "_api_name)\n";
    $txt .= $id . "_stub_dir = join_paths(get_option('datadir'), 'valaphp', " . $id . "_api_name)\n";
    $txt .= $id . "_generated_outputs = custom_target(\n";
    $txt .= "\t" . valaphp_meson_quote($rule["dst"] . "-vala") . ",\n";
    $txt .= "\tinput: " . valaphp_meson_quote($rule["src"]) . ",\n";
    $txt .= "\toutput: [" . valaphp_meson_quote($vala_output) . ", " . valaphp_meson_quote($stub_output) . "],\n";
    $txt .= "\tcommand: [\n";
    $txt .= "\t\tvalaphp_php2vala,\n";
    $txt .= "\t\t'--library',\n";
    $txt .= "\t\t'--php-stub-out',\n";
    $txt .= "\t\t'@OUTPUT1@',\n";
    $txt .= "\t\t" . valaphp_meson_quote($runtime_arg) . ",\n";
    $txt .= $optimizer_args;
    $txt .= "\t\t'@INPUT@',\n";
    $txt .= "\t\t'@OUTPUT0@',\n";
    $txt .= "\t],\n";
    $txt .= "\tinstall: true,\n";
    $txt .= "\tinstall_dir: [false, " . $id . "_stub_dir],\n";
    $txt .= ")\n";
    $txt .= $id . "_lib = library(\n";
    $txt .= "\t" . $id . "_api_name,\n";
    $txt .= "\t" . $id . "_generated_outputs,\n";
    $txt .= "\tdependencies: " . $id . "_deps,\n";
    $txt .= "\tc_args: valaphp_c_args,\n";
    $txt .= "\tvala_header: " . $id . "_api_name + '.h',\n";
    $txt .= "\tvala_vapi: " . $id . "_api_name + '.vapi',\n";
    $txt .= "\tinstall: true,\n";
    $txt .= "\tinstall_dir: [true, " . $id . "_include_dir, valaphp_vapi_dir],\n";
    $txt .= ")\n";
    $txt .= "custom_target(\n";
    $txt .= "\t" . $id . "_api_name + '-deps',\n";
    $txt .= "\toutput: " . $id . "_api_name + '.deps',\n";
    $txt .= "\tcommand: [\n";
    $txt .= "\t\t'sh',\n";
    $txt .= "\t\t'-c',\n";
    $txt .= "\t\t'output=\"\$1\"; shift; for dep do echo \"\$dep\"; done > \"\$output\"',\n";
    $txt .= "\t\t'sh',\n";
    $txt .= "\t\t'@OUTPUT@',\n";
    $txt .= "\t] + " . $id . "_public_vapi_pkg_names,\n";
    $txt .= "\tinstall: true,\n";
    $txt .= "\tinstall_dir: valaphp_vapi_dir,\n";
    $txt .= ")\n";
    $txt .= "valaphp_pkg.generate(\n";
    $txt .= "\t" . $id . "_lib,\n";
    $txt .= "\tname: " . $id . "_api_name,\n";
    $txt .= "\tdescription: project_description,\n";
    $txt .= "\tversion: meson.project_version(),\n";
    $txt .= "\trequires: " . $id . "_public_vapi_pkg_names,\n";
    $txt .= "\tsubdirs: " . $id . "_api_name,\n";
    $txt .= ")\n";
    return($txt);
}

function add_valaphp_bin($rule){
    $id = valaphp_identifier($rule["dst"]);
    $vala_output = $id . ".vala";
    $deps = valaphp_dependency_array($rule["deps"], true);
    $runtime_arg = valaphp_runtime_arg($rule["mode"]);
    $optimizer_args = valaphp_optimizer_command_lines($rule["optimizer"]);

    $txt = "";
    $txt .= $id . "_generated_output = custom_target(\n";
    $txt .= "\t" . valaphp_meson_quote($rule["dst"] . "-vala") . ",\n";
    $txt .= "\tinput: " . valaphp_meson_quote($rule["src"]) . ",\n";
    $txt .= "\toutput: " . valaphp_meson_quote($vala_output) . ",\n";
    $txt .= "\tcommand: [\n";
    $txt .= "\t\tvalaphp_php2vala,\n";
    $txt .= "\t\t" . valaphp_meson_quote($runtime_arg) . ",\n";
    $txt .= $optimizer_args;
    $txt .= "\t\t'@INPUT@',\n";
    $txt .= "\t\t'@OUTPUT@',\n";
    $txt .= "\t],\n";
    $txt .= ")\n";
    $txt .= $id . "_bin = executable(\n";
    $txt .= "\t" . valaphp_meson_quote($rule["dst"]) . ",\n";
    $txt .= "\t" . $id . "_generated_output,\n";
    $txt .= "\tdependencies: " . $deps . ",\n";
    $txt .= "\tc_args: valaphp_c_args,\n";
    $txt .= "\tinstall: true,\n";
    if($rule["install_dir"] !== ""){
        $txt .= "\tinstall_dir: " . valaphp_meson_quote($rule["install_dir"]) . ",\n";
    }
    $txt .= ")\n";
    return($txt);
}

function scan_for_subs(){
    $ret=array();
    $opendir=opendir(".");
    while($file=readdir($opendir)){
        if($file=="." || $file=="..") continue;
        if(file_exists($file."/meson.build") || file_exists($file."/meson.build.pw")){
            $ret[]=$file;
        }
    }
    closedir($opendir);
    return($ret);
}
function add_etc($param){
    // Catch filesystem target name of binary
    $tmp1 = explode("->",$param[1]);
    $tmp3 = explode("(",$tmp1[1]);
    $dst=trim($tmp3[0]);
    if(empty($dst)){
        echo "pw-build-meson: "."Missing destination in param:\r\n";
        print_r($param);
        exit(1);
    }

    // Catch etc-config-path
    $tmp1 = explode("(",$param[0]);
    $tmp2 = explode(")",$tmp1[1]);
    $etcfile = $tmp2[0];
    if(empty($etcfile)){
        echo "pw-build-meson: "."Missing etcfile in param:\r\n";
        print_r($param);
        exit(1);
    }

    // Build executable
    $path_to_calling="/usr/libexec/pw-etc-adjustments";
    $meson = add_bin(array("bin",$param[1]),$path_to_calling);

    // Build hook for etc-autoupdate
    $tmpsourcename=$dst.".etc-hook.meson-build".".php";

    $identifier=str_replace(".","_",$dst);
    $identifier=str_replace("-","_",$identifier);
    $meson.=$identifier."_etc_autoupdater_php = custom_target(\n";
    $meson.="\t'".$tmpsourcename."',\n";
    $meson.="\tcommand: [mycompiler_etcstub, '".$dst."', '".$etcfile."', '".$path_to_calling."/".$dst."'],\n";
    $meson.="\tcapture: true,\n";
    $meson.="\toutput: '".$tmpsourcename."',\n";
    $meson.=")\n";

    $meson.="strip_php_result_etc_autoupdate = custom_target(\n";
    $meson.="\t'".$dst."',\n";
    $meson.="\tinput: [".$identifier."_etc_autoupdater_php],\n";
    $meson.="\toutput: ['".$dst.".etc-hook.php'],\n";
    $meson.="\tcommand: [mycompiler_strip,'@INPUT@','@OUTPUT@'],\n";
    $meson.="\tinstall:true,\n";
    $meson.="\tinstall_mode: 'rw-r--r--',\n";
    $meson.="\tinstall_dir: '/etc/etc-autoupdate'\n";
    $meson.=")\n";


    return($meson);
}
function add_bin($param,$installdir=""){
    global $lib_mesonlib;
    $tmp1 = explode("->",$param[1]);
    $tmp3 = explode("(",$tmp1[1]);

    $src=trim($tmp1[0]);
    $dst=trim($tmp3[0]);

    $jit=false;
    if(trim($param[0])=="bin-jit")$jit=true;

    // Catch installdir from param[0]
    if(empty($installdir) && str_contains($param[0],"(") && str_contains($param[0],")")) {
        $tmp4 = explode("(", $param[0]);
        $tmp2 = explode(")", $tmp4[1]);
        $installdir = $tmp2[0];
        if(trim($tmp4[0])=="bin-jit")$jit=true;
    }


    $libs=array();
    $elflibs=array();
    $mesonlibs=array();
    $mesonlibs_h=array();
    if(isset($tmp3[1])){
        $tmp2 = explode(")",$tmp3[1]);
        $libs = explode(",",$tmp2[0]);
    }
    $rule_name = trim($param[0]);
    if(str_contains($rule_name,"(")){
        $rule_name = trim(explode("(",$rule_name)[0]);
    }
    foreach($libs as $lib){
        $lib = trim($lib);
        if(empty($lib)){
            continue;
        }
        $exp = explode(":",$lib);
        $dep_name = trim($exp[0]);
        if(str_starts_with($dep_name,"valaphp")){
            echo "pw-build-meson: Invalid dependency '".$dep_name."' for ".$rule_name." rule '".$dst."': bin/bin-jit use pwphpbytecode and cannot link VALAPHP libraries. Use a vbin/vlib rule for VALAPHP dependencies.\r\n";
            exit(1);
        }
        if(is_array($lib_mesonlib) && in_array($dep_name,$lib_mesonlib)){
            $mesonlibs[]=$dep_name.load_pwphpwrapper_suffix_fine()."_lib";
            $mesonlibs_h[]=$dep_name."_h";
        }
        else{
            $elflibs[]="dependency('".trim($dep_name."-".load_pwphpwrapper_suffix())."')";
        }
    }

    $txt = "";

    $compiler="mycompiler";
    $jit_append="";
    if($jit)$jit_append="'--enable-jit'";

    if($src == "pw-build-meson.php" && $dst == "pw-build-meson"){
        $txt .= "exe_php_src = custom_target(\n";
        $txt .= "\t'pw-build-meson-versioned-php',\n";
        $txt .= "\tinput: files( '" . $src . "' ),\n";
        $txt .= "\toutput: 'pw-build-meson.versioned.php',\n";
        $txt .= "\tcommand: ['sh', '-c', 'sed \"s/__PW_BUILD_MESON_VERSION__/\$1/g\" \"\$2\" > \"\$3\"', 'sh', meson.project_version(), '@INPUT@', '@OUTPUT@'],\n";
        $txt .= ")\n";
    }
    else{
        $txt .= "exe_php_src = files( '" . $src . "' )\n";
    }
    $txt .= "exe_c = ['".$src.".c']\n";
    $txt .= "exe_c_sources = custom_target(\n";
    $txt .= "\texe_c,\n";
    $txt .= "\tinput: [exe_php_src],\n";
    $txt .= "\toutput: [exe_c],\n";
    if (count($libs) > 0) {
        $txt .= "\tcommand: [".$compiler.",'@INPUT@','@OUTPUT@','" . join("','", $libs) . "',".$jit_append."]\n";
    } else {
        $txt .= "\tcommand: [".$compiler.",'@INPUT@','@OUTPUT@',".$jit_append."]\n";
    }
    $txt .= ")\n";
    $txt .= "exe_bin = executable('" . $dst . "',\n";
    $txt .= "\texe_c_sources,\n";
    $txt .= "\tdependencies: [pwphpbytecode_dependency, " . join(", ", $elflibs) . "],\n";
    if (count($mesonlibs) > 0) {
        $txt .= "\tlink_with: [" . join(", ", $mesonlibs) . "],\n";
        $txt .= "\tsources: [" . join(", ", $mesonlibs_h) . "],\n";
    }
    $txt .= "\tinstall:true,\n";
    if (!empty($installdir)) {
        $txt .= "\tinstall_dir: '" . $installdir . "'\n";
    }
    $txt .= ")\n";
    return($txt);
}
function add_strip($param,$installdir="",$exec=true){
    $tmp1 = explode("->",$param[1]);
    $tmp3 = explode("(",$tmp1[1]);

    $src=trim($tmp1[0]);
    $dst=trim($tmp3[0]);

    // Catch installdir from param[0]
    if(empty($installdir) && str_contains($param[0],"(") && str_contains($param[0],")")) {
        $tmp1 = explode("(", $param[0]);
        $tmp2 = explode(")", $tmp1[1]);
        $installdir = $tmp2[0];
        $exec=false;
    }

    $libs=array();
    if(isset($tmp3[1])){
        $tmp2 = explode(")",$tmp3[1]);
        $libs = explode(",",$tmp2[0]);
    }
    if(count($libs)>0){
        echo "pw-build-meson: "."Stripping not allows libs:\r\n";
        print_r($param);
        exit(1);
    }

    $txt="";
    $txt.="strip_php_sources = files( '".$src."' )\n";
    $txt.="strip_php_result = custom_target(\n";
    $txt.="\t'".$dst."',\n";
    $txt.="\tinput: [strip_php_sources],\n";
    $txt.="\toutput: ['".$dst."'],\n";
    $txt.="\tcommand: [mycompiler_strip,'@INPUT@','@OUTPUT@'],\n";
    $txt.="\tinstall:true,\n";
    if($exec){
        $txt.="\tinstall_mode: 'rwxr-xr-x',\n";
    }
    else{
        $txt.="\tinstall_mode: 'rw-r--r--',\n";
    }
    if(!empty($installdir)){
        $txt.="\tinstall_dir: '".$installdir."'\n";
    }
    else{
        $txt.="\tinstall_dir: '/usr/bin'\n";
    }
    $txt.=")\n";
    return($txt);
}

function add_lib($param){
    global $lib_elflibs,$lib_elflibs_raw,$lib_mesonlib,$lib_elflibs_intern,$class_pwphpbytecode_stub_files;
    $tmp1 = explode("->",$param[1]);
    $tmp3 = explode("(",$tmp1[1]);

    $srcs=trim($tmp1[0]);
    $dst=trim($tmp3[0]);
    $dst_withver=trim($tmp3[0]).load_pwphpwrapper_suffix_fine();
    $dst_withverfile=trim($tmp3[0])."-".load_pwphpwrapper_suffix();

    $libs=array();
    $elflibs=array();
    $elflibs_raw=array();
    $mesonlibs=array();
    if(isset($tmp3[1])){
        $tmp2 = explode(")",$tmp3[1]);
        $libs = explode(",",$tmp2[0]);
    }
    foreach($libs as $lib){
        echo "Dependencies not allowed on libs, add dependency directly to class!\r\n";
        exit(1);
    }

    $txt="";
    $srcs = explode(",",$srcs);
    foreach($srcs as $src){
        if(isset($lib_elflibs[$src])){
            foreach($lib_elflibs[$src] as $thislib){
                $elflibs[]=$thislib;
            }
            foreach($lib_elflibs_raw[$src] as $thislib){
                $elflibs_raw[]=$thislib;
            }
        }
        if(isset($lib_elflibs_intern[$src])){
            foreach($lib_elflibs_intern[$src] as $thislib){
                $mesonlibs[]=$thislib;
            }
        }
    }

    // Build library Default version
    $txt.=$dst_withver."_lib = shared_library('".$dst_withverfile."',\n";
    $txt.="\t[\n";
    foreach($srcs as $src){
        $txt.="\t\t".$src."_c_sources,\n";
    }
    $txt.="\t],\n";
    $txt.="\tdependencies: [pwphpbytecode_dependency, ".join(", ",$elflibs)."],\n";
    if(count($mesonlibs)>0){
        $txt.="\tlink_with: [".join(", ",$mesonlibs)."],\n";
    }
    $txt.="\tinstall:true,\n";
    $txt.=")\n";


    // Build header file
    $txt.="parts_of_global_header_file = [";
    foreach($srcs as $src){
        $txt.=$src."_h, ";
    }
    $txt.="]\n";
    $txt.=$dst."_h = custom_target(\n";
    $txt.="\t'".$dst.".h',\n";
    $txt.="\tcommand: [cat, '@INPUT@'],\n";
    $txt.="\tcapture: true,\n";
    $txt.="\tinput: parts_of_global_header_file,\n";
    $txt.="\toutput: '".$dst.".h',\n";
    $txt.="\tinstall_dir: get_option('includedir')+'/pwphp_".load_pwphpwrapper_suffix()."',\n";
    $txt.="\tinstall: true,\n";
    $txt.=")\n";

    // Build pkg-config
    $txt.="pkg = import('pkgconfig')\n";
    $txt.="pkg.generate(".$dst_withver."_lib, subdirs: 'pwphp_".load_pwphpwrapper_suffix()."')\n";

    foreach($srcs as $src){
        if(isset($class_pwphpbytecode_stub_files[$src])){
            $stub_file = $class_pwphpbytecode_stub_files[$src];
            rewrite_pwphpbytecode_stub_package($stub_file, $dst_withverfile);
            $txt .= "install_data(\n";
            $txt .= "\t'" . $stub_file . "',\n";
            $txt .= "\tinstall_dir: get_option('datadir')+'/pwphpbytecode-stubs/" . $dst_withverfile . "'\n";
            $txt .= ")\n";
        }
    }

    if(true) { //TODO: should be special activatable - not always true (also non-raw should be special activatable!)
        // Build library RAW (for CLI/FPM) Version
        $dst_withver_raw=trim($tmp3[0])."_raw".load_pwphpwrapper_suffix_fine();
        $dst_withverfile_raw=trim($tmp3[0])."_raw-".load_pwphpwrapper_suffix();

        $txt .= $dst_withver_raw . "_lib = shared_library('" . $dst_withverfile_raw . "',\n";
        $txt .= "\t[\n";
        foreach ($srcs as $src) {
            $txt .= "\t\t" . $src . "_c_sources,\n";
        }
        $txt .= "\t],\n";
        $txt .= "\tdependencies: [pwphpbytecode_dependency_raw, " . join(", ", $elflibs_raw) . "],\n";
        if (count($mesonlibs) > 0) {
            $txt .= "\tlink_with: [" . join(", ", $mesonlibs) . "],\n";
        }
        $txt .= "\tinstall:true,\n";
        $txt .= ")\n";

        // Build pkg-config
        $txt.="pkg.generate(".$dst_withver_raw."_lib, subdirs: 'pwphp_".load_pwphpwrapper_suffix()."')\n";
    }

    // add for internal reference
    if(!is_array($lib_mesonlib)){
        $lib_mesonlib=array();
    }
    $lib_mesonlib[]=$dst;
    return($txt);
}

function add_class($param){
    global $lib_elflibs,$lib_elflibs_raw,$lib_elflibs_intern,$lib_mesonlib,$_DOXYGEN,$class_pwphpbytecode_stub_files;
    $tmp1 = explode("->",$param[1]);
    $tmp3 = explode("(",$tmp1[1]);

    $src=trim($tmp1[0]);
    $dst=trim($tmp3[0]);

    $jit=false;
    if(trim($param[0])=="class-jit")$jit=true;

    $libs=array();
    $mesonlibs_h=array();
    if(isset($tmp3[1])){
        $tmp2 = explode(")",$tmp3[1]);
        $libs = explode(",",$tmp2[0]);
    }
    $pcclibs=array();
    $headerfiles_otherclasses=array();
    foreach($libs as $lib){


        $exp = explode(":",$lib);
        if(isset($exp[1]) && !empty($exp[1])) {
            if(!isset($lib_elflibs)){
                $lib_elflibs[$dst]=array();
            }
            if(is_array($lib_mesonlib) && in_array($exp[0],$lib_mesonlib)){
                $lib_elflibs_intern[$dst][]=$exp[0].load_pwphpwrapper_suffix_fine()."_lib";
                $mesonlibs_h[]=$exp[0]."_h";
            }
            else{
                $lib_elflibs[$dst][]="dependency('".trim($exp[0]."-".load_pwphpwrapper_suffix())."')";
                $lib_elflibs_raw[$dst][]="dependency('".trim($exp[0]."_raw-".load_pwphpwrapper_suffix())."')";
            }
            $pcclibs[]=$lib;
        }
        else{
            $pcclibs[]=$exp[0].":".$exp[0];
            $headerfiles_otherclasses[]=$exp[0]."_h";
        }
    }

    $txt="";

    $jit_append="";
    if($jit)$jit_append="'--enable-jit'";
    $stub_file = write_pwphpbytecode_stub($src, $dst);
    if(!empty($stub_file)){
        if(!isset($class_pwphpbytecode_stub_files) || !is_array($class_pwphpbytecode_stub_files)){
            $class_pwphpbytecode_stub_files=array();
        }
        $class_pwphpbytecode_stub_files[$dst] = $stub_file;
    }


    // Build .c file
    $txt .= $dst."_php_src = files( '" . $src . "' )\n";
    $txt .= $dst."_c = ['".$dst.".c']\n";
    $txt.=$dst."_c_sources = custom_target(\n";
    $txt.="\t".$dst."_c,\n";
    $txt.="\tinput: [".$dst."_php_src],\n";
    $txt.="\toutput: [".$dst."_c],\n";
    if(count($libs)>0){
        $txt.="\tcommand: [mycompiler,'".$dst."','@INPUT@','@OUTPUT@','".join("','",$pcclibs)."','--is-lib',".$jit_append."],\n";
    }
    else{
        $txt.="\tcommand: [mycompiler,'".$dst."','@INPUT@','@OUTPUT@','--is-lib',".$jit_append."],\n";
    }
    if(count($headerfiles_otherclasses)>0){
        $txt.="\tdepends: [".join(", ",$headerfiles_otherclasses)."],\n";
    }
    if(count($mesonlibs_h)>0){
        $txt.="\tdepends: [".join(", ",$mesonlibs_h)."],\n";
    }
    $txt.=")\n";

    // Build .h file
    $txt.=$dst."_h = custom_target(\n";
    $txt.="\t'".$dst.".h',\n";
    $txt.="\tinput: [".$dst."_c_sources],\n";
    $txt.="\toutput: '".$dst.".h',\n";
    $txt.="\tcommand: [makeheaders,'@INPUT@']\n";
    $txt.=")\n";

    if($_DOXYGEN) {
        // Install Man-Page
        $txt .= "custom_target('manpage_" . $dst . "',\n";
        $txt .= "\tcapture: true,\n";
        $txt .= "\toutput: '" . $dst . "." . load_pwphpwrapper_suffix() . ".3',\n";
        $txt .= "\tdepends: [ complete_man ],\n";
        $txt .= "\tcommand: [ cat, meson.current_build_dir()+'/man/man3/" . $dst . ".3' ],\n";
        $txt .= "\tinstall_dir: get_option('mandir')+'/man3',\n";
        $txt .= "\tinstall: true,\n";
        $txt .= "\tbuild_always_stale: true,\n";
        $txt .= ")\n";
    }

    return($txt);
}

function write_pwphpbytecode_stub($src, $class_name){
    if(!file_exists($src)){
        return("");
    }

    $source = file_get_contents($src);
    if($source === false){
        return("");
    }

    $methods = extract_phpbytecode_stub_methods($source, $class_name);
    if(count($methods) === 0){
        return("");
    }

    $stub_file = $class_name . "." . load_pwphpwrapper_suffix() . ".pwphpbytecode.stub.php";
    $stub = "<?php\n";
    $stub .= "/*\n";
    $stub .= " * Generated by pw-build-meson. Declarative pwphpbytecode stub only.\n";
    $stub .= " * Source: " . $src . "\n";
    $stub .= " */\n\n";
    $stub .= "/**\n";
    $stub .= " * @pwphpbytecode-loader php_load_class_" . $class_name . "\n";
    $stub .= " * @pwphpbytecode-package @PWPHPBYTECODE_PACKAGE@\n";
    $stub .= " */\n";
    $stub .= "class " . $class_name . " {\n";
    foreach($methods as $method){
        $stub .= format_php_stub_doc_comment($method["doc"] ?? "", $method["name"]);
        $stub .= "    public static function " . $method["name"] . "(" . $method["params"] . "): " . $method["return"] . " {}\n";
    }
    $stub .= "}\n";

    file_put_contents($stub_file, $stub);
    return($stub_file);
}

function rewrite_pwphpbytecode_stub_package($stub_file, $package_name){
    if(!file_exists($stub_file)){
        return;
    }
    $stub = file_get_contents($stub_file);
    if($stub === false){
        return;
    }
    $stub = str_replace("@PWPHPBYTECODE_PACKAGE@", $package_name, $stub);
    file_put_contents($stub_file, $stub);
}

function extract_phpbytecode_stub_methods($source, $class_name){
    $tokens = token_get_all($source);
    $methods = array();
    $in_target_class = false;
    $class_depth = 0;
    $brace_depth = 0;
    $pending_class = false;
    $pending_static = false;
    $pending_visibility = "";
    $pending_doc_comment = "";

    for($i = 0; $i < count($tokens); $i++){
        $token = $tokens[$i];
        $id = is_array($token) ? $token[0] : null;
        $text = is_array($token) ? $token[1] : $token;

        if($text === "{"){
            $brace_depth++;
            if($pending_class){
                $in_target_class = true;
                $class_depth = $brace_depth;
                $pending_class = false;
            }
            continue;
        }
        if($text === "}"){
            if($in_target_class && $brace_depth === $class_depth){
                $in_target_class = false;
                $class_depth = 0;
            }
            $brace_depth--;
            if($brace_depth < 0)$brace_depth = 0;
            continue;
        }

        if($id === T_CLASS){
            $name = next_non_whitespace_token_text($tokens, $i + 1);
            if($name === $class_name){
                $pending_class = true;
            }
            continue;
        }

        if(!$in_target_class){
            continue;
        }

        if($id === T_DOC_COMMENT){
            $pending_doc_comment = $text;
            continue;
        }
        if($id === T_PUBLIC || $id === T_PROTECTED || $id === T_PRIVATE){
            $pending_visibility = token_name($id);
            continue;
        }
        if($id === T_STATIC){
            $pending_static = true;
            continue;
        }
        if($id === T_FUNCTION){
            $method_name = next_non_whitespace_token_text($tokens, $i + 1);
            if($pending_static && ($pending_visibility === "" || $pending_visibility === "T_PUBLIC") && is_identifier_text($method_name)){
                $params = extract_parenthesized_text_after_function_name($tokens, $i + 1);
                $return = extract_return_type_after_function_signature($tokens, $i + 1);
                $methods[] = array(
                    "name" => $method_name,
                    "params" => normalize_php_stub_params($params),
                    "return" => normalize_php_stub_return($return),
                    "doc" => $pending_doc_comment,
                );
            }
            $pending_static = false;
            $pending_visibility = "";
            $pending_doc_comment = "";
            continue;
        }
    }

    if(count($methods) === 0){
        $methods = extract_phpbytecode_stub_methods_regex($source);
    }

    return($methods);
}

function extract_phpbytecode_stub_methods_regex($source){
    $methods = array();
    if(!preg_match_all('/(?:public\s+static|static\s+public|static)\s+function\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(([^)]*)\)\s*(?::\s*([^{;]+))?/m', $source, $matches, PREG_SET_ORDER)){
        return($methods);
    }
    foreach($matches as $match){
        $methods[] = array(
            "name" => $match[1],
            "params" => normalize_php_stub_params($match[2] ?? ""),
            "return" => normalize_php_stub_return($match[3] ?? ""),
            "doc" => "",
        );
    }
    return($methods);
}

function format_php_stub_doc_comment($doc_comment, $method_name){
    $doc_comment = trim($doc_comment);
    if($doc_comment === ""){
        return("    /**\n     * pwphpbytecode stub for " . $method_name . "().\n     */\n");
    }

    $lines = preg_split('/\R/', $doc_comment);
    $txt = "";
    foreach($lines as $line){
        $txt .= "    " . trim($line) . "\n";
    }
    return($txt);
}

function next_non_whitespace_token_text($tokens, $start){
    for($i = $start; $i < count($tokens); $i++){
        $token = $tokens[$i];
        if(is_array($token) && ($token[0] === T_WHITESPACE || $token[0] === T_COMMENT || $token[0] === T_DOC_COMMENT)){
            continue;
        }
        return(is_array($token) ? $token[1] : $token);
    }
    return("");
}

function extract_parenthesized_text_after_function_name($tokens, $start){
    $seen_name = false;
    $depth = 0;
    $text = "";
    for($i = $start; $i < count($tokens); $i++){
        $token = $tokens[$i];
        $part = is_array($token) ? $token[1] : $token;
        if(!$seen_name){
            if(is_array($token) && $token[0] === T_STRING){
                $seen_name = true;
            }
            continue;
        }
        if($part === "("){
            $depth++;
            if($depth === 1)continue;
        }
        if($part === ")"){
            $depth--;
            if($depth === 0)return($text);
        }
        if($depth > 0){
            $text .= $part;
        }
    }
    return("");
}

function extract_return_type_after_function_signature($tokens, $start){
    $seen_name = false;
    $depth = 0;
    $seen_signature_end = false;
    $return = "";
    for($i = $start; $i < count($tokens); $i++){
        $token = $tokens[$i];
        $part = is_array($token) ? $token[1] : $token;
        if(!$seen_name){
            if(is_array($token) && $token[0] === T_STRING){
                $seen_name = true;
            }
            continue;
        }
        if(!$seen_signature_end){
            if($part === "(")$depth++;
            elseif($part === ")"){
                $depth--;
                if($depth === 0)$seen_signature_end = true;
            }
            continue;
        }
        if(is_array($token) && $token[0] === T_WHITESPACE){
            continue;
        }
        if($part === ":"){
            continue;
        }
        if($part === "{" || $part === ";"){
            break;
        }
        $return .= $part;
    }
    return(trim($return));
}

function normalize_php_stub_params($params){
    return(trim(preg_replace('/\s+/', ' ', $params)));
}

function normalize_php_stub_return($return){
    $return = trim($return);
    if($return === ""){
        return("mixed");
    }
    if($return === "self" || $return === "static" || $return === "parent"){
        return("mixed");
    }
    return($return);
}

function is_identifier_text($text){
    return(is_string($text) && preg_match('/^[A-Za-z_][A-Za-z0-9_]*$/', $text));
}

function add_phpfpm($param){
    global $_PHP_SLOT;
    $tmp1 = explode("->",$param[1]);
    $tmp2 = explode(":",$tmp1[0]);

    $ext_name=trim($tmp1[1]);
    $ext_lib=trim($tmp2[0]);
    $ext_class=trim($tmp2[1]);

    $txt ="c_source_".$ext_name."_cfile = ['".$ext_name."c_source_phpfpmext.c']\n";
    $txt.="c_source_".$ext_name." = custom_target(\n";
    $txt.="\tc_source_".$ext_name."_cfile,\n";
    $txt.="\toutput: c_source_".$ext_name."_cfile,\n";
    $txt.="\tcommand: [phpfpmextbuilder, php_api_version, abi_implement_version, '".$ext_name."', meson.project_version(), ".$ext_class."_h, '".$ext_class."', '@OUTPUT@' ]\n";
    $txt.=")\n";

    # Find PHP-Config
    $txt.="phpconfig = find_program('/usr/lib64/php' + php_api_version + '/bin/php-config')\n";


    # Find PHP includes
    $txt.="phpconfig_includes_run = run_command(phpconfig, '--includes', check: true)\n";
    $txt.="phpconfig_includes = phpconfig_includes_run.stdout().strip().replace('-I','').split()\n";
    $txt.="php_dep_inc = include_directories([phpconfig_includes])\n";

    # Find PHP extension dir
    $txt.="phpconfig_extdir_run = run_command(phpconfig, '--extension-dir', check: true)\n";
    $txt.="phpconfig_extdir = phpconfig_extdir_run.stdout().strip()\n";

    # Plain library without linking (for usage in cli or fpm)
    $txt.="php_dependency_only_incs = declare_dependency(\n";
    $txt.="\tinclude_directories: [php_dep_inc,'..'],\n";
    $txt.=")\n";
    $txt.="phpextension_".$ext_name." = shared_library('phpfpm_".$ext_name."',\n";
    $txt.="\t[c_source_".$ext_name."],\n";
    $txt.="\tdependencies: [php_dependency_only_incs],\n";
    $txt.="\tinstall: true,\n";
    $txt.="\tinstall_dir: phpconfig_extdir,\n";
    $txt.="\tlink_args: [ '-Wl,--unresolved-symbols=ignore-all'],\n";
    $txt.="\tlink_with: [" . $ext_lib. "_raw".load_pwphpwrapper_suffix_fine()."_lib],\n";
    $txt.=")\n";

    # crate extension php.ini
    $txt.="c_source_".$ext_name."_ini = ['phpfpmext_".$ext_name.".ini']\n";
    $txt.="c_source_".$ext_name." = custom_target(\n";
    $txt.="\tc_source_".$ext_name."_ini,\n";
    $txt.="\toutput: c_source_".$ext_name."_ini,\n";
    $txt.="\tcommand: [echo,  'extension=libphpfpm_".$ext_name.".so'],\n";
    $txt.="\tinstall: true,\n";
    $txt.="\tcapture: true,\n";
    $txt.="\tinstall_dir: '/etc/php/fpm-php".$_PHP_SLOT."/ext-active',\n";
    $txt.=")\n";
    return($txt);
}
