# flake8: noqa

{{comments}}

from rcl_interfaces.msg import ParameterDescriptor
from rcl_interfaces.msg import SetParametersResult
from rcl_interfaces.msg import FloatingPointRange, IntegerRange
from rclpy.clock import Clock
from rclpy.exceptions import InvalidParameterValueException
from rclpy.time import Time
import copy
import rclpy
import rclpy.parameter
from generate_parameter_library_py.python_validators import ParameterValidators

{% if user_validation_file|length -%}
import {{user_validation_file}} as custom_validators
{% endif %}

class {{namespace}}:
{%- filter indent(width=4) %}

class Params:
{%- filter indent(width=4) %}
# for detecting if the parameter struct has been updated
stamp_ = Time()

{{field_content-}}
{{sub_struct_content-}}
{% endfilter -%}
{%- endfilter %}


    class ParamListener:
        def __init__(self, node, prefix=""):
            self.prefix_ = prefix
            self.params_ = {{namespace}}.Params()
            self.node_ = node
            self.logger_ = rclpy.logging.get_logger("{{namespace}}." + prefix)

            self.declare_params()

            self.node_.add_on_set_parameters_callback(self.update)
            self.user_callback = None
            self.clock_ = Clock()

        def get_params(self):
            tmp = self.params_.stamp_
            self.params_.stamp_ = None
            paramCopy = copy.deepcopy(self.params_)
            paramCopy.stamp_ = tmp
            self.params_.stamp_ = tmp
            return paramCopy

        def is_old(self, other_param):
            return self.params_.stamp_ != other_param.stamp_

        def unpack_parameter_dict(self, namespace: str, parameter_dict: dict):
            """
            Flatten a parameter dictionary recursively.

            :param namespace: The namespace to prepend to the parameter names.
            :param parameter_dict: A dictionary of parameters keyed by the parameter names
            :return: A list of rclpy Parameter objects
            """
            parameters = []
            for param_name, param_value in parameter_dict.items():
                full_param_name = namespace + param_name
                # Unroll nested parameters
                if isinstance(param_value, dict):
                    nested_params = self.unpack_parameter_dict(
                            namespace=full_param_name + rclpy.parameter.PARAMETER_SEPARATOR_STRING,
                            parameter_dict=param_value)
                    parameters.extend(nested_params)
                else:
                    parameters.append(rclpy.parameter.Parameter(full_param_name, value=param_value))
            return parameters

        def set_params_from_dict(self, param_dict):
            params_to_set = self.unpack_parameter_dict('', param_dict)
            self.update(params_to_set)

        def set_user_callback(self, callback):
            self.user_callback = callback

        def clear_user_callback(self):
            self.user_callback = None

        def refresh_dynamic_parameters(self):
            updated_params = self.get_params()
            # TODO remove any destroyed dynamic parameters
{%- filter indent(width=6) %}
{{remove_dynamic_parameters}}
{%- endfilter %}
            # declare any new dynamic parameters
{%- filter indent(width=12) %}
{{update_declare_dynamic_parameters}}
{%- endfilter %}

        def update(self, parameters):
            updated_params = self.get_params()

            for param in parameters:
{%- filter indent(width=16) %}
{{update_params_set}}
{%- endfilter %}
{% if update_dynamic_parameters|length %}
{%- filter indent(width=12) %}
# update dynamic parameters
for param in parameters:
{%- filter indent(width=4) %}
{{update_dynamic_parameters}}
{%- endfilter %}
{%- endfilter %}
{%- endif %}

            updated_params.stamp_ = self.clock_.now()
            self.update_internal_params(updated_params)
            if self.user_callback:
                self.user_callback(self.get_params())
            return SetParametersResult(successful=True)

        def update_internal_params(self, updated_params):
            self.params_ = updated_params

        def declare_params(self):
            updated_params = self.get_params()
            # declare all parameters and give default values to non-required ones
{%- filter indent(width=12) %}
{{declare_params}}
{%- endfilter %}
            # TODO: need validation
            # get parameters and fill struct fields

{%- filter indent(width=12) %}
{{declare_params_set}}
{%- endfilter %}
{% if declare_set_dynamic_params|length %}
{% filter indent(width=12) %}
# declare and set all dynamic parameters
{{declare_set_dynamic_params}}
{%- endfilter %}
{%- endif %}

            self.update_internal_params(updated_params)
