# This file is part of OpenModelica.
#
# Copyright (c) 1998-2026, Open Source Modelica Consortium (OSMC),
# c/o Linköpings universitet, Department of Computer and Information Science,
# SE-58183 Linköping, Sweden.
#
# All rights reserved.
#
# THIS PROGRAM IS PROVIDED UNDER THE TERMS OF AGPL VERSION 3 LICENSE OR
# THIS OSMC PUBLIC LICENSE (OSMC-PL) VERSION 1.8.
# ANY USE, REPRODUCTION OR DISTRIBUTION OF THIS PROGRAM CONSTITUTES
# RECIPIENT'S ACCEPTANCE OF THE OSMC PUBLIC LICENSE OR THE GNU AGPL
# VERSION 3, ACCORDING TO RECIPIENTS CHOICE.
#
# The OpenModelica software and the OSMC (Open Source Modelica Consortium)
# Public License (OSMC-PL) are obtained from OSMC, either from the above
# address, from the URLs:
# http://www.openmodelica.org or
# https://github.com/OpenModelica/ or
# http://www.ida.liu.se/projects/OpenModelica,
# and in the OpenModelica distribution.
#
# GNU AGPL version 3 is obtained from:
# https://www.gnu.org/licenses/licenses.html#GPL
#
# This program is distributed WITHOUT ANY WARRANTY; without
# even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE, EXCEPT AS EXPRESSLY SET FORTH
# IN THE BY RECIPIENT SELECTED SUBSIDIARY LICENSE CONDITIONS OF OSMC-PL.
#
# See the full OSMC Public License conditions for more details.

from lxml import etree as ET
from OMSimulator.unit import Unit
from OMSimulator.ssm import SSM
from OMSimulator import namespace
from OMSimulator.variable import SignalType, Float32, Float64, Int8, Int16, Int32, Int64, UInt8, UInt16, UInt32, UInt64

class Values:
  def __init__(self):
    self.start_values = {}

  def setValue(self, name, value, type : SignalType | None, unit=None, description=None):
    if unit is not None and not isinstance(value, float):
      raise TypeError("Unit can only be set for Real values.")
    self.start_values[name] = (value, type, unit, description)

  def getValue(self, name):
    return self.start_values.get(name)[0] if name in self.start_values else None

  def empty(self) -> bool:
    return not self.start_values

  def getAllParameters(self):
    return self.start_values

  def list(self, prefix = ""):
    if self.empty():
      return

    for key, (value, type, unit, description) in self.start_values.items():
      if type:
        type_tag = type.name
      else:
        type_tag = self._getVariableType(value)
      print(f"{prefix} ({type_tag} {key}, {str(value.value if hasattr(value, 'value') else value)}, {unit}, '{description}')")

  def exportToSSD(self, node, parameterMapping : SSM | None = None, unitDefinitions = None):
    if self.empty():
      return

    parameter_bindings_node = ET.SubElement(node, namespace.tag("ssd", "ParameterBindings"))
    parameter_binding_node = ET.SubElement(parameter_bindings_node, namespace.tag("ssd", "ParameterBinding"))
    parameter_values_node = ET.SubElement(parameter_binding_node, namespace.tag("ssd", "ParameterValues"))
    parameter_set_node = ET.SubElement(parameter_values_node, namespace.tag("ssv", "ParameterSet"))
    parameter_set_node.set("version", "2.0")
    parameter_set_node.set("name", "parameters")
    parameters_node = ET.SubElement(parameter_set_node, namespace.tag("ssv", "Parameters"))

    self.add_parameters(parameters_node)

    ## export parameter mapping inline
    if parameterMapping:
      parameterMapping.exportToSSD(parameter_binding_node)

    ## export unit definitions
    if unitDefinitions:
      unit_definitions_node = ET.SubElement(parameter_set_node, namespace.tag("ssv", "Units"))
      for unit in unitDefinitions:
        unit.exportToSSD(unit_definitions_node)


  def exportToSSV(self, node):
    if self.empty():
      return

    parameters_node = ET.SubElement(node, namespace.tag("ssv", "Parameters"))
    self.add_parameters(parameters_node)

  def add_parameters(self, parameters_node, prefix = None):
    """Generic function to add XML parameters based on the value type."""
    for key, (value, type, unit, description) in self.start_values.items():
      parameter_node = ET.SubElement(parameters_node, namespace.tag("ssv", "Parameter"))
      if prefix:
        parameter_node.set("name", str(prefix) + "." + str(key))
      else:
        parameter_node.set("name", str(key))
      if description:
        parameter_node.set("description", description)

      if type:
        type_tag = type.name
      else:
        type_tag = self._getVariableType(value)

      parameter_type = ET.SubElement(parameter_node, namespace.tag("ssv", type_tag))
      value_ = str(value.value if hasattr(value, 'value') else value)
      if type_tag == "Boolean":
        value_ = "true" if value else "false"
      parameter_type.set("value", value_)
      if unit is not None:
        parameter_type.set("unit", unit)

  def _getVariableType(self, value):
    match value:
      case float():
        return "Real"
      case bool():  # Check for boolean first, because it is a subclass of int
        return "Boolean"
      case int():
        return "Integer"
      case str():
        return "String"
      ## handle fmi3 types
      case Float32():
        return "Float32"
      case Float64():
        return "Float64"
      case Int8():
        return "Int8"
      case Int16():
        return "Int16"
      case Int32():
        return "Int32"
      case Int64():
        return "Int64"
      case UInt8():
        return "UInt8"
      case UInt16():
        return "UInt16"
      case UInt32():
        return "UInt32"
      case UInt64():
        return "UInt64"
      case _:
          raise TypeError(f"Unsupported type: {type(value)}")