<?php

$files = [];
$classes = scandir("./godot-master/doc/classes");
foreach ($classes as $filename) {
    $files[] = sprintf("./godot-master/doc/classes/%s", $filename);
}

// Search modules
$modules = scandir("./godot-master/modules");

foreach ($modules as $module) {
    //$files[] = sprintf("./classes/%s", $filename);
    $modulePath = sprintf("./godot-master/modules/%s", $module);
    if (substr($module, 0, 1) == ".") continue;

    if (is_dir($modulePath)) {
        $modulePath = sprintf("%s/doc_classes", $modulePath);
        if (is_dir($modulePath)) {
            $classes = scandir($modulePath);
            foreach ($classes as $filename) {
                $files[] = sprintf("%s/%s", $modulePath, $filename);
            }
        }
    }
}

$target = "./classesGd/%s.gd";
$moduleTarget = "./classesGd/modules/%s.gd";

$addDocumentation = function($data, $desc, $brief = null, $tutorials = null, $deprecated = false, $experimental = false) {
    if (!$brief && !$desc && !$tutorials && !$deprecated && !$experimental) {
        return $data;
    }

    $addSpace = false;
    if ($brief) {
        $addSpace = true;
        $lines = explode("\n", $brief);
        foreach ($lines as $line) {
            $line = trim($line);
            if ($line) {
                $data .= "## $line\n";
            }
        }
    }
    if ($desc) {
        if ($addSpace) {
            $data .= "##\n";
        }
        $addSpace = true;
        $lines = explode("\n", $desc);
        foreach ($lines as $line) {
            $line = trim($line);
            if ($line) {
                $data .= "## $line\n";
            }
        }
    }
    if ($tutorials) {
        if ($addSpace) {
            $data .= "##\n";
        }
        foreach ($tutorials as $tutorial) {
            $name = $tutorial[0];
            if ($name) {
                $name = "($name)";
            }
            $data .= sprintf("## @tutorial%s: %s\n", $name, $tutorial[1]);
        }
    }
    if ($deprecated == "true") {
        $data .= "## @deprecated\n";
    }
    if ($experimental == "true") {
        $data .= "## @experimental\n";
    }

    return $data;
};

$formatType = function($type) {
    if (substr($type, -2) == "[]") {
        $type = substr($type, 0, strlen($type) - 2);
        return sprintf("Array[%s]", $type);
    }

    return $type;
};

$parseParams = function($value) {
    $params = [];
    $parsed = [];
    $list = $value['param'] ?? [];
    if (!is_array($list)) {
        $list = [$list];
    }

    foreach ($list as $param) {
        $param = (array) $param;
        $p_att = $param['@attributes'];
        $p_name = $p_att['name'];
        if ($p_name == "var") $p_name = "variable";
        $parsed[$p_att['index']] = [$p_name, $p_att['type'], $p_att['default'] ?? null];
    }
    foreach ($parsed as $param) {
        $paramType = $param[1];
        if (str_ends_with($paramType, '[]')) {
            $paramType = substr($paramType, 0, strlen($paramType) - 2);
            $paramType = "Array[$paramType]";
        }

        $p = sprintf("%s: %s", $param[0], $paramType);
        if (($param[2] ?? null) !== null) {
            $p = sprintf("%s = %s", $p, $param[2]);
        }
        $params[] = $p;
    }

    return $params;
};

foreach ($files as $filepath) {
    $paths = explode("/", $filepath);
    $filename = $paths[count($paths) - 1];

    if ($filename == "." || $filename == "..") continue;
    if (substr($filename, strlen($filename) - 4) != ".xml") continue;

    $data = "";

    $content = file_get_contents($filepath);

    $xml = (array) simplexml_load_string($content);

    $att = (array) $xml['@attributes'];
    $class_name = str_replace("@", "_", $att['name'] ?? substr($filename, 0, strlen($filename) - 4));

    if ($att['inherits'] ?? null) {
        $data .= sprintf("extends %s\n", $att['inherits']);
    }

    $data .= sprintf("class_name %s\n\n", $class_name);

    /** Description */
    $baseTutorialUrl = 'https://docs.godotengine.org/en/stable';
    $desc = (array) ($xml['brief_description'] ?? []);
    $brief = $desc['0'] ?? "";

    $desc = (array) ($xml['description'] ?? []);
    $description = $desc['0'] ?? "";

    $links = (array) ($xml['tutorials'] ?? []);
    $tutorials = [];
    foreach ($xml['tutorials'] as $link) {
        $link = (array) $link;
        $p_att = $link['@attributes'];
        $value = str_replace('$DOCS_URL', $baseTutorialUrl, $link[0]);

        $tutorials[] = [$p_att['title'] ?? null, $value];
    }

    $data = $addDocumentation($data, $description, $brief, $tutorials, $att['is_deprecated'] ?? false, $att['is_experimental'] ?? false);
    $data .= "\n\n";

    /** Signals */
    foreach ($xml['signals'] ?? [] as $value) {
        $value = (array) $value;
        $att = (array) $value['@attributes'];

        $params = $parseParams($value);
        $desc = (array) ($value['description'] ?? []);
        $data = $addDocumentation($data, $desc['0'] ?? '', null, null, $att['is_deprecated'] ?? false, $att['is_experimental'] ?? false);

        $paramsStr = implode(', ', $params);
        if ($paramsStr) {
            $paramsStr = sprintf("(%s)", $paramsStr);
        }

        $data .= sprintf("signal %s%s\n", $att['name'], $paramsStr);
    }

    $enums = [];

    /** Constants */
    foreach ($xml['constants'] ?? [] as $value) {
        $value = (array) $value;
        $att = (array) $value['@attributes'];
        $data = $addDocumentation($data, $value['0'] ?? null, null, null, $att['is_deprecated'] ?? false, $att['is_experimental'] ?? false);
        $enum = $att['enum'] ?? null;
        if ($enum) {
            $enums[$enum][] = sprintf("%s = %s,\n", $att['name'], $att['value']);
        } else {
            $data .= sprintf("const %s = %s;\n\n", $att['name'], $att['value']);
        }
    }
    $data .= "\n";

    foreach ($enums as $key => $values) {
        $data .= "#enum $key\n";
        $data .= "enum {\n";
        foreach ($values as $value) {
            $data .= "    $value";
        }
        $data .= "}\n";
    }

    // enums can be accesses both ways as named & unnamed
    foreach ($enums as $key => $values) {
        if (str_contains($key, '.')) {
            continue;
        }
        $data .= "#enum $key\n";
        $data .= "enum $key {\n";
        foreach ($values as $value) {
            $data .= "    $value";
        }
        $data .= "}\n";
    }

    /** Variables */
    $getSetMethods = "";
    // TODO should this be incorporated into hints?
    if ($filename != 'ProjectSettings.xml') {
        foreach ($xml['members'] ?? [] as $value) {
            $value = (array) $value;
            $att = (array) $value['@attributes'];
            $data = $addDocumentation($data, $value['0'] ?? null, null, null, $att['is_deprecated'] ?? false, $att['is_experimental'] ?? false);
            $data .= sprintf("var %s: %s", $att['name'], $formatType($att['type']));

            $getter = $att['getter'] ?? null ? sprintf("get = %s", $att['getter']) : null;
            $setter = $att['setter'] ?? null ? sprintf("set = %s", $att['setter']) : null;
            $getSet = [];
            if ($getter) {
                $getSet[] = $getter;
                $getSetMethods .= sprintf("func %s() -> %s:\n\treturn %s\n\n", $att['getter'], $formatType($att['type']), $att['name']);
            }
            if ($setter) {
                $getSet[] = $setter;
                $getSetMethods .= sprintf("func %s(value: %s) -> void:\n\t%s = value\n\n", $att['setter'], $formatType($att['type']), $att['name']);
            }
            if ($getSet) {
                $data .= ":\n\t";
                $data .= implode(', ', $getSet);
            }
            $data .= "\n\n";
        }

        $data .= "\n";
    }

    /** Constructor methods */
    foreach ($xml['constructors'] ?? [] as $value) {
        $value = (array) $value;
        $att = (array) $value['@attributes'];
        $ret = (array) $value['return'];

        $params = $parseParams($value);
        $desc = (array) ($value['description'] ?? []);
        $data = $addDocumentation($data, $desc['0'] ?? '', null, null, $att['is_deprecated'] ?? false, $att['is_experimental'] ?? false);

        $data .= sprintf("func %s(%s) -> %s:\n", $att['name'], implode(', ', $params), $formatType($ret['@attributes']['type']));
        $data .= sprintf("\tpass;\n\n");
    }
    $data .= "\n";

    /** Methods */
    foreach ($xml['methods'] ?? [] as $value) {
        $value = (array) $value;
        $att = (array) $value['@attributes'];
        $ret = (array) ($value['return'] ?? ['@attributes' => [ 'type' => 'void' ]]);

        $quali = explode(' ', $att['qualifiers'] ?? '');
        // TODO Delete this when const func is implemented .. attention there is also "virtual" .. e.g. _init
        $allowed = ["static", "vararg"];
        $quali = array_filter($quali, fn($qualiItem) => in_array($qualiItem, $allowed));
        $quali = implode(' ', $quali);

        if ($quali) {
            $quali .= " ";
        }

        $params = $parseParams($value);
        $desc = (array) ($value['description'] ?? []);
        $data = $addDocumentation($data, $desc['0'] ?? '', null, null, $att['is_deprecated'] ?? false, $att['is_experimental'] ?? false);

        $data .= sprintf("%sfunc %s(%s) -> %s:\n", $quali, $att['name'], implode(', ', $params), $formatType($ret['@attributes']['type']));
        $data .= sprintf("\tpass;\n\n");
    }
    $data .= "\n";
    $data .= $getSetMethods;

    file_put_contents(sprintf($target, $class_name), $data);

}
