{% if TOOL_WRAPPER %}
# generated by {{TOOL_WRAPPER}}
{% endif %}

from pathlib import Path
{% if STRUCTURE_TYPES -%}
from typing import Union
{% endif %}
import ctypes
{% if ENUM_TYPES -%}
from enum import Enum
{% endif %}

# load the SCADE executable code
_lib = ctypes.cdll.LoadLibrary(str(Path(__file__).with_suffix('')))
{% if ELABORATION -%}
elaboration_fct = _lib.{{ELABORATION}}
elaboration_fct.argtypes = []
elaboration_fct.restype = ctypes.c_void_p
elaboration_fct()
{% endif %}
{% if SENSORS -%}
# sensors
class _Sensors:
    def __init__(self):
{% for opt in SENSORS %}
        self._{{ opt[0] }} = {{ opt[1] }}.in_dll(_lib, "{{ opt[2] }}")
{% endfor %}

{% for opt in SENSORS %}
    @property
    def {{ opt[0] }}(self) -> {{ opt[3] }}:
        return self._{{ opt[0] }}.value

    @{{ opt[0] }}.setter
    def {{ opt[0] }}(self, value: {{ opt[3] }}) -> None:
        self._{{ opt[0] }}.value = value
{% endfor %}

# unique instance
sensors = _Sensors()
{% endif %}

{% if STRUCTURE_TYPES -%}
def make_value(value, type_: type):
    """Return a ctypes value from a Python literal."""
    return type_(*value) if isinstance(value, tuple) else value


class SequencedStructure(ctypes.Structure):
    def __getitem__(self, key):
        field = self._fields_[key][0]
        return eval(f'self.{field}')

    def __setitem__(self, key, value):
        field = self._fields_[key][0]
        exec(f'self.{field} = {value}')

    def __str__(self):
        fields_str = ", ".join(
            [f"{field[0]}={getattr(self, field[0])}" for field in self._fields_]
        )
        return fields_str

    def __eq__(self, other: "SequencedStructure"):
        if not isinstance(other, SequencedStructure):
            return False
        return all(getattr(self, field[0]) == getattr(other, field[0]) for field in self._fields_)


{% for opt in STRUCTURE_TYPES %}
class {{ opt }}(SequencedStructure):
    _fields_ = [
        {% for val in STRUCTURE_TYPES[opt] %}
        ('{{ val[0] }}', {{ val[1] }}),
        {% endfor %}
    ]

    def __eq__(self, other: Union["{{ opt }}", tuple]):
        if isinstance(other, tuple):
            if len(other) != len(self._fields_):
                return False
            return self == {{ opt }}(*other)
        return super().__eq__(other)

{% endfor %}
{% endif %}
{% if ENUM_TYPES -%}
class EnumType(Enum):
    def __str__(self):
        return self.name


{% for opt in ENUM_TYPES %}
class {{ opt }}(Enum):
    {% for val in ENUM_TYPES[opt] %}
    {{ val }} = {{ loop.index - 1 }}
    {% endfor %}


{% endfor %}
{% endif %}
{% if IS_ARRAY_TYPES %}

def _ctypes_array2tuple(value: ctypes.Array) -> tuple:
    if not isinstance(value, ctypes.Array):
        raise TypeError("Expected a ctypes.Array")
    return tuple(_ctypes_array2tuple(item) if isinstance(item, ctypes.Array) else item for item in value)
{% endif %}
{% for OPT in OPERATORS_DATA %}


# operator inputs
class {{OPT["op_name"]}}_{{OPT["inputs"]["name"]}}:
    def __init__(self):
{% if OPT["inputs"]["parameters"] %}
{% for opt in OPT["inputs"]["parameters"] %}
    {% if opt[4] -%} {# array #}
        self._{{ opt[0] }} = ({{ opt[1] }}){{ opt[2] }}
    {% else %}
        self._{{ opt[0] }}: {{ opt[1] }} = {{ opt[2] }}
    {% endif %}
{% endfor %}
{% macro get_in_type(opt) %}
    {% if opt[4] -%}
        {{ opt[4] -}}
    {% elif opt[3] -%}
        {{ opt[3] -}}
    {% elif opt[1] -%}
        {{ opt[1] -}}
    {% else -%}
        None
    {% endif -%}
{% endmacro %}
{% for opt in OPT["inputs"]["parameters"] %}

    @property
    def {{ opt[0] }}(self) -> {{ get_in_type(opt) }}:
        {% if opt[1] == "bool" %}
        return bool(self._{{ opt[0] }})
        {% elif opt[3] %}
        if isinstance(self._{{ opt[0] }}, int):
            return {{ opt[3] }}(self._{{ opt[0] }})
        return {{ opt[3] }}(self._{{ opt[0] }}.value)
        {% else %}
        return self._{{ opt[0] }}
        {% endif %}

    @{{ opt[0] }}.setter
    def {{ opt[0] }}(self, value: {{ get_in_type(opt) }}) -> None:
    {% if opt[1] == "bool" %}
        self._{{ opt[0] }} = 1 if value else 0
    {% elif opt[3] %}
        if not isinstance(value, {{ opt[3] }}):
            raise TypeError(f"{{ opt[0] }} expected a {{ opt[3] }}, but {type(value).__name__} was given.")
        self._{{ opt[0] }} = value.value
    {% elif "()" in opt[2] and not opt[4]%}
        self._{{ opt[0] }} = make_value(value, {{ opt[1] }})
    {% elif opt[4] %}
        self._{{ opt[0] }} = ({{ opt[1] }})(*value)
    {% else %}
        self._{{ opt[0] }} = value
    {% endif %}

{% endfor %}
{% else %}
        pass
{% endif %}

# operator outputs
class {{OPT["op_name"]}}_{{OPT["outputs"]["name"]}}:
{% if OPT["outputs"] %}
{% if OPT["ios_context"] %}
    def __init__(self, context, offsets):
{% for parameter in OPT["ios_context"] %}
        {{parameter}}
{% endfor %}
{% else %}
    def __init__(self):
{% for parameter in OPT["outputs"]["parameters"] %}
    {% if "()" in parameter[2] and not parameter[4] == "tuple" %}
        self._{{parameter[0]}} = {{parameter[2]}}
    {% elif parameter[4] %} {# array #}
        self._{{ parameter[0] }} = ({{ parameter[5] }}){{ parameter[2] }}
    {% else %}
        self._{{parameter[0]}} = {{parameter[5]}}({{parameter[2]}})
    {% endif %}
{% endfor %}
{% endif %}
{% macro get_out_type(opt) %}
    {% if opt[4] -%}
        {{ opt[4] -}}
    {% elif opt[3] -%}
        {{ opt[3] -}}
    {% elif opt[1] -%}
        {{ opt[1] -}}
    {% else -%}
        None
    {% endif -%}
{% endmacro %}

{% for opt in OPT["outputs"]["parameters"] %}
    @property
    def {{ opt[0] }}(self) -> {{ get_out_type(opt) }}:
        {% if opt[1] == "bool" %}
        return bool(self._{{ opt[0] }})
        {% elif opt[3] %}
        if isinstance(self._{{ opt[0] }}, int):
            return {{ opt[3] }}(self._{{ opt[0] }})
        return {{ opt[3] }}(self._{{ opt[0] }}.value)
        {% elif "()" in opt[2] and not opt[4]%}
        return self._{{ opt[0] }}
        {% elif opt[4] %}
        return _ctypes_array2tuple(self._{{ opt[0] }})
        {% elif OPT["op_cycle"]["return"] %}
        return self._{{ opt[0] }}
        {% else %}
        return self._{{ opt[0] }}.value
        {% endif %}

    @{{ opt[0] }}.setter
    def {{ opt[0] }}(self, value: {{ get_out_type(opt) }}) -> None:
    {% if opt[1] == "bool" %}
        self._{{ opt[0] }} = 1 if value else 0
    {% elif opt[3] %}
        if not isinstance(value, {{ opt[3] }}):
            raise TypeError(f"{{ opt[0] }} expected a {{ opt[3] }}, but {type(value).__name__} was given.")
        self._{{ opt[0] }} = value.value
    {% elif opt[4] %}
        self._{{ opt[0] }} = ({{ opt[1] }})(*value)
    {% else %}
        self._{{ opt[0] }} = value
    {% endif %}

{% endfor %}
{% else %}
        pass
{% endif %}

class {{ OPT["op_name"] }}:
    def __init__(self):
{% if OPT["op_context"] %}
        alloc_fct = _lib.py_alloc_{{OPT["op_context"]["c_name"]}}
        alloc_fct.argtypes = []
        alloc_fct.restype = ctypes.c_void_p
        context = alloc_fct()
        self.{{OPT["op_context"]["py_name"]}} = ctypes.c_void_p.from_address(context)
    {% if OPT["op_context"]["len_context"] != 0 %}
        offsets = (ctypes.c_int64 * {{OPT["op_context"]["len_context"]}}).in_dll(_lib, "py_offsets_{{OPT["op_context"]['c_name']}}")
    {% endif %}
{% endif %}
{% if OPT["op_reset"] %}
        self.reset_fct = _lib.{{OPT["op_reset"]["c_name"]}}
    {% if OPT["op_cycle"]["return"] %}
        self.reset_fct.restype = {{OPT["op_cycle"]["return"][5]}}
    {% else %}
        self.reset_fct.restype = ctypes.c_void_p
    {% endif %}
{% endif %}
        self.cycle_fct = _lib.{{OPT["op_cycle"]["c_name"]}}
        self.cycle_fct.argtypes = [
        {% for opt in OPT["op_cycle"]["parameter"] %}
            {{ opt }},
        {% endfor %}
        ]
    {% if OPT["op_cycle"]["return"] %}
        self.cycle_fct.restype = {{OPT["op_cycle"]["return"][5]}}
    {% else %}
        self.cycle_fct.restype = ctypes.c_void_p
    {% endif %}
        self.inputs = {{OPT["op_name"]}}_{{OPT["inputs"]["name"]}}()
{% if OPT["ios_context"] %}
        self.outputs = {{OPT["op_name"]}}_{{OPT["outputs"]["name"]}}(context, offsets)
{% else %}
        self.outputs = {{OPT["op_name"]}}_{{OPT["outputs"]["name"]}}()
{% endif %}

{% if OPT["op_context"] %}
    def __del__(self):
        free_fct = _lib.py_free_{{OPT["op_context"]["c_name"]}}
        free_fct.argtypes = [ctypes.c_void_p]
        free_fct.restype = None
        free_fct(ctypes.byref(self.{{OPT["op_context"]["py_name"]}}))

{% endif %}
    def reset(self) -> None:
{% if OPT["op_reset"] %}
        {% if OPT["op_reset"]["parameters"] %}
        self.reset_fct(ctypes.byref(self.{{OPT["op_context"]["py_name"]}}))
        {% else %}
            self.reset_fct()
        {% endif %}
{% else %}
        # no reset function
        pass
{% endif %}

    def cycle(self, cycles: int = 1) -> None:
        for i in range(cycles):
        {% if OPT["op_cycle"]["return"] %}
            self.outputs._{{OPT["op_cycle"]["return"][0]}} = self.cycle_fct(
        {% else %}
            self.cycle_fct(
        {% endif %}
        {% if OPT["ios_context"] %}
            {% for opt in OPT["op_cycle"]["parameters_name"] %}
                {% if "out_c" in opt %}
                self._out_c,
                {% else %}
                self.inputs._{{ opt }},
                {% endif %}
            {% endfor %}
        {% else %}
            {% for opt in OPT["op_cycle"]["parameters_name"] %}
                self.inputs._{{ opt }},
        {% endfor %}
        {% if not OPT["op_cycle"]["return"] %}
                {% for parameter in OPT["outputs"]["parameters"] %}
                self.outputs._{{parameter[0]}},
                {% endfor %}
            {% endif %}
        {% endif %}
            )

{% endfor %}
# end of file
