/*
 Copyright (C) 2023 Erik Ogenvik

 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <boost/algorithm/string/replace.hpp>
#include "pythonbase/Python_API.h"
#include "rules/ai/python/CyPy_Ai.h"
#include "rules/python/CyPy_Rules_impl.h"
#include "rules/python/CyPy_Physics.h"
#include "rules/entityfilter/python/CyPy_EntityFilter_impl.h"
#include "rules/entityfilter/Filter_impl.h"
#include "rules/entityfilter/ParserDefinitions_impl.h"
#include "rules/python/CyPy_Atlas.h"
#include "rules/python/CyPy_Common.h"
#include "rules/simulation/python/CyPy_Server.h"
#include "rules/simulation/LocatedEntity.h"
#include "rules/ai/MemEntity.h"
#include "rules/simulation/python/CyPy_LocatedEntity.h"
#include <spdlog/sinks/stdout_sinks.h>

template
struct EntityFilter::parser::query_parser<std::string::const_iterator, LocatedEntity>;
template
struct EntityFilter::parser::query_parser<std::string::const_iterator, MemEntity>;

/**
 * A simple tool that will load all of our bindings and then at request try to generate suitable stubs based on the meta data from the bindings.
 * This is meant to be used by the CMakeFile present in "docs", which exposes a custom target that regenerates the Python doc stubs.
 *
 * Note that the generation is incomplete. We don't expose any properties, mainly because the Python bindings currently use a "swallow all" approach with
 * "getattro" and "setattro" methods, which doesn't allow for inspection from the outside.
 * If we instead exposed the properties as Python Property it would probably be doable.
 * We don't expose any docs for the constructor parameters. Although those could be defined in the general documentation of the class.
 * @param argc
 * @param argv
 * @return
 */
int main(int argc, char** argv) {
	//Ant log output goes to cerr, so as not to interfere with program output.
	spdlog::default_logger()->sinks().clear();
	spdlog::default_logger()->sinks().emplace_back(std::make_shared<spdlog::sinks::stderr_sink_mt>());
	init_python_api({&CyPy_Server::init, &CyPy_Ai::init, &CyPy_Rules<LocatedEntity, CyPy_LocatedEntity>::init, &CyPy_Physics::init, &CyPy_EntityFilter<LocatedEntity>::init, &CyPy_Atlas::init,
					 &CyPy_Common::init}, {}, false);

	if (argc > 1) {
		auto moduleName = argv[1];

		std::string commandString = R"EOF(
import inspect
import sys
import %1 as mod

def processObject(m, output, indent, isclass = False):
    #TODO: handle PyGetSetDef (which also needs support in the PyCXX lib
    i = lambda i: "    " * i
    for f in inspect.getmembers(m):
        if not f[0].startswith("_"):
            if type(f[1]) is int:
                output.append(f'{i(indent)}{f[0]} = {f[1]}')
            if type(f[1]) is str:
                output.append(f'{i(indent)}{f[0]} = "{f[1]}"')

    functions = inspect.getmembers(m)
    functions = list(filter(lambda func: func[0] == "__init__" or (inspect.isroutine(func[1]) and not func[0].startswith("_")), functions))
    if len(functions) == 0 and isclass:
        output.append(f'{i(indent)}pass')
    for f in functions:
        functiondocs = inspect.getdoc(f[1])

        try:
            sig = inspect.signature(f[1])
        except ValueError:
            sig = None
            pass

        params = []
        if isclass and f[0] != "__init__":
            params.append("self")
        if sig:
            for (name, param) in sig.parameters.items():
                if param.kind == inspect.Parameter.VAR_POSITIONAL:
                    params.append("*" + name)
                elif param.kind == inspect.Parameter.VAR_KEYWORD:
                    params.append("**" + name)
                else:
                    params.append(name)
        output.append(f'{i(indent)}def {f[0]}({", ".join(params)}):')
        if functiondocs and f[0] != "__init__":
            output.append(f'{i(indent + 1)}"""{functiondocs}"""')
        output.append(f'{i(indent + 1)}pass')


    for klass in inspect.getmembers(m, inspect.isclass):
        if not klass[0].startswith("_"):
            output.append(f"class {klass[0]}:")
            klassdocs = inspect.getdoc(klass[1])
            if klassdocs:
                output.append(f'{i(indent + 1)}"""{klassdocs}"""')
            processObject(klass[1], output, indent + 1, True)
            output.append("")

output = ["#This file is auto generated by the PythonDocGenerator tool. Please do not edit manually.", "#Its sole purpose is to provide Python stubs for the C++ code, which can be used in your IDE to help during development.", "#Do note that these types aren't complete, with constructor args and properties missing."]
moduledocs = inspect.getdoc(mod)
if moduledocs:
    output.append(f'"""{moduledocs}"""')
output.append("")

processObject(mod, output, 0)

for line in output:
    print(line)

)EOF";
		boost::replace_all(commandString, "%1", moduleName);

		PyRun_SimpleString(commandString.c_str());
	}
	shutdown_python_api();
}
