#!/usr/bin/python
# coding=utf-8

"""
Generates and keeps up-to-date actor skeleton sources.

Usage:
    gen_actor_source.py (--project|--update) DECLARATION.json

    --project       Run in generate project mode
    --update        Run in update files mode (used from Makefiles)

Example (create new actor skeleton):
    cd kumir2/src/actors
    mkdir mygreatactor
    cd mygreatactor
    { ... create here file mygreatactor.json ...}
    ../../../scripts/gen_actor_source.py --project mygreatactor.json

This will create the following files:
    CMakeLists.txt              -- CMake project file
    mygreatactormodule.h        -- header skeleton
    mygreatactormodule.cpp      -- source skeleton

===

                        0. INTRODUCTION

The module operates in two ways:
1. Update project files. Usually called from CMakeLists.txt within build process.
2. Create new project. Creates project file (CMakeLists.txt) and module skeleton.


                        1. CODE LAYOUT

Here are two group of classes. The first one covers module description tree, while
the second one covers generated files implementation.


1.1. Module description class group

The module description input data represented as JSON-file, handled by Python as
a dictionary object. Parsing this dictionary, the following structure produces
(here '+' significates 'one or more', '*' is 'zero or none', '?' is 'zero or one',
and '|' is 'or'):

    class Module (a top level class of tree) :=
        name: class Name
        methods: (class Method)+
        types: (class BaseType)*
        gui: (class Gui)?
        settings: (class Settings)?

    class Name: a Kumir/C++ name representation

    class Method :=
        name: class Name
        returnType: class BaseType | None
        async_: bool
        arguments: (class Argument)*

    class BaseType :=
        name: class Name
        standard: bool
        fields: ( <class Name, class BaseType> )*

    class Argument :=
        name: class Name
        base_type: class BaseType
        dimension: [0, 1, 2, 3]
        constatnt: bool
        reference: bool
        readable: bool

    class Gui :=
        windows: (class Window)*
        menus: (class MenuItem)*

    class Window :=
        icon: string
        role: string

    class MenuItem :=
        title: class Name
        icon: string | None
        items: (class MenuItem)*

    class Settings :=
        entries: (class SettingsEntry)*

    class SettingsEntry :=
        key: string
        title: class Name
        type: string
        default: string
        minimum: string | None
        maximum: string | None


1.2. Module implementation class group

After the JSON file is read and corresponding module tree is built, the next
stage is to generate source files.

There are four C++ modules generated by the module description:


1.2.1. Module Qt-Plugin class (here is class PluginCppClass)

The Kumir's extension system module, implements Actor interface. Provides
actor information to Kumir. This class code is auto generated and not ought to
be modified.


1.2.2. Module implementation base class (here is ModuleBaseCppClass)

A class to be inherited by implementation. The class contains mostly pure virtual
methods according to actor algorithms headers (in C++ syntax). This class
is auto generated and not ought to be modified.

1.2.3. Module implementation skeleton (here is ModuleCppClass)

A skeleton files to be implemented by actor functionality. The class inherits
auto-generated base to match up-to-date actor description. The class generates once and
must be implemented by actor developer.

1.2.4. Async run thread class (here is AsyncThreadCppClass)

A supplementary class, which declared and implemented near plugin class in the same files.
Some actor methods should be 'Asynchronous', what means that they run in separate thread.
This class provides transparent and convient layout to developer not to implement this
feature hisself.


                        2. SCRIPT WORKFLOW

The first, script reads provided JSON file and creates an actor module description tree
(class Module instance).

Then, there are two modes of operation: generate an update set of files, or generate a
project set of files.


2.1. Update set of files

In this mode the following files generated:
    - actor plugin header (function createPluginHeaderFile);
    - actor plugin source (function createPluginSourceFile);
    - actor implementation base header (function createModuleBaseHeaderFile);
    - actor implementation base source (function createModuleBaseSourceFile);
    - actor plugin specification (function createPluginSpecFile).

2.2. Project set of files

In this mode the following files generated:
    - actor implementation header (function createModuleHeaderFile);
    - actor implementation source (function createModuleSourceFile);
    - actor project file (function createCMakeListsTxt);
    - actor documentation file (function createDocbookFile).

"""
import copy
import sys
import json
import string
import os
import inspect
from _ast import mod


def string_join(lines, sep):
    result = ""
    for index, line in enumerate(lines):
        if index > 0:
            result += sep
        result += line
    return result


# Hacks for Python 2/3 compatibility
if sys.version_info.major >= 3:
    unicode = str
    string.join = string_join

MODULE_CLASS_SUFFIX = "Module"
MODULE_BASE_CLASS_SUFFIX = "ModuleBase"
MODULE_RUN_THREAD_SUFFIX = "AsyncRunThread"
MODULE_PLUGIN_CLASS_SUFFIX = "Plugin"
MODULE_NAMESPACE_PREFIX = "Actor"
SETTINGS_TYPES = {
    "int": "Integer",
    "double": "Double",
    "string": "String",
    "char": "Char",
    "bool": "Bool",
    "color": "Color",
    "font": "Font",
    "choice": "Choice",
}


def _render_template(template, values):
    """
    Renders simple template using $-substitutions

    :param template:    template to render
    :type template:     str
    :type values:       dict
    :param values:      values to substitute
    :rtype:             unicode
    :return:            a string with replaced substitutions
    """
    assert isinstance(template, str) or isinstance(template, unicode)
    assert isinstance(values, dict)
    for key, value in values.items():
        template = template.replace("$" + key, value)
    return template


def _add_indent(text):
    """
    Adds a one indent (4 spaces) to each line of text

    :type text:     unicode or str
    :param text:    text to indent
    :rtype:         unicode or str
    :return:        4-space indented text
    """
    assert isinstance(text, unicode) or isinstance(text, str)
    lines = text.split('\n')
    indented_lines = map(lambda x: "    " + x, lines)
    # noinspection PyUnresolvedReferences
    return string.join(indented_lines, '\n')


# The group of module description tree classes

class Name:
    """
    A class representing localizable name.
    """

    def __init__(self, json_node):
        """
        Initializes from JSON value
        """
        assert isinstance(json_node, dict) or isinstance(json_node, str) or isinstance(json_node, unicode)
        if isinstance(json_node, dict):
            assert "ascii" in json_node
            self.data = json_node
        else:
            self.data = dict()
            self.data["ascii"] = unicode(json_node)

    def get_kumir_value(self):
        """
        Returns an Unicode representation for Russian language or ASCII

        :rtype:     unicode
        :return:    name as is
        """
        if "ru_RU" in self.data:
            return unicode(self.data["ru_RU"])
        else:
            return unicode(self.data["ascii"])

    def get_ascii_value(self):
        """
        Returns an ASCII representation

        :rtype:     str
        :return:    ASCII name as is
        """
        return str(self.data["ascii"])

    def get_cpp_value(self):
        """
        Returns a valid C++ name based on source name

        :rtype:     str
        :return:    valid C++ identifier
        """
        result = ""
        next_is_capital = False
        ascii_name = self.data["ascii"]
        if ascii_name == "+":
            return "OperatorPLUS"
        elif ascii_name == "-":
            return "OperatorMINUS"
        elif ascii_name == "*":
            return "OperatorASTERISK"
        elif ascii_name == "**":
            return "OperatorPOWER"
        elif ascii_name == "/":
            return "OperatorSLASH"
        elif ascii_name == "=":
            return "OperatorEQUAL"
        elif ascii_name == "<>":
            return "OperatorNOTEQUAL"
        elif ascii_name == "<":
            return "OperatorLESS"
        elif ascii_name == ">":
            return "OperatorGREATER"
        elif ascii_name.startswith(":="):
            return "OperatorASSIGN"
        elif ascii_name == "input":
            return "OperatorINPUT"
        elif ascii_name == "output":
            return "OperatorOUTPUT"
        for c in ascii_name:
            if c == ' ':
                next_is_capital = True
            elif c in "\\%":
                break
            else:
                if next_is_capital:
                    result += c.upper()
                elif c.upper() in "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_":
                    result += c
                next_is_capital = False
        result = result.replace("#", "").replace("?", "").replace("!", "")
        return result

    def get_camel_case_cpp_value(self):
        """
        Returns a valid CamelCase C++ name based on source name

        :rtype:     str
        :return:    valid C++ identifier
        """
        cpp = self.get_cpp_value()
        return cpp[0].upper() + cpp[1:]


class BaseType:
    """
    Scalar data type
    """
    _typeTable = dict()  # a table of actor's custom types

    def __init__(self, module, json_node):
        """
        Initializes from  JSON value or uses existing type

        :type   module:     Module or None
        :param  module:     module, where type declared
        :type   json_node:   dict, str, unicode
        :param  json_node:   JSON node for custom type or name for standard type
        """
        assert isinstance(json_node, dict) or isinstance(json_node, str) or isinstance(json_node, unicode)
        self._module = None
        self._standard = None
        self._name = None
        self._fields = None
        if isinstance(json_node, dict):
            self.__init__from_dict(json_node)
        else:
            self.__init__from_string(str(json_node))
        if self._module is None:
            self._module = module

    def __init__from_dict(self, json_node):
        """
        Initializes a custom type from detailed specification

        :type   json_node:   dict
        :param  json_node:   JSON node for custom type details
        """
        assert isinstance(json_node, dict)
        assert "name" in json_node and "fields" in json_node
        self._standard = False
        self._name = Name(json_node["name"])
        self._fields = []
        node_fields = json_node["fields"]
        assert isinstance(node_fields, list)
        for field in node_fields:
            assert isinstance(field, dict)
            assert "name" in field and "baseType" in field
            field_name = Name(field["name"])
            field_type = BaseType(None, field["baseType"])
            pair = field_name, field_type
            self._fields.append(pair)
        BaseType._typeTable[self._name.get_ascii_value()] = self

    def __init__from_string(self, name):
        """
        Initializes a standard type by its name

        :type   name:   str
        :param  name:   a value in ["int", "double", "string", "bool", "char"]
        """
        assert isinstance(name, str) or isinstance(name, unicode)
        if name in BaseType._typeTable:
            existing_type = BaseType._typeTable[name]
            assert isinstance(existing_type, BaseType)
            self._name = copy.deepcopy(existing_type.get_name())
            self._fields = copy.deepcopy(existing_type.get_fields())
            self._standard = existing_type.is_standard_type()
            self._module = existing_type.get_module()
        else:
            self._standard = True
            self._fields = []
            qualified_name = dict()
            qualified_name["ascii"] = name
            if name == "int":
                qualified_name["ru_RU"] = u"цел"
            elif name == "double":
                qualified_name["ru_RU"] = u"вещ"
            elif name == "bool":
                qualified_name["ru_RU"] = u"лог"
            elif name == "char":
                qualified_name["ru_RU"] = u"сим"
            elif name == "string":
                qualified_name["ru_RU"] = u"лит"
            self._name = Name(qualified_name)
            BaseType._typeTable[self._name.get_ascii_value()] = self

    def get_name(self):
        return self._name

    def get_fields(self):
        return self._fields

    def get_module(self):
        return self._module

    def is_standard_type(self):
        """
        Is a standard Qt base type

        :rtype:     bool
        :return:    True, if int, double, bool, char or string
        """
        return self._standard

    def get_kumir_name(self):
        """
        Kumir name for the type

        :rtype:     unicode
        :return:    russian type name
        """
        return self._name.get_kumir_value()

    def get_ascii_name(self):
        return self._name.get_ascii_value()

    def get_qt_name(self):
        """
        Qt-style type name

        :rtype:     str
        :return:    ASCII name matched to Qt/C++ type
        """
        if not self._standard:
            return self._name.get_camel_case_cpp_value()
        elif self._name.get_ascii_value() == "string":
            return "QString"
        elif self._name.get_ascii_value() == "char":
            return "QChar"
        elif self._name.get_ascii_value() == "double":
            return "qreal"
        else:
            return self._name.get_ascii_value()

    def get_cpp_declaration(self):
        """
        For non-standard types creates type declaration or an empty string

        :rtype:     str
        :return:    an empty string for standard type or a struct declaraion otherwise
        """
        if self._standard:
            return ""
        else:
            name_decl = self.get_qt_name()
            fields_decl = ""
            # noinspection PyTypeChecker
            for name, base_type in self._fields:
                assert isinstance(name, Name)
                assert isinstance(base_type, BaseType)
                fields_decl += "    "
                fields_decl += base_type.get_qt_name() + " "
                fields_decl += name.get_cpp_value() + ";"
                fields_decl += "\n"
            return _render_template("""
struct $name {
$fields
};
            """, {"name": name_decl, "fields": fields_decl})

    def get_cpp_custom_type_creation(self, variable_to_append, variable_to_assign):
        """
        For non-standard types creates kumir type declaration, an empty string otherwise

        :type variable_to_append:    str
        :para variable_to_append:    C++ variable name (std::list or QList) to store result
        :rtype:     unicode
        :return:    an empty string for standard type or Shared::ActorInterface::CustomType implementation code
        """
        if self._standard:
            return ""
        else:
            assert variable_to_append is None or isinstance(variable_to_append, str)
            assert variable_to_assign is None or isinstance(variable_to_assign, str)
            assert variable_to_assign or variable_to_append
            result = "{\n"
            result += "    Shared::ActorInterface::CustomType custom;\n"
            result += "    Shared::ActorInterface::Record record;\n"
            # noinspection PyTypeChecker
            for fieldName, fieldType in self._fields:
                assert isinstance(fieldName, Name)
                assert isinstance(fieldType, BaseType)
                assert fieldType.get_qt_name() in ["int", "qreal", "bool", "QChar", "QString"]
                name = fieldName.get_kumir_value()
                typee = fieldType.get_qt_name()
                if typee[0].lower() == 'q':
                    typee = typee[1:]
                typee = typee[0].upper() + typee[1:]
                result += "    record.push_back(Field(QByteArray(\"%s\"), %s));\n" % (name, typee)
            type_name = self._name.get_kumir_value()
            result += \
                "    custom = Shared::ActorInterface::CustomType(QString::fromUtf8(\"%s\"), record);\n" % type_name
            if variable_to_append:
                result += "    %s.push_back(custom);\n" % variable_to_append
            elif variable_to_assign:
                result += "    %s = custom;\n" % variable_to_assign
            result += "}\n"
            return result

    def get_cpp_record_spec_creation(self, variable_to_append, variable_to_assign):
        """
        For non-standard types creates kumir type declaration, an empty string otherwise

        :type variable_to_append:    str or None
        :param variable_to_append:   C++ variable name (std::list or QList) to store result
        :type variable_to_assign:    str or None
        :param variable_to_assign:   C++ variable name to assign
        :rtype:     unicode
        :return:    an empty string for standard type or Shared::ActorInterface::CustomType implementation code
        """
        if self._standard:
            return ""
        else:
            assert variable_to_append is None or isinstance(variable_to_append, str)
            assert variable_to_assign is None or isinstance(variable_to_assign, str)
            assert variable_to_assign or variable_to_append
            result = "{\n"
            result += "    Shared::ActorInterface::RecordSpecification recordSpec;\n"
            # noinspection PyTypeChecker
            for field_name, field_type in self._fields:
                assert isinstance(field_name, Name)
                assert isinstance(field_type, BaseType)
                assert field_type.get_qt_name() in ["int", "qreal", "bool", "QChar", "QString"]
                name = field_name.get_kumir_value()
                typee = field_type.get_qt_name()
                if typee[0].lower() == 'q':
                    typee = typee[1:]
                typee = "Shared::ActorInterface::" + typee[0].upper() + typee[1:]
                result += "    recordSpec.record.push_back(Shared::ActorInterface::Field(QByteArray(\"%s\"), %s));\n" \
                          % (name, typee)
            result += "    recordSpec.asciiName = QByteArray(\"%s\");\n" % self._name.get_ascii_value()
            for key, value in self._name.data.items():
                qlocale = None
                if key == "ru_RU":
                    qlocale = "QLocale::Russian"
                if qlocale:
                    result += "     recordSpec.localizedNames[%s] = QString::fromUtf8(\"%s\");\n" % (qlocale, value)
            if variable_to_append:
                result += "    %s.push_back(recordSpec);\n" % variable_to_append
            elif variable_to_assign:
                result += "    %s = recordSpec;\n" % variable_to_assign
            result += "}\n"
            return result

    def get_cpp_custom_type_encode_decode(self):
        """
        Generates encode/decode to/from QVariant inline functions for custom type methods

        :rtype:     str
        :return:    functions C++ inline body for custom type or empty string for standard string
        """
        if self._standard:
            return ""
        else:
            body_encode = ""
            body_decode = ""
            # noinspection PyTypeChecker
            for index, (field_name, field_type) in enumerate(self._fields):
                assert isinstance(field_name, Name)
                assert isinstance(field_type, BaseType)
                assert field_type.get_qt_name() in ["int", "qreal", "bool", "QChar", "QString"]
                typee = field_type.get_qt_name()
                defvalue = ""
                if typee[0] == 'Q':
                    conversion = "to" + typee[1].upper() + typee[2:] + "()"
                    defvalue = typee + "()"
                elif typee == "qreal":
                    conversion = "toDouble()"
                    defvalue = "0.0"
                else:
                    conversion = "to" + typee[0].upper() + typee[1:] + "()"
                if not defvalue:
                    if typee == "int":
                        defvalue = "0"
                    elif typee == "bool":
                        defvalue = "false"
                field = field_name.get_cpp_value()
                body_encode += "    result << QVariant(record.%s);\n" % field
                body_decode += "    result.%s = alist.size() > %i ? alist.at(%i).%s : %s;\n" % (
                    field, index, index, conversion, defvalue
                )
            substitutions = {
                "typeName": self.get_qt_name(),
                "bodyEncode": body_encode,
                "bodyDecode": body_decode
            }
            return _render_template("""
QVariant encode(const $typeName & record) {
    QVariantList result;
$bodyEncode
    return result;
}

$typeName decode(const QVariant & raw) {
    $typeName result;
    const QVariantList alist = raw.toList();
$bodyDecode
    return result;
}
            """, substitutions)

    def get_typedef(self):
        """Creates  typedef in case of external namespace"""
        if self._module:
            return "typedef %s::%s %s;" % (
                self._module.get_module_cpp_namespace(),
                self.get_qt_name(), self.get_qt_name()
            )
        else:
            return ""


class Argument:
    """
    An actor method argument
    """

    def __init__(self, json_node):
        """
        Initializes from JSON node value

        :type   json_node:   dict
        :param  json_node:   an argument specification
        """
        assert isinstance(json_node, dict)
        self.name = Name(json_node["name"])
        self.base_type = BaseType(None, json_node["baseType"])
        self.dimension = 0
        if "dim" in json_node:
            self.dimension = int(json_node["dim"])
        assert 0 <= self.dimension <= 3
        if "access" in json_node:
            self.constant = json_node["access"] in ["in"]
            self.reference = json_node["access"] in ["out", "in/out"]
            self.readable = json_node["access"] in ["in", "in/out"]
        else:
            self.constant = True
            self.reference = False
            self.readable = True

    def _get_vector_type(self):
        """
        Creates Qt-vector type declaration

        :rtype:     str
        :return:    C++ argument declaration
        """
        base_type = self.base_type.get_qt_name()
        dimension = self.dimension
        assert isinstance(base_type, str)
        assert isinstance(dimension, int)
        assert 0 < dimension <= 3
        result = "QVector< " * dimension + base_type + " >" * dimension
        return result

    def get_cpp_argument_declaration(self):
        """
        Creates C++ function argument declaration

        :rtype:     str
        :return:    C++ argument declaration
        """
        result = ""
        if self.constant:
            result += "const "
        if self.dimension > 0:
            result += self._get_vector_type()
        else:
            result += self.base_type.get_qt_name()
        if self.dimension > 0 or not self.base_type.get_qt_name() in ["int", "qreal", "bool"] or self.reference:
            result += "&"
        result += " " + self.name.get_cpp_value()
        return result

    def get_cpp_local_variable_declaration(self):
        """
        Creates C++ local variable declaration

        :rtype:     str
        :return:    C++ plain variable declaration
        """
        result = ""
        if self.dimension > 0:
            result += self._get_vector_type()
        else:
            result += self.base_type.get_qt_name()
        result += " " + self.name.get_cpp_value()
        return result

    def get_kumir_argument_declaration(self):
        """
        Creates Kumir argument declaration

        :rtype:     unicode
        :return:    Kumir argument declaraion
        """
        result = ""
        if self.constant and not self.readable:
            result += u"арг "
        elif self.readable and self.reference:
            result += u"аргрез "
        elif self.reference:
            result += u"рез "
        result += self.base_type.get_kumir_name()
        if self.dimension > 0:
            result += u"таб"
        result += " " + self.name.get_kumir_value()
        if self.dimension > 0:
            result += "[" + "0:0," * (self.dimension - 1) + "0:0]"
        return result


class Method:
    """
    An actor static method
    """

    def __init__(self, json_node):
        assert isinstance(json_node, dict)
        self.name = Name(json_node["name"])
        if "returnType" in json_node:
            self.return_type = BaseType(None, json_node["returnType"])
        else:
            self.return_type = None
        if "async" in json_node:
            self.async_ = bool(json_node["async"])
        else:
            self.async_ = self.return_type is None
        self.arguments = []
        if "arguments" in json_node:
            for arg in json_node["arguments"]:
                assert isinstance(arg, dict)
                argument = Argument(arg)
                self.arguments.append(argument)

    def get_cpp_declaration(self):
        """
        C++ method declaraion

        rtype:      str
        return:     C++ header declaration for this method
        """
        result = ""
        if self.return_type is None:
            result += "void "
        else:
            rtype = self.return_type
            assert isinstance(rtype, BaseType)
            result += rtype.get_qt_name() + " "
        arg_declarations = map(lambda x: x.get_cpp_argument_declaration(), self.arguments)
        # noinspection PyUnresolvedReferences
        result += "run" + self.name.get_camel_case_cpp_value() + "(" + string.join(arg_declarations, ", ") + ")"
        return result

    def get_kumir_declaration(self):
        """
        Kumir method declaration

        rtype:      unicode
        return:     Kumir header to be parsed by Kumir analizer as text program
        """
        result = u"алг "
        if self.return_type is not None:
            rtype = self.return_type
            assert isinstance(rtype, BaseType)
            result += rtype.get_kumir_name() + " "
        result += self.name.get_kumir_value()
        if self.arguments:
            arg_declarations = map(lambda x: x.get_kumir_argument_declaration(), self.arguments)
            # noinspection PyUnresolvedReferences
            result += "(" + string.join(arg_declarations, ", ") + ")"
        return result

    def get_cpp_implementation_stub(self, class_name):
        """
        Creates default method implementation stub

        :type  class_name:  str
        :param class_name:  module class name used in C++ before ::
        :rtype:             unicode
        :return:            C++ implementation stub
        """
        kumir_return_type = ""
        retval = None
        result = "/* public slot */ "
        if self.return_type is not None:
            kumir_return_type = self.return_type.get_kumir_name() + " "
            if self.return_type.get_qt_name() == "qreal":
                retval = "0.0"
            elif self.return_type.get_qt_name() == "int":
                retval = "0"
            elif self.return_type.get_qt_name() == "bool":
                retval = "false"
            else:
                retval = self.return_type.get_qt_name() + "()"
        if self.return_type is None:
            result += "void "
        else:
            result += self.return_type.get_qt_name() + " "
        result += class_name + "::run" + self.name.get_camel_case_cpp_value()
        body = u"/* алг " + kumir_return_type + self.name.get_kumir_value()
        if self.arguments:
            body += "("
            for index, argument in enumerate(self.arguments):
                assert isinstance(argument, Argument)
                if index:
                    body += ", "
                body += argument.get_kumir_argument_declaration()
            body += ")"
        body += " */\n"
        body += "// TODO implement me\n"
        if not self.arguments:
            result += "()\n"
        else:
            result += "("
            for argument in self.arguments:
                assert isinstance(argument, Argument)
                argument_line = argument.get_cpp_argument_declaration()
                if argument != self.arguments[-1]:
                    argument_line += ", "
                else:
                    argument_line += ")"
                result += argument_line
                body += "Q_UNUSED(" + argument.name.get_cpp_value() + ")  // Remove this line on implementation;\n"
            result += "\n"
        if retval:
            body += "return " + retval + ";\n"
        result += "{\n" + _add_indent(body) + "\n}\n\n"
        return result


class Window:
    """
    A windows of GUI part.
    """

    def __init__(self, json_node):
        """
        Initializes from JSON value

        :type   json_node:   dict
        :param  json_node:   window specification
        """
        assert isinstance(json_node, dict)
        self.role = str(json_node["role"])
        self.icon = str(json_node["icon"])


class MenuItem:
    """
    An item of GUI menu.
    """

    def __init__(self, json_node):
        """
        Initializes from JSON value

        :type   json_node:   dict
        :param  json_node:   menu item specification
        """
        assert isinstance(json_node, dict)
        assert "title" in json_node
        self.title = Name(json_node["title"])
        self.items = []
        if "items" in json_node:
            for item in json_node["items"]:
                self.items.append(MenuItem(item))
        if "icon" in json_node:
            self.icon = str(json_node["icon"])
        else:
            self.icon = None

    def __repr__(self):
        return self.title.get_ascii_value()


class Gui:
    """
    GUI part of module.
    """

    def __init__(self, json_node):
        """
        Initializes from JSON value

        :type   json_node:   dict
        :param  json_node:   GUI specification
        """
        assert isinstance(json_node, dict)
        self.windows = []
        self.menus = []
        if "windows" in json_node:
            for window in json_node["windows"]:
                self.windows.append(Window(window))
        if "menus" in json_node:
            for menu in json_node["menus"]:
                self.menus.append(MenuItem(menu))
        if "optional" in json_node:
            self.optional = json_node["optional"]
        else:
            self.optional = False

    def get_icon_name(self, role):
        """
        Icon name for specified window role

        :type role:     str
        :param role:    role of window (currently "main" and "pult")
        :rtype:         str
        :return:        icon base name of an empty string if not specified
        """
        for window in self.windows:
            assert isinstance(window, Window)
            if window.role == role and window.icon:
                return window.icon
        return ""


class Module:
    """
    A Kumir module.
    """

    def __init__(self, module_dir, json_file_name, json_node):
        """
        Initializes from JSON value

        :type   module_dir:  str or unicode
        :param  module_dir:  module root directory
        :type   json_node:   dict
        :param  json_node:   module specification (JSON root)
        """
        assert isinstance(json_node, dict)
        assert "name" in json_node
        assert "methods" in json_node
        assert isinstance(json_file_name, str) or isinstance(json_file_name, unicode)
        self.name = Name(json_node["name"])
        self.types = []
        self.uses_list = []
        self.default_template_parameters = []
        self.json_file_name = json_file_name
        if "templateDefaults" in json_node:
            self.default_template_parameters = json_node["templateDefaults"]
        if "types" in json_node:
            for typee in json_node["types"]:
                self.types.append(BaseType(self, typee))
        if "uses" in json_node:
            self.uses_list = json_node["uses"]
            self._read_dependencies(module_dir, self.uses_list)
        self.methods = []
        for method in json_node["methods"]:
            self.methods.append(Method(method))
        if "gui" in json_node:
            self.gui = Gui(json_node["gui"])
        else:
            self.gui = None
        if "settings" in json_node:
            self.settings = Settings(json_node["settings"])
        else:
            self.settings = None

    def _read_dependencies(self, module_dir, uses_list):
        """
        Read custom types provided by used actors
        :param module_dir: module directory
        :param uses_list: a list of ASCII actor names
        """
        updir = os.path.normpath(module_dir + os.path.sep + ".." + os.path.sep)
        for use in uses_list:
            filename = updir + os.path.sep + use.lower() + os.path.sep + use.lower() + ".json"
            use_module = Module.read(filename)
            self.types += use_module.types

    @classmethod
    def read(cls, file_name):
        """
        Reads module definition from JSON file

        :type   file_name:  unicode
        :param  file_name:  a file name to read from
        :rtype:             Module
        :return:            Kumir module object
        """
        f = open(file_name, 'r', encoding="utf-8")
        data = json.load(f)
        f.close()
        absolute_path = os.path.abspath(file_name)
        module_dir = os.path.dirname(absolute_path)
        result = Module(module_dir, os.path.split(file_name)[1], data)
        return result

    def get_module_cpp_class_name(self):
        """
        Module class name to be implemented by developer

        :rtype:     str
        :return:    class name
        """
        return self.name.get_camel_case_cpp_value() + MODULE_CLASS_SUFFIX

    def get_base_cpp_class_name(self):
        """
        Base module class name generated and updated by script

        :rtype:     str
        :return:    class name
        """
        return self.name.get_camel_case_cpp_value() + MODULE_BASE_CLASS_SUFFIX

    def get_plugin_cpp_class_name(self):
        """
        Plugin class name generated and updated by script

        :rtype:     str
        :return:    class name
        """
        return self.name.get_camel_case_cpp_value() + MODULE_PLUGIN_CLASS_SUFFIX

    def get_run_thread_cpp_class_name(self):
        """
        Class name for asynchronuous thread

        :rtype:     str
        :return:    class name
        """
        return self.name.get_camel_case_cpp_value() + MODULE_RUN_THREAD_SUFFIX

    def get_module_cpp_namespace(self):
        """
        Module namespace

        :rtype:     str
        :return:    namespace
        """
        return MODULE_NAMESPACE_PREFIX + self.name.get_camel_case_cpp_value()


class SettingsEntry:
    """
    An entry for actor settings page
    """

    def __init__(self, key, json_entry):
        """
        Initializes from JSON value

        :type   key:        str
        :param  key:        an unique settings key string
        :type   json_entry:  dict
        :param  json_entry:  settings entry specification
        """
        assert isinstance(key, str) or isinstance(key, unicode)
        assert isinstance(json_entry, dict)
        self._key = key
        self._type = json_entry["type"]
        self._default = json_entry["default"]
        if isinstance(self._default, dict):
            self._default = self._default[os.name]
        if "minimum" in json_entry:
            self._minimum = json_entry["minimum"]
        else:
            self._minimum = None
        if "maximum" in json_entry:
            self._maximum = json_entry["maximum"]
        else:
            self._maximum = None
        self._title = Name(json_entry["title"])
        if "order" in json_entry:
            self._order = json_entry["order"]
        else:
            self._order = 99999999
        if "items" in json_entry:
            self._items = json_entry["items"]
        else:
            self._items = None

    def get_entry_cpp_implementation(self, map_to_add):
        """
        Creates C++ implementation for creation of settings entry

        :type   map_to_add: str
        :param  map_to_add: C++ valid name of variable of type QMap<QString,Entry> to store result
        :rtype:             unicode
        :return:            C++ code for settings entry creation
        """
        assert isinstance(map_to_add, str)
        if "ru_RU" in self._title.data:
            title = self._title.data["ru_RU"]
        else:
            title = self._title.data["ascii"]
        global SETTINGS_TYPES
        typee = SETTINGS_TYPES[self._type]
        if typee in ["String", "Color", "Font"]:
            default = "QString::fromUtf8(\"%s\")" % unicode(self._default)
        else:
            default = unicode(self._default)
        if self._minimum is None:
            minimum = "QVariant::Invalid"
        else:
            minimum = unicode(self._minimum)
        if self._maximum is None:
            maximum = "QVariant::Invalid"
        else:
            maximum = unicode(self._maximum)
        items = "QStringList()";
        if self._items:
            for item in self._items:
                items += " << QString::fromUtf8(\"" + item + "\")"
        return """
{
    Widgets::DeclarativeSettingsPage::Entry entry;
    entry.title = QString::fromUtf8("%s");  // TODO non-Russian language support
    entry.type = Widgets::DeclarativeSettingsPage::%s;
    entry.defaultValue = %s;
    entry.minimumValue = %s;
    entry.maximumValue = %s;
    entry.items = %s;
    entry.displayOrder = %s;
    %s["%s"] = entry;
}
        """ % (title, typee, default, minimum, maximum, items, self._order, map_to_add, self._key)


class Settings:
    """
    Settings entries specification
    """

    def __init__(self, json_node):
        """
        Initializes from JSON node

        :type   json_node:   dict
        :param  json_node:   settings specification
        """
        assert isinstance(json_node, dict)
        self._entries = []
        for key, value in json_node.items():
            self._entries.append(SettingsEntry(key, value))

    def get_settings_page_creation(self, variable_name):
        """
        Creates a C++ code for settings page creation

        :type   variable_name:  str
        :param  variable_name:  DeclarativeSettingsPage* type C++ variable name to store
        :rtype:                 unicode
        :return:                C++ code for settings page creation
        """
        result = "QMap<QString,Widgets::DeclarativeSettingsPage::Entry> entries;\n"
        self._entries.sort(key=lambda entry: entry._key)
        for entry in self._entries:
            assert isinstance(entry, SettingsEntry)
            result += entry.get_entry_cpp_implementation("entries")
        result += """
bool guiAvailable = (qobject_cast<QApplication*>(qApp) != 0);
#ifdef Q_OS_LINUX
//guiAvailable = 0 != getenv("DISPLAY");
#endif
if (guiAvailable) {
    %s = new Widgets::DeclarativeSettingsPage(
                            Shared::actorCanonicalName(localizedModuleName(QLocale::Russian)),
                            mySettings(),
                            entries
                          );
    connect(%s, SIGNAL(settingsChanged(QStringList)), this, SLOT(handleSettingsChangedCppImplementation(QStringList)));
}

        """ % (variable_name, variable_name)
        return result


# The group of classes to generate sources

class CppClassBase:
    """
    Base class for C++ class implementation.

    The derived classes implements methods with suffix "CppImplementation", while base class
    functionality is to introspect derived class structure and generate corresponding header
    and source file data

    """

    def __init__(self):
        """
        Field initialization
        """
        self.class_name = None
        self.class_declaration_prefix = ""
        self.class_declaration_suffix = ""
        self.fields = []
        self.signals = []
        self.base_classes = []
        self.abstract_public_methods = []
        self.abstract_public_slots = []
        self.extra_implementations = []

    def __implementation_of_method__(self, method_name):
        """
        Calls method implementation creation and returns a result.

        Each implemented C++ method should have corresponding Python method named
        the same + suffix 'CppImplementation'

        :type   method_name: str
        :param  method_name: С++ method name to find an call its Pythonic creator
        :rtype:             unicode
        :return:            C++ code
        """
        assert isinstance(method_name, str)
        python_method_name = method_name + "CppImplementation"
        try:
            method = getattr(self, python_method_name).__func__
        except AttributeError:
            method = None
        if method:
            return method(self)
        else:
            return ""

    def get_cpp_declaration(self):
        """
        Generates C++ (with Qt extensions) class declaration for header file

        :rtype:     unicode
        :return:    C++ class declaration
        """
        publics = []
        public_slots = []
        protecteds = []
        protected_slots = []
        privates = []
        private_slots = []
        public_virtuals = []
        public_virtual_slots = []
        constructor = None
        public_typedefs = []
        non_inspectable = map(lambda x: ("CppImplementation", x), self.extra_implementations)
        self_members = inspect.getmembers(self)
        if sys.version_info.major >= 3:
            non_inspectable = list(non_inspectable)
        for key, value in self_members + non_inspectable:
            assert isinstance(key, str)
            if key.endswith("CppImplementation"):
                if isinstance(value, str) or isinstance(value, unicode):
                    implementation = value
                else:
                    implementation_get_method = value.__func__
                    implementation = implementation_get_method(self)
                assert isinstance(implementation, str) or isinstance(implementation, unicode)
                first_line = implementation.strip().split('\n')[0].strip()
                assert isinstance(first_line, str) or isinstance(first_line, unicode)
                if not first_line:
                    continue  # pure virtual method
                group_end_pos = first_line.index("*/")
                group_name = first_line[2:group_end_pos].strip().replace(" ", "_")
                # noinspection PyTypeChecker
                return_type_end_pos = first_line.index(" " + self.class_name + "::", group_end_pos + 3)
                return_type = first_line[group_end_pos + 2:return_type_end_pos].strip()
                # noinspection PyTypeChecker
                signature_begin_pos = first_line.index(self.class_name + "::", return_type_end_pos) + 2 + len(
                        self.class_name)
                signature = first_line[signature_begin_pos:]
                assert group_name in ["public", "protected", "private", "public_slot", "protected_slot", "private_slot",
                                      "public_virtual", "public_virtual_slot", "public_static"]
                if "virtual" in group_name:
                    modifier = "virtual "
                    group = locals()[group_name + 's']
                elif "static" in group_name:
                    modifier = "static "
                    group = locals()[group_name[0:-7] + 's']
                else:
                    modifier = ""
                    group = locals()[group_name + 's']
                group += [_add_indent(modifier + return_type + " " + signature + ";")]
            elif key == "constructorImplementation":
                implementation_get_method = value.__func__
                implementation = implementation_get_method(self)
                assert isinstance(implementation, str) or isinstance(implementation, unicode)
                first_line = implementation.strip().split('\n')[0].strip()
                assert isinstance(first_line, str) or isinstance(first_line, unicode)
                assert "::" in first_line
                start = first_line.index("::") + 2
                signature = first_line[start:].strip()
                publics.insert(0, _add_indent(signature + ";"))
        publics += map(lambda x: _add_indent("virtual " + x + " = 0;"), self.abstract_public_methods)
        public_slots += map(lambda x: _add_indent("virtual " + x + " = 0;"), self.abstract_public_slots)
        if publics or public_virtuals:
            publics = ["public /* methods */:"] + publics + public_virtuals
        if public_slots or public_virtual_slots:
            public_slots = ["public Q_SLOTS:"] + public_slots + public_virtual_slots
        if protecteds:
            protecteds = ["protected /* methods */:"] + protecteds
        if privates:
            privates = ["private /* methods */:"] + privates
        if private_slots:
            private_slots = ["private Q_SLOTS:"] + private_slots
        if self.signals:
            # noinspection PyUnresolvedReferences
            signals = string.join(["Q_SIGNALS:"] + list(map(lambda x: _add_indent(x + ";"), self.signals)), '\n')
        else:
            signals = ""
        if self.fields:
            # noinspection PyUnresolvedReferences
            fields = string.join(["protected /* fields */:"] + list(map(lambda x: _add_indent(x + ";"), self.fields)),
                                 '\n')
        else:
            fields = ""
        if self.base_classes:
            # noinspection PyUnresolvedReferences
            base_classes = "\n    : public " + string.join(self.base_classes, "\n    , public ")
        else:
            base_classes = ""
        # noinspection PyUnresolvedReferences
        substitutions = {
            "className": self.class_name,
            "classDeclarationPrefix": self.class_declaration_prefix,
            "classDeclarationSuffix": self.class_declaration_suffix,
            "baseClasses": base_classes,
            "publicMethods": string.join(publics, "\n"),
            "publicSlots": string.join(public_slots, "\n"),
            "protectedMethods": string.join(protecteds, "\n"),
            "privateMethods": string.join(privates, "\n"),
            "protectedSlots": string.join(protected_slots, "\n"),
            "privateSlots": string.join(private_slots, "\n"),
            "privateFields": fields,
            "signals": signals
        }
        return _render_template("""
class $className$baseClasses
{
$classDeclarationPrefix
$publicMethods
$publicSlots
$signals


    /* ========= CLASS PRIVATE ========= */
$protectedMethods
$privateMethods
$protectedSlots
$privateSlots
$privateFields
$classDeclarationSuffix
};
        """, substitutions)

    def get_cpp_implementation(self):
        """
        Generates complete C++ class implementation for all the methods

        :rtype:     unicode
        :return:    C++ class implementation code
        """
        result = ""
        non_inspectable = list(map(lambda x: ("CppImplementation", x), self.extra_implementations))
        for key, value in inspect.getmembers(self) + non_inspectable:
            assert isinstance(key, str)
            if key.endswith("CppImplementation") or key == "constructorImplementation":
                if isinstance(value, str) or isinstance(value, unicode):
                    implementation = value
                else:
                    implementation_get_method = value.__func__
                    implementation = implementation_get_method(self)
                assert isinstance(implementation, str) or isinstance(implementation, unicode)
                if key == "constructorImplementation":
                    result = implementation.strip() + "\n\n" + result
                else:
                    result += implementation.strip() + "\n\n"
        return result


class PluginCppClass(CppClassBase):
    """
    C++ Qt Plugin class.
    """

    def __init__(self, module):
        """
        Initializes from actor module tree

        :type   module: Module
        :param  module: actor module root
        """
        CppClassBase.__init__(self)
        assert isinstance(module, Module)
        self._module = module
        self.class_name = module.get_plugin_cpp_class_name()
        self.base_classes = [
            "ExtensionSystem::KPlugin",
            "Shared::ActorInterface"
        ]
        self.fields = [
            "class %s* module_" % module.get_base_cpp_class_name(),
            "class %s* asyncRunThread_" % module.get_run_thread_cpp_class_name(),
            "class Widgets::DeclarativeSettingsPage* settingsPage_",
            "QString errorText_",
            "QVariant result_",
            "QVariantList optResults_",
            "ExtensionSystem::CommandLine commandLineParameters_"
        ]
        self.signals = ["void sync()", "void asyncRun(quint32, const QVariantList &)",
                        "void notifyOnTemplateParametersChanged()"]
        self.class_declaration_prefix = """
    friend class %s;
    friend class %s;
    Q_OBJECT
#if QT_VERSION >= 0x050000
    Q_PLUGIN_METADATA(IID "kumir2.%s")
#endif
    Q_INTERFACES(Shared::ActorInterface)
        """ % (
            self._module.get_run_thread_cpp_class_name(),
            self._module.get_base_cpp_class_name(),
            self._module.get_module_cpp_namespace()
        )
        self.class_declaration_suffix = """
private:
    template <typename T> inline static QVector<T> toVector1(const QVariant & v)
    {
        const QVariantList l = v.toList();
        QVector<T> result;
        result.resize(l.size());
        for (int i=0; i<l.size(); i++) {
            result[i] = qvariant_cast<T>(l[i]);
        }
        return result;
    }
    template <typename T> inline static QVector< QVector<T> > toVector2(const QVariant & v)
    {
        const QVariantList l = v.toList();
        QVector< QVector<T> > result;
        result.resize(l.size());
        for (int i=0; i<l.size(); i++) {
            const QVariantList ll = l[i].toList();
            result[i].resize(ll.size());
            for (int j=0; j<ll.size(); j++) {
                result[i][j] = qvariant_cast<T>(ll[j]);
            }
        }
        return result;
    }
    template <typename T> inline static QVector< QVector< QVector<T> > > toVector3(const QVariant & v)
    {
        const QVariantList l = v.toList();
        QVector< QVector< QVector<T> > > result;
        result.resize(l.size());
        for (int i=0; i<l.size(); i++) {
            const QVariantList ll = l[i].toList();
            result[i].resize(ll.size());
            for (int j=0; j<ll.size(); j++) {
                const QVariantList lll = ll[j].toList();
                result[i][j].resize(lll.size());
                for (int k=0; k<lll.size(); k++) {
                    result[i][j][k] = qvariant_cast<T>(lll[k]);
                }
            }
        }
        return result;
    }
        """

    # noinspection PyPep8Naming
    def constructorImplementation(self):
        """
        Returns implementation of class constructor

        :rtype:     unicode
        :return:    C++ constructor code
        """
        return """
%s::%s()
    : ExtensionSystem::KPlugin()
    , module_(nullptr)
    , asyncRunThread_(nullptr)
    , settingsPage_(nullptr)
{
    //bool hasGuiThread = true;
#ifdef Q_OS_LINUX
    //hasGuiThread = getenv("DISPLAY") != 0;
#endif
    QObject::connect(
        this, SIGNAL(asyncRun(quint32,QVariantList)),
        this, SLOT(asyncEvaluate(quint32,QVariantList)),
        //hasGuiThread? Qt::QueuedConnection :
        Qt::DirectConnection
    );
}
        """ % (self.class_name, self.class_name)

    # noinspection PyPep8Naming
    def createPluginSpecCppImplementation(self):
        actor_name = self._module.name.get_camel_case_cpp_value()
        return """
/* protected */ void %s::createPluginSpec()
{
    _pluginSpec.name = "Actor%s";
    _pluginSpec.gui = isGuiRequired();
}
        """ % (self.class_name, actor_name)

    # noinspection PyPep8Naming
    def isGuiRequiredCppImplementation(self):
        """
        Creates implementation of isGuiRequired

        :rtype:     str
        :return:    implementation of bool isGuiRequired() const
        """
        if self._module.gui:
            guiRequired = "true"
            if self._module.gui.optional:
                guiRequired = "false"
        else:
            guiRequired = "false"
        return """
/* public */ bool %s::isGuiRequired() const
{
    return %s;
}
        """ % (self.class_name, guiRequired)

    # noinspection PyPep8Naming
    def notifyGuiReadyCppImplementation(self):
        if self._module.gui:
            return """
/* public */ void %s::notifyGuiReady()
{
    module_->handleGuiReady();
}
            """ % (self.class_name)
        else:
            return "\n"

    # noinspection PyPep8Naming
    def asciiModuleNameCppImplementation(self):
        return """
/* public */ QByteArray %s::asciiModuleName() const
{
    return QByteArray("%s");
}
        """ % (self.class_name, self._module.name.get_ascii_value().replace("\\", "\\\\"))

    # noinspection PyPep8Naming
    def localizedModuleNameCppImplementation(self):
        """
        Creates implementation of name

        :rtype:     unicode
        :return:    implementation of QString name() const
        """
        # TODO non-Russian language implementation
        return """
/* public */ QString %s::localizedModuleName(const QLocale::Language ) const
{
    // TODO non-Russian languages not implemented yet
    return QString::fromUtf8("%s");
}
        """ % (self.class_name, self._module.name.get_kumir_value().replace("\\", "\\\\"))

    # noinspection PyPep8Naming
    def templateParametersCppImplementation(self):
        return """
/* public */ QVariantList %s::templateParameters() const
{
    if (module_) {
        return module_->templateParameters();
    }
    else {
        return defaultTemplateParameters();
    }
}
        """ % self.class_name

    # noinspection PyPep8Naming
    def defaultTemplateParametersCppImplementation(self):
        body = "QVariantList result;\n"
        for value in self._module.default_template_parameters:
            body += "result.append(QVariant("
            if isinstance(value, unicode):
                body += "QString::fromUtf8(\"" + value + "\")"
            elif isinstance(value, str):
                body += "QString::fromLatin1(\"" + value + "\")"
            else:
                body += str(value)
            body += "));\n"
        body += "return result;"
        return """
/* public */ QVariantList %s::defaultTemplateParameters() const
{
%s
}
        """ % (self.class_name, _add_indent(body))

    # noinspection PyPep8Naming
    def functionListCppImplementation(self):
        methods = self._module.methods
        body = ""
        for method in methods:
            assert isinstance(method, Method)
            body += "\n/* " + method.get_kumir_declaration() + " */\n"
            body += "result.push_back(Shared::ActorInterface::Function());\n"
            body += "result.last().id = result.size() - 1;\n"
            if method.name.get_kumir_value().startswith("@"):
                body += "result.last().accessType = Shared::ActorInterface::TeacherModeFunction;\n"
            else:
                body += "result.last().accessType = Shared::ActorInterface::PublicFunction;\n"
            body += 'result.last().asciiName = QByteArray("%s");\n' % method.name.get_ascii_value().replace("\\",
                                                                                                            "\\\\")
            assert isinstance(method.name.data, dict)
            for key, value in method.name.data.items():
                qlocale = None
                if key == "ru_RU":
                    qlocale = "QLocale::Russian"
                if qlocale:
                    body += 'result.last().localizedNames[%s] = QString::fromUtf8("%s");\n' % (
                        qlocale, value.replace("\\", "\\\\"))
            if method.return_type:
                assert isinstance(method.return_type, BaseType)
                if method.return_type.get_ascii_name() == "int":
                    body += "result.last().returnType = Shared::ActorInterface::Int;\n"
                elif method.return_type.get_ascii_name() == "double":
                    body += "result.last().returnType = Shared::ActorInterface::Real;\n"
                elif method.return_type.get_ascii_name() == "bool":
                    body += "result.last().returnType = Shared::ActorInterface::Bool;\n"
                elif method.return_type.get_ascii_name() == "char":
                    body += "result.last().returnType = Shared::ActorInterface::Char;\n"
                elif method.return_type.get_ascii_name() == "string":
                    body += "result.last().returnType = Shared::ActorInterface::String;\n"
                else:
                    body += "result.last().returnType = Shared::ActorInterface::RecordType;\n"
                    body += method.return_type.get_cpp_record_spec_creation(None,
                                                                            "result.last().returnTypeSpecification")
            else:
                body += "result.last().returnType = Shared::ActorInterface::Void;\n"
            for argument in method.arguments:
                assert isinstance(argument, Argument)
                assert isinstance(argument.base_type, BaseType)
                body += "result.last().arguments.push_back(Shared::ActorInterface::Argument());\n"
                if argument.get_kumir_argument_declaration().startswith(u'аргрез '):
                    body += "result.last().arguments.last().accessType = Shared::ActorInterface::InOutArgument;\n"
                elif argument.get_kumir_argument_declaration().startswith(u'рез '):
                    body += "result.last().arguments.last().accessType = Shared::ActorInterface::OutArgument;\n"
                else:
                    body += "result.last().arguments.last().accessType = Shared::ActorInterface::InArgument;\n"
                if argument.base_type.get_ascii_name() == "int":
                    body += "result.last().arguments.last().type = Shared::ActorInterface::Int;\n"
                elif argument.base_type.get_ascii_name() == "double":
                    body += "result.last().arguments.last().type = Shared::ActorInterface::Real;\n"
                elif argument.base_type.get_ascii_name() == "bool":
                    body += "result.last().arguments.last().type = Shared::ActorInterface::Bool;\n"
                elif argument.base_type.get_ascii_name() == "char":
                    body += "result.last().arguments.last().type = Shared::ActorInterface::Char;\n"
                elif argument.base_type.get_ascii_name() == "string":
                    body += "result.last().arguments.last().type = Shared::ActorInterface::String;\n"
                else:
                    body += "result.last().arguments.last().type = Shared::ActorInterface::RecordType;\n"
                    body += argument.base_type.get_cpp_record_spec_creation(
                            None,
                            "result.last().arguments.last().typeSpecification"
                    )
                body += "result.last().arguments.last().dimension = %du;\n" % argument.dimension

        return """
/* public */ Shared::ActorInterface::FunctionList %s::functionList() const
{
    Shared::ActorInterface::FunctionList result;
%s;
    return result;
}
        """ % (self.class_name, _add_indent(body))

    # noinspection PyPep8Naming
    def typeListCppImplementation(self):
        """
        Creates implementation of typeList

        :rtype:     unicode
        :return:    implementation of TypeList typeList() const
        """
        body = ""
        if self._module.types:
            body += """
        """
            for typee in self._module.types:
                assert isinstance(typee, BaseType)
                if typee.get_module() == self._module:
                    if typee.get_cpp_record_spec_creation("result", None):
                        body += typee.get_cpp_record_spec_creation("result", None)
        return """
/* public */ Shared::ActorInterface::TypeList %s::typeList() const
{
    Shared::ActorInterface::TypeList result;
%s
    return result;
}
        """ % (self.class_name, _add_indent(body))

    # noinspection PyPep8Naming
    def customValueToStringCppImplementation(self):
        """
        Creates implementation of customValueToString

        :rtype:     unicode
        :return:    implementation of QString customValueToString(const CustomType &, const QVariant &) const
        """
        body = ""
        clazz = ""
        value = ""
        for method in self._module.methods:
            assert isinstance(method, Method)
            if method.name.get_ascii_value() == "output":
                clazz = "clazz"
                value = "value"
                assert len(method.arguments) == 1
                argument = method.arguments[0]
                assert isinstance(argument, Argument)
                assert not argument.base_type.is_standard_type()
                if body:
                    body += "    else "
                body += "    if (clazz==QByteArray(\"%s\")) {\n" % argument.base_type.get_ascii_name()
                body += "        %s x = decode(value);\n" % argument.base_type.get_qt_name()
                body += "        result = module_->runOperatorOUTPUT(x);\n    }\n"
        return """
/* public */ QString %s::customValueToString(const QByteArray & %s, const QVariant & %s) const
{
    QString result;
%s
    return result;
}
        """ % (self.class_name, clazz, value, body)

    # noinspection PyPep8Naming
    def customValueFromStringCppImplementation(self):
        """
        Creates implementation of customValueFromString

        :rtype:     unicode
        :return:    implementation of QVariant customValueFromString(const CustomType &, const QString &) const
        """
        body = ""
        clazz = ""
        stringg = ""
        for method in self._module.methods:
            assert isinstance(method, Method)
            if method.name.get_ascii_value() == "input":
                clazz = "clazz"
                stringg = "stringg"
                return_type = method.return_type
                assert isinstance(return_type, BaseType)
                assert len(method.arguments) == 2
                argument = method.arguments[0]
                assert isinstance(argument, Argument)
                assert argument.base_type.get_qt_name() == "QString"
                if body:
                    body += "    else "
                body += "    if (clazz==QByteArray(\"%s\")) {\n" % return_type.get_ascii_name()
                body += "        %s x; bool ok = false;\n" % return_type.get_qt_name()
                body += "        x = module_->runOperatorINPUT(stringg, ok);\n"
                body += "        if (ok) {\n"
                body += "            result = encode(x);\n"
                body += "        }\n"
                body += "    }\n"
        return """
/* public */ QVariant %s::customValueFromString(const QByteArray & %s, const QString & %s) const
{
    QVariant result;
%s
    return result;
}
        """ % (self.class_name, clazz, stringg, body)

    # noinspection PyPep8Naming
    def mainIconNameCppImplementation(self):
        """
        Creates mainIconName C++ implementation

        :rtype:     str
        :return:    implementation of QString mainIconName() const
        """
        icon_name = ""
        if self._module.gui:
            gui = self._module.gui
            assert isinstance(gui, Gui)
            icon_name = gui.get_icon_name("main")
        return """
/* public */ QString %s::mainIconName() const
{
    return QString::fromLatin1(\"%s\");
}
        """ % (self.class_name, icon_name)

    # noinspection PyPep8Naming
    def pultIconNameCppImplementation(self):
        """
        Creates pultIconName C++ implementation

        :rtype:     str
        :return:    implementation of QString pultIconName() const
        """
        icon_name = ""
        if self._module.gui:
            gui = self._module.gui
            assert isinstance(gui, Gui)
            icon_name = gui.get_icon_name("pult")
        return """
/* public */ QString %s::pultIconName() const
{
    return QString::fromLatin1(\"%s\");
}
        """ % (self.class_name, icon_name)

    # noinspection PyPep8Naming
    def mainWidgetCppImplementation(self):
        """
        Creates mainWidget C++ implementation

        :rtype:     str
        :return:    implementation of QWidget* mainWidget()
        """
        return """
/* public */ QWidget* %s::mainWidget()
{
    return module_->mainWidget();
}
        """ % self.class_name

    # noinspection PyPep8Naming
    def pultWidgetCppImplementation(self):
        """
        Creates pultWidget C++ implementation

        :rtype:     str
        :return:    implementation of QWidget* pultWidget()
        """
        return """
/* public */ QWidget* %s::pultWidget()
{
    return module_->pultWidget();
}
        """ % self.class_name

    # noinspection PyPep8Naming
    def moduleMenusCppImplementation(self):
        """
        Creates moduleMenus C++ implementation

        :rtype:     str
        :return:    implementation of QList<QMeny*> moduleMenus() const
        """
        return """
/* public */ QList<QMenu*> %s::moduleMenus() const
{
    return module_->moduleMenus();
}
        """ % self.class_name

    # noinspection PyPep8Naming
    def settingsEditorPageCppImplementation(self):
        """
        Creates settingsEditorPage C++ implementation

        :rtype:     str
        :return:    implementation of QWidget* settingsEditorPage();
        """
        return """
/* public */ QWidget* %s::settingsEditorPage()
{
    return settingsPage_;
}
        """ % self.class_name

    # noinspection PyPep8Naming
    def resetCppImplementation(self):
        """
        Creates reset C++ implementation

        :rtype:     str
        :return:    implementation of void reset();
        """
        return """
/* public */ void %s::reset()
{
    module_->reset();
}
        """ % self.class_name

        # noinspection PyPep8Naming

    def terminateEvaluationCppImplementation(self):
        """
        Creates reset C++ implementation

        :rtype:     str
        :return:    implementation of void terminateEvaluation();
        """
        return """
/* public */ void %s::terminateEvaluation()
{
    module_->terminateEvaluation();
}
        """ % self.class_name

    # noinspection PyPep8Naming
    def setAnimationEnabledCppImplementation(self):
        """
        Creates setAnimationEnabled C++ implementation

        :rtype:     str
        :return:    implementation of void setAnimationEnabled(bool)
        """
        return """
/* public */ void %s::setAnimationEnabled(bool enabled)
{
    // The module might be not created at a time of call,
    // so check it propertly
    if (module_) {
        module_->setAnimationEnabled(enabled);
    }
}
        """ % self.class_name

    # noinspection PyPep8Naming
    def evaluateCppImplementation(self):
        """
        Creates evaluate C++ implementation

        :rtype:     unicode
        :return:    implementation of EvaluationStatus evaluate(quint32, const QVariantList&)
        """
        switch_body = ""
        for method in self._module.methods:
            assert isinstance(method, Method)
            method_index = self._module.methods.index(method)
            switch_body += "case 0x%04x: {\n" % method_index
            switch_body += "    /* %s */\n" % method.name.get_ascii_value()
            if method.async_:
                switch_body += "    Q_EMIT asyncRun(index, args);\n"
                switch_body += "    return ES_Async;\n"
            else:
                args = []
                for index, argument in enumerate(method.arguments):
                    assert isinstance(argument, Argument)
                    switch_body += "    %s = " % argument.get_cpp_local_variable_declaration()
                    if argument.dimension > 0:
                        switch_body += "toVector%d<%s>(args[%d])" % (
                            argument.dimension,
                            argument.base_type.get_qt_name(),
                            index
                        )
                    elif argument.base_type.get_qt_name() in ["int", "qreal", "bool", "QString", "QChar"]:
                        switch_body += "qvariant_cast<%s>(args[%d])" % (argument.base_type.get_qt_name(), index)
                    else:
                        switch_body += "decode(args[%d])" % index
                    switch_body += ";\n"
                    args += [argument.name.get_cpp_value()]
                if method.return_type:
                    return_type = method.return_type
                    assert isinstance(return_type, BaseType)
                    switch_body += "    result_ = "
                    if return_type.get_qt_name() in ["int", "qreal", "bool", "QString", "QChar"]:
                        switch_body += "QVariant::fromValue("
                    else:
                        switch_body += "encode("
                else:
                    switch_body += "    "
                # noinspection PyUnresolvedReferences
                switch_body += "module_->run%s(%s)" % (method.name.get_camel_case_cpp_value(), string.join(args, ", "))
                if method.return_type:
                    switch_body += ");\n"
                else:
                    switch_body += ";\n"
                returns_any_argument = False
                for argument in method.arguments:
                    assert isinstance(argument, Argument)
                    switch_body += "    optResults_ << "
                    if argument.reference and not argument.constant:
                        returns_any_argument = True
                        plain_type = argument.base_type.get_qt_name() in ["int", "qreal", "bool", "QString", "QChar"]
                        if plain_type or argument.dimension > 0:
                            switch_body += "QVariant::fromValue(%s);\n" % argument.name.get_cpp_value()
                        else:
                            switch_body += "encode(%s);\n" % argument.name.get_cpp_value()
                    else:
                        switch_body += "QVariant::Invalid;\n"
                switch_body += "    if (errorText_.length() > 0) {\n"
                switch_body += "        return ES_Error;\n"
                switch_body += "    }\n"
                if returns_any_argument and method.return_type:
                    switch_body += "    return ES_StackRezResult;\n"
                elif returns_any_argument:
                    switch_body += "    return ES_RezResult;\n"
                elif method.return_type:
                    switch_body += "    return ES_StackResult;\n"
                else:
                    switch_body += "    return ES_NoResult;\n"
            switch_body += "    break;\n"
            switch_body += "}\n\n"
        return """
/* public */ Shared::EvaluationStatus %s::evaluate(quint32 index, const QVariantList & args)
{
    using namespace Shared;
    errorText_.clear();
    result_ = QVariant::Invalid;
    optResults_.clear();
    switch (index) {
%s
        default : {
            errorText_ = "Unknown method index";
            return ES_Error;
        }
    }
}
        """ % (self.class_name, _add_indent(_add_indent(switch_body)))

    # noinspection PyPep8Naming
    def asyncEvaluateCppImplementation(self):
        """
        Creates evaluate C++ implementation

        :rtype:     unicode
        :return:    implementation of void asyncEvaluate(quint32, const QVariantList&)
        """
        switch_body = ""
        for method in self._module.methods:
            assert isinstance(method, Method)
            method_index = self._module.methods.index(method)
            if method.async_:
                switch_body += "case 0x%04x: {\n" % method_index
                switch_body += "    /* %s */\n" % method.name.get_ascii_value()
                args = []
                for index, argument in enumerate(method.arguments):
                    assert isinstance(argument, Argument)
                    switch_body += "    %s = " % argument.get_cpp_local_variable_declaration()
                    if argument.dimension > 0:
                        switch_body += "toVector%d<%s>(args[%d])" % (
                            argument.dimension,
                            argument.base_type.get_qt_name(),
                            index
                        )
                    elif argument.base_type.get_qt_name() in ["int", "qreal", "bool", "QString", "QChar"]:
                        switch_body += "qvariant_cast<%s>(args[%d])" % (argument.base_type.get_qt_name(), index)
                    else:
                        switch_body += "decode(args[%d])" % index
                    switch_body += ";\n"
                    args += [argument.name.get_cpp_value()]
                if method.return_type:
                    return_type = method.return_type
                    assert isinstance(return_type, BaseType)
                    switch_body += "    result_ = "
                    if return_type.get_qt_name() in ["int", "qreal", "bool", "QString", "QChar"]:
                        switch_body += "QVariant::fromValue("
                    else:
                        switch_body += "encode("
                else:
                    switch_body += "    "
                # noinspection PyUnresolvedReferences
                switch_body += "module_->run%s(%s)" % (method.name.get_camel_case_cpp_value(), string.join(args, ", "))
                if method.return_type:
                    switch_body += ");\n"
                else:
                    switch_body += ";\n"
                for argument in method.arguments:
                    assert isinstance(argument, Argument)
                    switch_body += "    optResults_ << "
                    if argument.reference and not argument.constant:
                        plain_type = argument.base_type.get_qt_name() in ["int", "qreal", "bool", "QString", "QChar"]
                        if plain_type or argument.dimension > 0:
                            switch_body += "QVariant::fromValue(%s);\n" % argument.name.get_cpp_value()
                        else:
                            switch_body += "encode(%s);\n" % argument.name.get_cpp_value()
                    else:
                        switch_body += "QVariant::Invalid;\n"
                switch_body += "    break;\n"
                switch_body += "}\n\n"

        return """
/* private slot */ void %s::asyncEvaluate(quint32 index, const QVariantList & args)
{
    using namespace Shared;
    errorText_.clear();
    result_ = QVariant::Invalid;
    optResults_.clear();
    switch (index) {
%s
        default : {
            errorText_ = "Unknown method index for async evaluation";
        }
    }
    Q_EMIT sync();
}
        """ % (self.class_name, _add_indent(_add_indent(switch_body)))

    # noinspection PyPep8Naming
    def resultCppImplementation(self):
        """
        Creates result C++ implementation

        :rtype:     str
        :return:    implementation of QVariant result() const
        """
        return """
/* public */ QVariant %s::result() const
{
    return result_;
}
        """ % self.class_name

    # noinspection PyPep8Naming
    def algOptResultsCppImplementation(self):
        """
        Creates algOptResult C++ implementation

        :rtype:     str
        :return:    implementation of QVariantList algOptResults() const
        """
        return """
/* public */ QVariantList %s::algOptResults() const
{
    return optResults_;
}
        """ % self.class_name

    # noinspection PyPep8Naming
    def errorTextCppImplementation(self):
        """
        Creates errorText C++ implementation

        :rtype:     str
        :return:    implementation of QString errorText() const
        """
        return """
/* public */ QString %s::errorText() const
{
    return errorText_;
}
        """ % self.class_name

    # noinspection PyPep8Naming
    def connectSyncCppImplementation(self):
        """
        Creates connectSync C++ implementation

        :rtype:     str
        :return:    implementation of void connectSync(QObject*, const char*)
        """
        return """
/* public */ void %s::connectSync(QObject* receiver, const char* method)
{
    QObject::connect(this, SIGNAL(sync()), receiver, method, Qt::DirectConnection);
}
        """ % self.class_name

    # noinspection PyPep8Naming
    def initializeCppImplementation(self):
        """
        Creates initialize C++ implementation

        :rtype:     str
        :return:    implementation of QString initialize(const QStringList&)
        """
        body = "module_ = new %s(this);\n" % self._module.get_module_cpp_class_name()
        methods = self._module.methods
        async_methods = list(filter(lambda method: method.async_, methods))
        if self._module.settings:
            body += self._module.settings.get_settings_page_creation("settingsPage_").strip() + "\n\n"
        if async_methods:
            body += "asyncRunThread_ = new %s(this, module_);\n" % self._module.get_run_thread_cpp_class_name()
            body += "QObject::connect(asyncRunThread_, SIGNAL(finished()),\n"
            body += "                 this, SIGNAL(sync()));\n"
            body += "QObject::connect(module_, SIGNAL(notifyOnTemplateParametersChanged()),\n"
            body += "                 this, SIGNAL(notifyOnTemplateParametersChanged()));\n"
        return """
/* protected */ QString %s::initialize(const QStringList &a, const ExtensionSystem::CommandLine &b)
{
%s
    return module_->initialize(a, b);
}
        """ % (self.class_name, _add_indent(body))

    # noinspection PyPep8Naming
    def isSafeToQuitCppImplementation(self):
        return """
/* public */ bool %s::isSafeToQuit()
{
    return module_->isSafeToQuit();
}
        """ % self.class_name

    # noinspection PyPep8Naming
    def sleepCppImplementation(self):
        """
        Creates sleep implementation

        :rtype:     str
        :return:    implementation of void sleep(unsigned long)
        """
        return """
/* protected */ void %s::sleep(unsigned long secs)
{
    if (QThread::currentThread()==asyncRunThread_) {
        asyncRunThread_->asleep(secs);
    }
}
        """ % self.class_name

    # noinspection PyPep8Naming
    def msleepCppImplementation(self):
        """
        Creates msleep implementation

        :rtype:     str
        :return:    implementation of void msleep(unsigned long)
        """
        return """
/* protected */ void %s::msleep(unsigned long secs)
{
    //if (QThread::currentThread()==asyncRunThread_) {
        asyncRunThread_->amsleep(secs);
    //}
}
        """ % self.class_name

    # noinspection PyPep8Naming
    def usleepCppImplementation(self):
        """
        Creates usleep implementation

        :rtype:     str
        :return:    implementation of void usleep(unsigned long)
        """
        return """
/* protected */ void %s::usleep(unsigned long secs)
{
    if (QThread::currentThread()==asyncRunThread_) {
        asyncRunThread_->ausleep(secs);
    }
}
        """ % self.class_name

    # noinspection PyPep8Naming
    def changeGlobalStateCppImplementation(self):
        """
        Creates changeGlobalState implementation

        :rtype:     str
        :return:    implementation of void changeGlobalState(GlobalState, GlobalState)
        """
        return """
/* protected */ void %s::changeGlobalState(ExtensionSystem::GlobalState old, ExtensionSystem::GlobalState current)
{
    module_->changeGlobalState(old, current);
}
        """ % self.class_name

    # noinspection PyPep8Naming
    def handleSettingsChangedCppImplementation(self):
        """
        Creates handleSettingsChanged implementation

        :rtype:     str
        :return:    implementation of void handleSettingsChanged()
        """
        return """
/* private slot */ void %s::handleSettingsChangedCppImplementation(const QStringList & keys)
{
    if (module_) {
        module_->reloadSettings(mySettings(), keys);
    }
}
        """ % self.class_name

    # noinspection PyPep8Naming
    def updateSettingsCppImplementation(self):
        """
        Creates updateSettings implementation

        :rtype:     str
        :return:    implementation of void updateSettings()
        """
        return """
/* private */ void %s::updateSettings(const QStringList & keys)
{
    if (settingsPage_) {
        settingsPage_->setSettingsObject(mySettings());
    }
    if (module_) {
        module_->reloadSettings(mySettings(), keys);
    }
}
        """ % self.class_name

    # noinspection PyPep8Naming
    def loadActorDataCppImplementation(self):
        """
        Creates loadActorData implementation

        :rtype:     str
        :return:    implementation of void loadActorData(QIODevice *)
        """
        return """
/* public slot */ void %s::loadActorData(QIODevice * source)
{
    if (module_) {
        module_->loadActorData(source);
    }
}
        """ % self.class_name

    # noinspection PyPep8Naming
    def acceptableCommandLineParametersCppImplementation(self):
        """
        Implementation of ExtensionSystem::KPlugin::acceptableCommandLineParameters()

        :rtype:     str
        :return:    implementation text
        """
        return """
/* protected */ QList<ExtensionSystem::CommandLineParameter> %s::acceptableCommandLineParameters() const
{
    return %s::acceptableCommandLineParameters();
}
        """ % (self.class_name, self._module.get_module_cpp_class_name())

    # noinspection PyPep8Naming
    def usesListCppImplementation(self):
        if not self._module.uses_list:
            return """
/* public */ QList<Shared::ActorInterface*> %s::usesList() const
{
    static const QList<Shared::ActorInterface*> empty = QList<Shared::ActorInterface*>();
    return empty;
}
            """ % self.class_name
        else:
            plugins_list = map(lambda s: "\"Actor" + s + "\"", self._module.uses_list)
            # noinspection PyUnresolvedReferences
            return """
/* public */ QList<Shared::ActorInterface*> %s::usesList() const
{
    static const QList<QByteArray> usesNames = QList<QByteArray>()
        << %s ;
    QList<Shared::ActorInterface*> result;
    Q_FOREACH (const QByteArray & name, usesNames) {
        ExtensionSystem::KPlugin * plugin = myDependency(name);
        Shared::ActorInterface * actor =
                qobject_cast<Shared::ActorInterface*>(plugin);
        result.push_back(actor);
    }
    return result;
}
            """ % (self.class_name, string.join(plugins_list, " << "))


class AsyncThreadCppClass(CppClassBase):
    """
    Class for asynchronous method evaluation in separate thread
    """

    def __init__(self, module):
        """
        Initializes from actor module tree
        """
        CppClassBase.__init__(self)
        assert isinstance(module, Module)
        self._module = module
        self.class_name = module.get_run_thread_cpp_class_name()
        self.fields = [
            "quint32 index_",
            "QVariantList args_",
            "class %s* plugin_" % module.get_plugin_cpp_class_name(),
            "class %s* module_" % module.get_base_cpp_class_name()
        ]
        self.base_classes = ["QThread"]

    # noinspection PyPep8Naming
    def constructorImplementation(self):
        """
        Creates C++ constructor implementation

        :rtype:     str
        :return:    C++ code for constructor implementation
        """
        return """
%s::%s(class %s* plugin, class %s* module)
    : QThread(plugin)
    , index_(0)
    , args_(QVariantList())
    , plugin_(plugin)
    , module_(module)
{
}
        """ % (self.class_name, self.class_name, self._module.get_plugin_cpp_class_name(),
               self._module.get_base_cpp_class_name())

    # noinspection PyPep8Naming
    def initCppImplementation(self):
        """
        Creates init implementation

        :rtype:     str
        :return:    implementation of void init(quint32, const QVariantList &)
        """
        return """
/* public */ void %s::init(quint32 index, const QVariantList & args)
{
    index_ = index;
    args_ = args;
}
        """ % self.class_name

    # noinspection PyPep8Naming
    def asleepCppImplementation(self):
        """
        Creates asleep implementation

        :rtype:     str
        :return:    implementation of void asleep(unsigned long)
        """
        return """
/* public */ void %s::asleep(unsigned long secs)
{
    sleep(secs);
}
        """ % self.class_name

    # noinspection PyPep8Naming
    def amsleepCppImplementation(self):
        """
        Creates amsleep implementation

        :rtype:     str
        :return:    implementation of void amsleep(unsigned long)
        """
        return """
/* public */ void %s::amsleep(unsigned long secs)
{
    msleep(secs);
}
        """ % self.class_name

    # noinspection PyPep8Naming
    def ausleepCppImplementation(self):
        """
        Creates ausleep implementation

        :rtype:     str
        :return:    implementation of void ausleep(unsigned long)
        """
        return """
/* public */ void %s::ausleep(unsigned long secs)
{
    usleep(secs);
}
        """ % self.class_name

    # noinspection PyPep8Naming
    def runCppImplementation(self):
        """
        Creates thread run implementation

        :rtype:     unicode
        :return:    implementation of void run()
        """
        switch_body = ""
        methods = filter(lambda module_method: module_method.async_, self._module.methods)
        for method in methods:
            assert isinstance(method, Method)
            method_index = self._module.methods.index(method)
            switch_body += "case 0x%04x: {\n" % method_index
            switch_body += "    /* %s */\n" % method.name.get_ascii_value()
            args = []
            for index, argument in enumerate(method.arguments):
                assert isinstance(argument, Argument)
                args += [argument.name.get_cpp_value()]
                switch_body += "    %s = " % argument.get_cpp_local_variable_declaration()
                if argument.dimension > 0:
                    switch_body += "toVector%d<%s>(args[%d])" % (
                        argument.dimension,
                        argument.base_type.get_qt_name(),
                        index
                    )
                elif argument.base_type.get_qt_name() in ["int", "qreal", "bool", "QString", "QChar"]:
                    switch_body += "qvariant_cast<%s>(args_[%d])" % (argument.base_type.get_qt_name(), index)
                else:
                    switch_body += "decode(args_[%d])" % index
                switch_body += ";\n"
            if method.return_type:
                return_type = method.return_type
                assert isinstance(return_type, BaseType)
                switch_body += "    plugin_->result_ = "
                if return_type.get_qt_name() in ["int", "qreal", "bool", "QString", "QChar"]:
                    switch_body += "QVariant::fromValue("
                else:
                    switch_body += "encode("
            else:
                switch_body += "    "
            # noinspection PyUnresolvedReferences
            switch_body += "module_->run%s(%s)" % (method.name.get_camel_case_cpp_value(), string.join(args, ", "))
            if method.return_type:
                switch_body += ");\n"
            else:
                switch_body += ";\n"
            for argument in method.arguments:
                assert isinstance(argument, Argument)
                switch_body += "    plugin_->optResults_ << "
                if argument.reference and not argument.constant:
                    plain_type = argument.base_type.get_qt_name() in ["int", "qreal", "bool", "QString", "QChar"]
                    if plain_type or argument.dimension > 0:
                        switch_body += "QVariant::fromValue(%s);\n" % argument.name.get_cpp_value()
                    else:
                        switch_body += "encode(%s);\n" % argument.name.get_cpp_value()
                else:
                    switch_body += "QVariant::Invalid;\n"
            switch_body += "    break;\n"
            switch_body += "}\n"
        return """
/* private */ void %s::run()
{
    switch (index_) {
%s
        default: {
            plugin_->errorText_ = "Unknown method index";
        }
    }
}
        """ % (self.class_name, _add_indent(_add_indent(switch_body)))


class ModuleBaseCppClass(CppClassBase):
    """
    C++ base class for module implementation
    """

    def __init__(self, module):
        """
        Initializes from actor module tree
        """
        assert isinstance(module, Module)
        CppClassBase.__init__(self)
        self.base_classes = ["QObject"]
        self._module = module
        self.class_name = module.get_base_cpp_class_name()
        self.class_declaration_prefix = "    Q_OBJECT"
        self.signals = ["void notifyOnTemplateParametersChanged()"]
        self.abstract_public_slots = [
            "void reset()",
            "void reloadSettings(ExtensionSystem::SettingsPtr settings, const QStringList & keys)",
            "void changeGlobalState(ExtensionSystem::GlobalState old, ExtensionSystem::GlobalState current)",
            "void terminateEvaluation()"
        ]
        if module.gui:
            self.abstract_public_methods += [
                "QWidget* mainWidget() const",
                "QWidget* pultWidget() const",
            ]
            self.abstract_public_slots += [
                "void setAnimationEnabled(bool enabled)"
            ]
        for method in module.methods:
            assert isinstance(method, Method)
            self.abstract_public_slots += [method.get_cpp_declaration()]
        if module.gui:
            for menu in module.gui.menus:
                self.fields += self._add_menu_actions_as_fields(menu)

    def _add_menu_actions_as_fields(self, menu, parent_prefix="", toplevel=True):
        """
        Walks on menu item tree and yields menu items as QMenu* or QAction* fields

        :type   menu:           MenuItem
        :param  menu:           current top level menu item
        :type   parent_prefix:  str
        :param  parent_prefix:  parent prefix name constructed from top level items
        :type   toplevel:       bool
        :param  toplevel:       True if menu item is a top level
        :rtype:                 list
        :return:                list of field declarations
        """
        assert isinstance(menu, MenuItem)
        result = []
        if toplevel:
            root = "QMenu* m_menu"
        else:
            root = "QAction* m_action"
        root += parent_prefix + menu.title.get_camel_case_cpp_value()
        result += [root]
        for child in menu.items:
            result += self._add_menu_actions_as_fields(child, parent_prefix + menu.title.get_camel_case_cpp_value(),
                                                       False)
        return result

    def _add_menu_actions_creation(self, menu, parent_prefix="", deep=0):
        """
        Walks on menu item tree and yields menu items creation code

        :type   menu:           MenuItem
        :param  menu:           current top level menu item
        :type   parent_prefix:  str
        :param  parent_prefix:  parent prefix name constructed from top level items
        :type   deep:           int
        :param  deep:           the deep of current menu item tree
        :rtype:                 unicode
        :return:                C++ code for menu items creation
        """
        assert isinstance(menu, MenuItem)
        result = ""
        if deep == 0:
            result += "m_menu%s = new QMenu();\n" % menu.title.get_camel_case_cpp_value()
            set_title = "m_menu%s->setTitle" % menu.title.get_camel_case_cpp_value()
        else:
            assert parent_prefix
            if deep == 1:
                parent = "m_menu" + parent_prefix
            else:
                parent = "m_action%s->menu()" % parent_prefix
            result += "m_action%s = %s->addAction(\"\");\n" % (
                parent_prefix + menu.title.get_camel_case_cpp_value(),
                parent
            )
            set_title = "m_action%s->setText" % (parent_prefix + menu.title.get_camel_case_cpp_value())
        ascii_name = ""
        other_names = {}
        for key, value in menu.title.data.items():
            assert isinstance(key, str) or isinstance(key, unicode)
            assert isinstance(value, str) or isinstance(value, unicode)
            if key == "ascii":
                ascii_name = value
            else:
                other_names[key] = value
        assert ascii_name
        for index, (key, value) in enumerate(other_names.items()):
            if index > 0:
                result += "else "
            result += "if (currentLocaleName==\"%s\") {\n" % key
            result += "    %s(QString::fromUtf8(\"%s\"));\n" % (set_title, value)
            result += "}\n"
        default = "%s(QString::fromLatin1(\"%s\"));\n" % (set_title, ascii_name)
        if other_names:
            result += "else {\n    %s}\n" % default
        else:
            result += default
        result += "\n"
        if deep > 0 and menu.items:
            result += "m_action%s->setMenu(new QMenu);\n" % (parent_prefix + menu.title.get_camel_case_cpp_value())
            result += "m_action%s->menu()->setTitle(m_action%s->text());\n" % (
                parent_prefix + menu.title.get_camel_case_cpp_value(),
                parent_prefix + menu.title.get_camel_case_cpp_value()
            )
        for child in menu.items:
            result += self._add_menu_actions_creation(child,
                                                      parent_prefix + menu.title.get_camel_case_cpp_value(),
                                                      deep + 1)
        return result

    # noinspection PyPep8Naming
    def constructorImplementation(self):
        """
        Creates C++ constructor implementation

        :rtype:     unicode
        :return:    implementation of C++ constructor
        """
        body = ""
        if self._module.gui:
            body += "static const QString currentLocaleName = QLocale().name();\n\n"
            for menu in self._module.gui.menus:
                body += self._add_menu_actions_creation(menu)

        return """
%s::%s(ExtensionSystem::KPlugin* parent)
    : QObject(parent)
{
    bool hasGui = (qobject_cast<QApplication*>(qApp) != 0);
#ifdef Q_OS_LINUX
    //hasGui = getenv("DISPLAY")!=0;
#endif
    if (hasGui) {
%s
    }
}
        """ % (self.class_name, self.class_name, _add_indent(_add_indent(body)))

    # noinspection PyPep8Naming
    def setAnimationEnabledCppImplementation(self):
        """
        Creates setAnimationEnabled implementation or returns an empty string for
        pure virtual method, if there is an implementation in derived class

        :rtype:     str
        :return:    implementation of void setAnimationEnabled(bool) or an empty string
        """
        if self._module.gui:
            return ""  # no implementation, just pure virtual method
        else:
            return """
/* public virtual slot */ void %s::setAnimationEnabled(bool enabled)
{
    Q_UNUSED(enabled);
}
        """ % self.class_name

    # noinspection PyPep8Naming
    def mainWidgetCppImplementation(self):
        """
        Creates mainWidget implementation or returns an empty string for
        pure virtual method, if there is an implementation in derived class

        :rtype:     str
        :return:    implementation of QWidget* mainWidget() or an empty string
        """
        if self._module.gui:
            return ""  # no implementation, just pure virtual method
        else:
            return """
/* public */ QWidget* %s::mainWidget() const
{
    return nullptr;
}
            """ % self.class_name

    # noinspection PyPep8Naming
    def pultWidgetCppImplementation(self):
        """
        Creates pultWidget implementation or returns an empty string for
        pure virtual method, if there is an implementation in derived class

        :rtype:     str
        :return:    implementation of QWidget* pultWidget() or an empty string
        """
        if self._module.gui:
            return ""  # no implementation, just pure virtual method
        else:
            return """
/* public */ QWidget* %s::pultWidget() const
{
    return nullptr;
}
            """ % self.class_name

    # noinspection PyPep8Naming
    def handleGuiReadyCppImplementation(self):
        return """
/* public virtual */ void %s::handleGuiReady()
{
}
        """ % self.class_name

    # noinspection PyPep8Naming
    def moduleMenusCppImplementation(self):
        """
        Creates moduleMenus implementation

        :rtype:     str
        :return:    implementation of QList<QMenu*> moduleMenus() const
        """
        body = ""
        if self._module.gui:
            for menu in self._module.gui.menus:
                assert isinstance(menu, MenuItem)
                body += "result.push_back(m_menu%s);\n" % menu.title.get_camel_case_cpp_value()
        return """
/* public */ QList<QMenu*> %s::moduleMenus() const
{
    bool hasGui = (qobject_cast<QApplication*>(qApp) != 0);
#ifdef Q_OS_LINUX
    //hasGui = getenv("DISPLAY")!=0;
#endif
    if (hasGui) {
        QList<QMenu*> result;
%s
        return result;
    }
    else {
        return QList<QMenu*>();
    }
}
        """ % (self.class_name, _add_indent(_add_indent(body)))

    # noinspection PyPep8Naming
    def setErrorCppImplementation(self):
        """
        Creates setError implementation

        :rtype:     str
        :return:    implementation of void setError(const QString &)
        """
        return """
/* protected */ void %s::setError(const QString & errorText)
{
    %s* plugin = qobject_cast<%s*>(parent());
    plugin->errorText_ = errorText;
}
        """ % (self.class_name, self._module.get_plugin_cpp_class_name(), self._module.get_plugin_cpp_class_name())

    # noinspection PyPep8Naming
    def mySettingsCppImplementation(self):
        """
        Creates mySettings implementation

        :rtype:     str
        :return:    implementation of ExtensionSystem::SettingsPtr mySettings() const
        """
        return """
/* public */ ExtensionSystem::SettingsPtr %s::mySettings() const
{
    %s* plugin = qobject_cast<%s*>(parent());
    return plugin->mySettings();
}
        """ % (self.class_name, self._module.get_plugin_cpp_class_name(), self._module.get_plugin_cpp_class_name())

    # noinspection PyPep8Naming
    def myResourcesDirCppImplementation(self):
        return """
/* public */ QDir %s::myResourcesDir() const
{
    %s* plugin = qobject_cast<%s*>(parent());
    return plugin->myResourcesDir();
}
        """ % (self.class_name, self._module.get_plugin_cpp_class_name(), self._module.get_plugin_cpp_class_name())

    # noinspection PyPep8Naming
    def sleepCppImplementation(self):
        """
        Creates sleep implementation

        :rtype:     str
        :return:    implementation of void sleep(unsigned long)
        """
        return """
/* protected */ void %s::sleep(unsigned long secs)
{
    %s* plugin = qobject_cast<%s*>(parent());
    plugin->sleep(secs);
}
        """ % (self.class_name, self._module.get_plugin_cpp_class_name(), self._module.get_plugin_cpp_class_name())

    # noinspection PyPep8Naming
    def msleepCppImplementation(self):
        """
        Creates msleep implementation

        :rtype:     str
        :return:    implementation of void msleep(unsigned long)
        """
        return """
/* protected */ void %s::msleep(unsigned long msecs)
{
    %s* plugin = qobject_cast<%s*>(parent());
    plugin->msleep(msecs);
}
        """ % (self.class_name, self._module.get_plugin_cpp_class_name(), self._module.get_plugin_cpp_class_name())

    # noinspection PyPep8Naming
    def usleepCppImplementation(self):
        """
        Creates usleep implementation

        :rtype:     str
        :return:    implementation of void usleep(unsigned long)
        """
        return """
/* protected */ void %s::usleep(unsigned long usecs)
{
    %s* plugin = qobject_cast<%s*>(parent());
    plugin->usleep(usecs);
}
        """ % (self.class_name, self._module.get_plugin_cpp_class_name(), self._module.get_plugin_cpp_class_name())

    # noinspection PyPep8Naming
    def loadActorDataCppImplementation(self):
        """
        Creates loadActorData implementation

        :rtype:     str
        :return:    implementation of void loadActorData(QIODevice *)
        """
        return """
/* public virtual slot */ void %s::loadActorData(QIODevice * source)
{
    Q_UNUSED(source);  // By default do nothing

}
        """ % self.class_name

    # noinspection PyPep8Naming
    def commandLineParametersCppImplementation(self):
        """
        Get command line parameters passed to an actor

        :rtype:     str
        :return:    implementation of const CommandLine& commandLineParameters() const
        """
        return """
/* protected */ const ExtensionSystem::CommandLine& %s::commandLineParameters() const
{
    %s * plugin = qobject_cast<%s*>(parent());
    return plugin->commandLineParameters_;
}
        """ % (self.class_name, self._module.get_plugin_cpp_class_name(), self._module.get_plugin_cpp_class_name())

    # noinspection PyPep8Naming
    def templateParametersCppImplementation(self):
        return """
/* public virtual */ QVariantList %s::templateParameters() const
{
    %s * plugin = qobject_cast<%s*>(parent());
    return plugin->defaultTemplateParameters();
}
        """ % (self.class_name, self._module.get_plugin_cpp_class_name(), self._module.get_plugin_cpp_class_name())

    # noinspection PyPep8Naming
    def initializeCppImplementation(self):
        """
        Pass initialization to module itself

        :rtype:     str
        :return:    default implementaion (does nothing)
        """
        return "/* public virtual */ QString %s::initialize" % self.class_name + \
               """(const QStringList &configurationParameters, const ExtensionSystem::CommandLine & runtimeParameters)
{
    Q_UNUSED(configurationParameters);
    Q_UNUSED(runtimeParameters);

    // Return error text or an empty string on successfull  initialization
    return QString();
}
"""

    # noinspection PyPep8Naming
    def isSafeToQuitCppImplementation(self):
        return """
/* public virtual */ bool %s::isSafeToQuit()
{
    return true;
}
        """ % self.class_name


class ModuleCppClass(CppClassBase):
    """
    Actor module implementation C++ derived class
    """

    def __init__(self, module):
        """
        Initialized from actor module tree
        """
        assert isinstance(module, Module)
        self._module = module
        CppClassBase.__init__(self)
        self.base_classes = [module.get_base_cpp_class_name()]
        self.class_name = module.get_module_cpp_class_name()
        self.class_declaration_prefix = "    Q_OBJECT"
        for method in self._module.methods:
            stub = method.get_cpp_implementation_stub(self.class_name)
            self.extra_implementations.append(stub)

    # noinspection PyPep8Naming
    def constructorImplementation(self):
        """
        Creates C++ constructor implementation stub

        :rtype:     str
        :return:    implementation of C++ constructor
        """
        return """
%s::%s(ExtensionSystem::KPlugin * parent)
    : %s(parent)
{
    // Module constructor, called once on plugin load
    // TODO implement me
}
        """ % (self.class_name, self.class_name, self._module.get_base_cpp_class_name())

    # noinspection PyPep8Naming
    def mainWidgetCppImplementation(self):
        """
        Creates mainWidget implementation stub or en empty string if derived class has no implementation

        :rtype:     str
        :return:    implementation of QWidget* mainWidget() const or an empty string
        """
        if not self._module.gui:
            return ""
        else:
            return """
/* public */ QWidget* %s::mainWidget() const
{
    // Returns module main view widget, or nullptr if there is no any views
    // NOTE: the method is const and might be called at any time,
    //       so DO NOT create widget here, just return!
    // TODO implement me
    return nullptr;
}
            """ % self.class_name

    # noinspection PyPep8Naming
    def pultWidgetCppImplementation(self):
        """
        Creates pultWidget implementation stub or en empty string if derived class has no implementation

        :rtype:     str
        :return:    implementation of QWidget* pultWidget() const or an empty string
        """
        if not self._module.gui:
            return ""
        else:
            return """
/* public */ QWidget* %s::pultWidget() const
{
    // Returns module control view widget, or nullptr if there is no control view
    // NOTE: the method is const and might be called at any time,
    //       so DO NOT create widget here, just return!
    // TODO implement me
    return nullptr;
}
            """ % self.class_name

    # noinspection PyPep8Naming
    def resetCppImplementation(self):
        """
        Creates reset implementation stub

        :rtype:     str
        :return:    implementation of void reset()
        """
        return """
/* public slot */ void %s::reset()
{
    // Resets module to initial state before program execution
    // TODO implement me
}
        """ % self.class_name

    # noinspection PyPep8Naming
    def terminateEvaluationCppImplementation(self):
        """
        Creates terminateEvaluation implementation stub

        :rtype:     str
        :return:    implementation of void terminateEvaluation()
        """
        return """
/* public slot */ void %s::terminateEvaluation()
{
    // Called on program interrupt to ask long-running module's methods
    // to stop working
    // TODO implement me
}
        """ % self.class_name

    # noinspection PyPep8Naming
    def reloadSettingsCppImplementation(self):
        """
        Creates reloadSettings implementation stub

        :rtype:     str
        :return:    implementation of void reloadSettings(ExtensionSystem::SettingsPtr)
        """
        return """
/* public slot */ void %s::reloadSettings(ExtensionSystem::SettingsPtr settings, const QStringList & keys)
{
    // Updates setting on module load, workspace change or appliyng settings dialog.
    // If @param keys is empty -- should reload all settings, otherwise load only setting specified by @param keys
    // TODO implement me
    Q_UNUSED(settings);  // Remove this line on implementation
    Q_UNUSED(keys);  // Remove this line on implementation
}
        """ % self.class_name

    # noinspection PyPep8Naming
    def changeGlobalStateCppImplementation(self):
        """
        Creates changeGlobalState implementation stub

        :rtype:     str
        :return:    implementation of void changeGlobalState(GlobalState, GlobalState)
        """
        return """
/* public slot */ void %s::changeGlobalState(ExtensionSystem::GlobalState old, ExtensionSystem::GlobalState current)
{
    // Called when changed kumir state. The states are defined as enum ExtensionSystem::GlobalState:
    /*
    namespace ExtensionSystem {
        enum GlobalState {
            GS_Unlocked, // Edit mode
            GS_Observe, // Observe mode
            GS_Running, // Running mode
            GS_Input,  // User input required
            GS_Pause  // Running paused
        };
    }
    */
    // TODO implement me
    using namespace ExtensionSystem;  // not to write "ExtensionSystem::" each time in this method scope
    Q_UNUSED(old);  // Remove this line on implementation
    Q_UNUSED(current);  // Remove this line on implementation
}
        """ % self.class_name

    # noinspection PyPep8Naming
    def setAnimationEnabledCppImplementation(self):
        """
        Creates setAnimationEnabled implementation stub or an empty string if derived class has no implementation

        :rtype:     str
        :return:    implementation of void setAnimationEnabled(bool) or an empty string
        """
        if not self._module.gui:
            return ""
        else:
            return """
/* public slot */ void %s::setAnimationEnabled(bool enabled)
{
    // Sets GUI animation flag on run
    // NOTE this method just setups a flag and might be called anytime, even module not needed
    // TODO implement me
    Q_UNUSED(enabled);  // Remove this line on implementation
}
            """ % self.class_name

    # noinspection PyPep8Naming
    def loadActorDataCppImplementation(self):
        """
        Creates loadActorData implementation

        :rtype:     str
        :return:    implementation of void loadActorData(QIODevice *)
        """
        return """
/* public slot */ void %s::loadActorData(QIODevice * source)
{
    // Set actor specific data (like environment)
    // The source should be ready-to-read QIODevice like QBuffer or QFile
    Q_UNUSED(source);  // By default do nothing

}
        """ % self.class_name

    # noinspection PyPep8Naming
    def acceptableCommandLineParametersCppImplementation(self):
        """
        Empty implementation to get acceptable command line parameters

        :rtype:     str
        :return:    implementation of QList<ExtensionSystem::CommandLineParameter> acceptableCommandLineParameters()
        """
        return """

/* public static */ QList<ExtensionSystem::CommandLineParameter> %s::acceptableCommandLineParameters()
{
    // See "src/shared/extensionsystem/commandlineparameter.h" for constructor details
    return QList<ExtensionSystem::CommandLineParameter>();
}
        """ % self.class_name


# Script workflow functions

def _update_file(file_name, target_dir, text):
    """
    Creates (if not exists) or updates (if content differs) a text file

    :type   file_name:  str
    :param  file_name:  file name to update
    :type   target_dir: str
    :param  target_dir: directory to store a file
    :type   text:       unicode
    :param  text:       a new text contents of file
    """
    force_rewrite = True
    assert isinstance(text, unicode)
    if sys.version_info.major < 3:
        data = text.encode("utf-8")
    else:
        data = text
    if target_dir:
        if not os.path.exists(target_dir):
            os.makedirs(target_dir)
        file_name = os.path.normpath(target_dir + os.path.sep + file_name)
    if os.path.exists(file_name):
        f = open(file_name, 'r', encoding="utf-8")
        old_data = f.read()
        f.close()
        force_rewrite = old_data != data
    if force_rewrite:
        f = open(file_name, 'w', encoding="utf-8")
        f.write(data)
        f.close()


def create_module_type_header_file(module, basetype, target_dir=""):
    """
    :type   module:     Module
    :param  module:     actor module tree root
    :type   basetype:   BaseType
    :param  basetype:   base type definition
    :type   target_dir: str
    :param  target_dir: directory path to store (optional, by default uses current dir)
    """
    assert isinstance(module, Module)
    assert isinstance(basetype, BaseType)
    assert isinstance(target_dir, str)
    filename = "type" + basetype.get_qt_name().lower() + ".h"
    substitutions = {
        "headerGuard": "TYPE" + basetype.get_qt_name().upper() + "_H",
        "namespace": module.get_module_cpp_namespace(),
        "typeDeclaration": basetype.get_cpp_declaration()
    }
    data = _render_template("""
/*
DO NOT EDIT THIS FILE!

This file is autogenerated from "--update" and will be replaced
every build time

*/

#ifndef $headerGuard
#define $headerGuard

// Qt includes
#include <QtGlobal>

namespace $namespace {

$typeDeclaration

} // namespace $namespace

#endif // $headerGuard
    """, substitutions).strip() + "\n"
    _update_file(filename, target_dir, data)


def create_plugin_header_file(module, target_dir=""):
    """
    Creates or updates module plugin header file

    :type   module:     Module
    :param  module:     actor module tree root
    :type   target_dir: str
    :param  target_dir: directory path to store (optional, by default uses current dir)
    """
    assert isinstance(module, Module)
    assert isinstance(target_dir, str)
    file_base_name = module.get_plugin_cpp_class_name().lower()
    substitutions = {
        "headerGuard": file_base_name.upper() + "_H",
        "namespace": module.get_module_cpp_namespace(),
        "pluginClassDeclaration": PluginCppClass(module).get_cpp_declaration(),
        "threadClassDeclaration": AsyncThreadCppClass(module).get_cpp_declaration()
    }
    data = _render_template("""
/*
DO NOT EDIT THIS FILE!

This file is autogenerated from "--update" and will be replaced
every build time

*/

#ifndef $headerGuard
#define $headerGuard

// Kumir includes
#include <kumir2-libs/extensionsystem/kplugin.h>
#include <kumir2/actorinterface.h>
namespace Widgets {
class DeclarativeSettingsPage;
}


// Qt includes
#include <QtPlugin>
#include <QThread>

class QWidget;

namespace $namespace {

$pluginClassDeclaration

$threadClassDeclaration

} // namespace $namespace

#endif // $headerGuard

    """, substitutions).strip() + "\n"
    _update_file(file_base_name + ".h", target_dir, data)


def create_plugin_source_file(module, target_dir=""):
    """
    Creates or updates module plugin source file

    :type   module:     Module
    :param  module:     actor module tree root
    :type   target_dir: str
    :param  target_dir: directory path to store (optional, by default uses current dir)
    """
    assert isinstance(module, Module)
    assert isinstance(target_dir, str)
    file_base_name = module.get_plugin_cpp_class_name().lower()
    static_functions = ""
    for customType in module.types:
        assert isinstance(customType, BaseType)
        static_functions += customType.get_cpp_custom_type_encode_decode() + "\n"

    substitutions = {
        "headerFileName": file_base_name + ".h",
        "moduleBaseHeaderFileName": module.get_base_cpp_class_name().lower() + ".h",
        "moduleHeaderFileName": module.get_module_cpp_class_name().lower() + ".h",
        "namespace": module.get_module_cpp_namespace(),
        "staticFunctions": static_functions,
        "pluginClassImplementation": PluginCppClass(module).get_cpp_implementation(),
        "threadClassImplementation": AsyncThreadCppClass(module).get_cpp_implementation(),
        "pluginClassName": module.get_plugin_cpp_class_name()
    }
    data = _render_template("""
/*
DO NOT EDIT THIS FILE!

This file is autogenerated from "--update" and will be replaced
every build time

*/

// Self include
#include "$headerFileName"
#include "$moduleBaseHeaderFileName"
#include "$moduleHeaderFileName"
#include <kumir2-libs/widgets/declarativesettingspage.h>
#include <QApplication>

namespace $namespace {

$staticFunctions

$pluginClassImplementation

$threadClassImplementation

} // namespace $namespace
#if QT_VERSION < 0x050000
Q_EXPORT_PLUGIN($namespace::$pluginClassName)
#endif

    """, substitutions).strip() + "\n"
    _update_file(file_base_name + ".cpp", target_dir, data)


def create_module_base_header_file(module, target_dir=""):
    """
    Creates or updates module implementation C++ base header file

    :type   module:     Module
    :param  module:     actor module tree root
    :type   target_dir: str
    :param  target_dir: directory path to store (optional, by default uses current dir)
    """
    assert isinstance(module, Module)
    file_base_name = module.get_base_cpp_class_name().lower()
    custom_type_declarations = ""
    typedefs = ""
    for typee in module.types:
        assert isinstance(typee, BaseType)
        if not typee.is_standard_type():
            filename_prefix = ""
            if typee.get_module() and typee.get_module() != module:
                filename_prefix = "../" + typee.get_module().name.get_ascii_value().lower() + "/"
                typedefs += typee.get_typedef() + "\n"
            type_header_name = filename_prefix + "type" + typee.get_qt_name().lower() + ".h"
            custom_type_declarations += "#include \"%s\"\n" % type_header_name
    substitutions = {
        "headerGuard": file_base_name.upper() + "_H",
        "typeDefs": typedefs,
        "customTypeDeclarations": custom_type_declarations,
        "namespace": module.get_module_cpp_namespace(),
        "classDeclaration": ModuleBaseCppClass(module).get_cpp_declaration()
    }
    data = _render_template("""
/*
DO NOT EDIT THIS FILE!

This file is autogenerated from "--update" and will be replaced
every build time

*/

#ifndef $headerGuard
#define $headerGuard

// Custom types declaration headers (if any)
$customTypeDeclarations

// Kumir includes
#include <kumir2-libs/extensionsystem/kplugin.h>

// Qt includes
class QMenu;
class QAction;

namespace $namespace {

$typeDefs
$classDeclaration

} // namespace $namespace

#endif // $headerGuard

    """, substitutions).strip() + "\n"
    _update_file(file_base_name + ".h", target_dir, data)


def create_module_base_source_file(module, target_dir=""):
    """
    Creates or updates module implementation C++ base source file

    :type   module:     Module
    :param  module:     actor module tree root
    :type   target_dir: str
    :param  target_dir: directory path to store (optional, by default uses current dir)
    """
    assert isinstance(module, Module)
    file_base_name = module.get_base_cpp_class_name().lower()
    substitutions = {
        "selfInclude": file_base_name + ".h",
        "pluginInclude": module.get_plugin_cpp_class_name().lower() + ".h",
        "namespace": module.get_module_cpp_namespace(),
        "classImplementation": ModuleBaseCppClass(module).get_cpp_implementation()
    }
    data = _render_template("""
/*
DO NOT EDIT THIS FILE!

This file is autogenerated from "--update" and will be replaced
every build time

*/

// Self includes
#include "$selfInclude"
#include "$pluginInclude"

// Kumir includes
#include <kumir2-libs/extensionsystem/kplugin.h>

// Qt includes
#include <QMenu>
#include <QAction>
#include <QApplication>

namespace $namespace {

$classImplementation

} // namespace $namespace


    """, substitutions).strip() + "\n"
    _update_file(file_base_name + ".cpp", target_dir, data)


def create_module_header_file(module, target_dir=""):
    """
    Creates or updates module implementation header file

    :type   module:     Module
    :param  module:     actor module tree root
    :type   target_dir: str
    :param  target_dir: directory path to store (optional, by default uses current dir)
    """
    assert isinstance(module, Module)
    file_base_name = module.get_module_cpp_class_name().lower()
    substitutions = {
        "headerGuard": file_base_name.upper() + "_H",
        "namespace": module.get_module_cpp_namespace(),
        "baseClassHeader": module.get_base_cpp_class_name().lower() + ".h",
        "classDeclaration": ModuleCppClass(module).get_cpp_declaration()
    }
    data = _render_template("""
/*
This file is generated, but you can safely change it
until you run "gen_actor_source.py" with "--project" flag.

Generated file is just a skeleton for module contents.
You should change it corresponding to functionality.
*/

#ifndef $headerGuard
#define $headerGuard

// Base class include
#include "$baseClassHeader"

// Kumir includes
#include <kumir2-libs/extensionsystem/kplugin.h>

// Qt includes
#include <QtCore>
#if QT_VERSION >= 0x050000
#   include <QtWidgets>
#else
#   include <QtGui>
#endif

namespace $namespace {

$classDeclaration

} // namespace $namespace

#endif // $headerGuard
    """, substitutions).strip() + "\n"
    _update_file(file_base_name + ".h", target_dir, data)


def create_module_source_file(module, target_dir=""):
    """
    Creates or updates module implementation source file

    :type   module:     Module
    :param  module:     actor module tree root
    :type   target_dir: str
    :param  target_dir: directory path to store (optional, by default uses current dir)
    """
    assert isinstance(module, Module)
    file_base_name = module.get_module_cpp_class_name().lower()
    substitutions = {
        "namespace": module.get_module_cpp_namespace(),
        "selfHeader": module.get_module_cpp_class_name().lower() + ".h",
        "classImplementation": ModuleCppClass(module).get_cpp_implementation()
    }
    data = _render_template("""
/*
This file is generated, but you can safely change it
until you run "gen_actor_source.py" with "--project" flag.

Generated file is just a skeleton for module contents.
You should change it corresponding to functionality.
*/

// Self include
#include "$selfHeader"

// Kumir includes
#include <kumir2-libs/extensionsystem/kplugin.h>

// Qt includes
#include <QtCore>
#if QT_VERSION >= 0x050000
#   include <QtWidgets>
#else
#   include <QtGui>
#endif

namespace $namespace {

$classImplementation

} // namespace $namespace

    """, substitutions).strip() + "\n"
    _update_file(file_base_name + ".cpp", target_dir, data)


def create_cmakelists_txt(module, json_file_name, target_dir=""):
    """
    Creates or updates project CMakeLists.txt file

    :type   module:         Module
    :param  module:         actor module tree root
    :type   json_file_name: str
    :param  json_file_name: name of source JSON file used to watch for changes
    :type   target_dir:     str
    :param  target_dir:     directory path to store (optional, by default uses current dir)
    """
    substitutions = {
        "projectName": module.get_module_cpp_namespace(),
        "moduleFileName": module.get_module_cpp_class_name().lower(),
        "actorName": module.name.data["ascii"]
    }
    data = _render_template(r"""
project($projectName)
cmake_minimum_required(VERSION 3.0)

find_package(Kumir2 REQUIRED)
kumir2_use_qt(Core Gui)


set(SOURCES
    $moduleFileName.cpp
)

set(MOC_HEADERS
    $moduleFileName.h
)

kumir2_wrap_cpp(MOC_SOURCES ${MOC_HEADERS})

kumir2_add_actor(
    NAME        $actorName
    SOURCES     ${SOURCES} ${MOC_SOURCES}
    LIBRARIES   ${QT_LIBRARIES}
)
""", substitutions).strip() + "\n"
    _update_file("CMakeLists.txt", target_dir, data)


def create_docbook_file(module, target_dir):
    """
    Creates or updates module documentation DocBook XML file

    :type   module:     Module
    :param  module:     actor module tree root
    :type   target_dir: str
    :param  target_dir: directory path to store (optional, by default uses current dir)
    """
    file_name = "Actor" + module.name.get_camel_case_cpp_value() + ".xml"
    if "ru_RU" in module.name.data:
        module_name = module.name.data["ru_RU"]
    else:
        module_name = module.name.get_ascii_value()
    substitutions = {
        "moduleName": module_name
    }
    data = _render_template("""
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE book
    PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
    "docbookV4.5/docbookx.dtd"
    []
>
<article>
    <title>$moduleName</title>
    <!--
        Write module documentation here.

        See DocBook specification for more details,
        or use some GUI tool to edit this file (like "Serna Free").

    -->
</article>

        """, substitutions).strip() + "\n"
    _update_file(file_name, target_dir, data)


def main_update(args):
    """
    Script entry point for update mode workflow

    :type   args:   list
    :param  args:   script arguments
    :rtype:         int
    :return:        exit status
    """
    file_name = ""
    for arg in args[1:]:
        if not arg.startswith("-"):
            file_name = arg
    if not file_name:
        return main_help(args)
    module = Module.read(file_name)
    for customType in module.types:
        create_module_type_header_file(module, customType)
    create_plugin_header_file(module)
    create_plugin_source_file(module)
    create_module_base_header_file(module)
    create_module_base_source_file(module)
    return 0


def main_project(args):
    """
    Script entry point for create project mode workflow

    :type   args:   list
    :param  args:   script arguments
    :rtype:         int
    :return:        exit status
    """
    file_name = ""
    for arg in args[1:]:
        if not arg.startswith("-"):
            file_name = arg
    if not file_name:
        return main_help(args)
    module = Module.read(file_name)
    create_module_header_file(module)
    create_module_source_file(module)
    create_cmakelists_txt(module, file_name)
    create_docbook_file(module, "../../../userdocs")
    return 0


def main_help(args):
    """
    Script entry point for show help mode workflow

    :type   args:   list
    :param  args:   script arguments
    :rtype:         int
    :return:        exit status
    """
    message = __doc__[:__doc__.index("===")].strip() + "\n"
    if "--help" in args:
        sys.stdout.write(message.encode("utf-8"))
        return 0
    else:
        sys.stderr.write(message.encode("utf-8"))
        return 127


def main(args):
    """
    Script entry point

    :type   args:   list
    :param  args:   script arguments
    :rtype:         int
    :return:        exit status
    """
    if "--help" in args or len(args) < 3:
        return main_help(args)
    elif "--update" in args:
        return main_update(args)
    elif "--project" in args:
        return main_project(args)
    else:
        return main_help(args)


if __name__ == "__main__":
    sys.exit(main(sys.argv))
