# Autogenerated code. DO NOT EDIT. Manual code can be added to the corresponding .template.py file.

from __future__ import annotations
from dlubal.api.common.connection import _ApiKey, check_ssl, init_channel
from dlubal.api.rfem.packing import pack_object, pack_object_list, unpack_object, unpack_object_list, get_internal_value
from dlubal.api.common.exceptions import exception_handler
from dlubal.api.common.table import Table
from dlubal.api.common.table_data_pb2 import TableData
from dlubal.api.rfem.results import ResultsId
from dlubal.api.rfem.results.results_query_pb2 import ResultsQuery, ResultsFilter
import grpc

from collections.abc import Iterable
from pandas import DataFrame
import dlubal.api.common.model_id_pb2
import dlubal.api.rfem.application_pb2
import dlubal.api.rfem.application_pb2_grpc
import google.protobuf.empty_pb2


class Application:

    def __init__(self,
                 api_key_value=None,
                 api_key_name=None,
                 url='127.0.0.1',
                 port='9000',
                 ssl=False,
                 ssl_file=''):
        '''
        Initialize the RFEM client and connect to gRPC server.

        Args:
            api_key_value (str, optional): Value of the API key. It can be found in Extranet under My Data. Every API key starts with 'ak' (default is None).
            api_key_name (str, optional): Name of the API key stored or to be stored. Can be any string of letters and numbers starting with 'ak', 51 characters long.
            url (str, optional): URL number.
            port (int, optional): Port number.
            ssl (bool, optional): False if NOT using SSL auth. True if using the installed version of SSL certificate, or path to .crt certificate if using a file.
            ssl_file (str, optional): Path to SSL certificate file (.crt, .pem).
        '''
        self.url = url
        self.port = port
        check_ssl(ssl, ssl_file)
        self.ssl = ssl
        self.ssl_file = ssl_file
        self.api_key = _ApiKey(api_key_name, api_key_value)
        self.channel = init_channel(self.api_key.value, self.url, self.port, self.ssl, self.ssl_file)
        self.stub = dlubal.api.rfem.application_pb2_grpc.ApplicationStub(self.channel) if self.channel else None

    def __enter__(self):
        '''
        Start a session by entering the context manager
        '''
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        '''
        Ensure the session is finished and channel is closed properly before handling exceptions.
        '''

        # Attempt to close the connection first to prevent delays
        try:
            self.close_connection()
        except Exception as e:
            pass

        # Now handle exceptions that occurred in the `with` block
        if exc_type is not None:
            if issubclass(exc_type, grpc.RpcError):
                raise RuntimeError(f"gRPC Error: {exc_value.code().name} - {exc_value.details()}") from None
            elif issubclass(exc_type, (SystemExit, KeyboardInterrupt)):
                return False
            raise RuntimeError(f"Unexpected error: {str(exc_value)}") from None

    # Functions using packing

    def get_object(self, obj, model_id: dlubal.api.common.model_id_pb2.ModelId | None = None):
        '''
        Retrieves a single object from the model using only the `no` argument in the object arguments.

        Args:
            obj (obj): An object to be retrieved defined by its number. Refer to the :ref:`rfem_objects` for available objects.
            model_id (:ref:`ModelId` | None): Unique identifier of the model.

        Returns:
            :ref:`rfem_objects`: A retrieved object.
        '''
        result = self.stub.get_object_impl(pack_object(obj, model_id))
        return unpack_object(result, type(obj))

    def get_object_list(self, objs, model_id: dlubal.api.common.model_id_pb2.ModelId | None = None):
        '''
        Retrieves a list of objects from the model.

        Args:
            objs (obj): An object dict to be retrieved. Refer to the :ref:`rfem_objects` for available objects.
            model_id (:ref:`ModelId` | None): Unique identifier of the model.

        Returns:
            :ref:`rfem_objects`: A retrieved object.
        '''
        result = self.stub.get_object_list_impl(pack_object_list(objs, model_id))
        return unpack_object_list(result, type(objs[0]))

    def create_object(self, obj, model_id: dlubal.api.common.model_id_pb2.ModelId | None = None):
        '''
        Creates a single object in the model.

        Args:
            obj (obj): An object to be created. Refer to the :ref:`rfem_objects` for available objects.
            model_id (:ref:`ModelId` | None): Unique identifier of the model.

        Returns:
           ObjectId: ID of the created object
        '''
        return self.stub.create_object_impl(pack_object(obj, model_id))

    def create_object_list(self, objs, model_id: dlubal.api.common.model_id_pb2.ModelId | None = None, return_object_id=False):
        '''
        Creates a list of objects in the model.

        Args:
            objs (list[obj]): A list of objects to be created. Refer to the :ref:`rfem_objects` for available objects.
            model_id (:ref:`ModelId` | None): Unique identifier of the model.
            return_object_id (bool): Specifies whether to return the list of object IDs.

        Returns:
            ObjectIdList | None: List of IDs of the created objects if `return_object_id` is True, otherwise None.
        '''
        return self.stub.create_object_list_impl(pack_object_list(objs, model_id, return_object_id))

    def update_object(self, obj, model_id: dlubal.api.common.model_id_pb2.ModelId | None = None):
        '''
        Updates a single object in the model.

        Args:
            obj (obj): An object to be updated. Refer to the :ref:`rfem_objects` for available objects.
            model_id (:ref:`ModelId` | None): Unique identifier of the model.

        Returns:
            None
        '''
        self.stub.update_object_impl(pack_object(obj, model_id))

    def update_object_list(self, objs, model_id: dlubal.api.common.model_id_pb2.ModelId | None = None):
        '''
        Updates a list of objects in the model.

        Args:
            objs (list[obj]): A list of objects to be updated. Refer to the :ref:`rfem_objects` for available objects.
            model_id (:ref:`ModelId` | None): Unique identifier of the model.

        Returns:
            None
        '''
        self.stub.update_object_list_impl(pack_object_list(objs, model_id))

    def delete_object(self, obj, model_id: dlubal.api.common.model_id_pb2.ModelId | None = None):
        '''
        Deletes a single object from the model.

        Args:
            obj (obj): An object to be deleted defined by its number. Refer to the :ref:`rfem_objects` for available objects.
            model_id (:ref:`ModelId` | None): Unique identifier of the model.

        Returns:
            None
        '''
        self.stub.delete_object_impl(pack_object(obj, model_id))

    def delete_object_list(self, objs, model_id: dlubal.api.common.model_id_pb2.ModelId | None = None):
        '''
        Deletes a list of objects from the model.

        Args:
            objs (list[obj]): A list of objects to be deleted. Refer to the :ref:`rfem_objects` for available objects.
            model_id (:ref:`ModelId` | None): Unique identifier of the model.

        Returns:
            None
        '''
        self.stub.delete_object_list_impl(pack_object_list(objs, model_id))

    def get_results(self, results_id: ResultsId, filters: Iterable[ResultsFilter] | None = None, **keyword_filters):
        '''
        GetResults returns Table, which is just a convenience wrapper around a Pandas Dataframe.
        The Dataframe can be directly accessed as .data

        Args:
            results_id (list[obj]): A list of objects to be deleted. Refer to the :ref:`rfem_objects` for available objects.
                example: ResultsId.RESULTS_ID_TEST
            filters (ResultsFilter, optional): Filter to get only relevant results. Valid filter expressions are: "min", "max", ">value", "<value", and just "value" for equality.
                example: filters=[ResultsFilter(column_id="support_force_p_x", filter_expression="max")]
        Returns:
            Table(data_frame): Requested results in form of table.
        '''
        all_filters = [].append(filters) if filters else []

        for key, value in keyword_filters.items():
            all_filters.append(ResultsFilter(column_id=key, filter_expression=str(value)))

        results: TableData = self.stub.get_results_impl(ResultsQuery(results_id=results_id, filters=filters))

        rows_data = []
        for row in results.rows:
            rows_data.append([get_internal_value(v) for v in row.values])

        data_frame = DataFrame(columns=list(results.column_ids), data=rows_data)

        return Table(data_frame)

    def create_model(self, *, name: str, template_path: str | None = None) -> dlubal.api.common.model_id_pb2.ModelId:
        """
        Creates new model with the name specified in request.
        Returns model id of created model

        Args:
            name (str): Name of the new model
            template_path (str | None): Path to the existing template to be opened

        Returns:
            dlubal.api.common.model_id_pb2.ModelId
        """
        request = dlubal.api.rfem.application_pb2.CreateModelRequest(name=name, template_path=template_path)
        return self.stub.create_model(request)

    def open_model(self, *, path: str) -> dlubal.api.common.model_id_pb2.ModelId:
        """
        Opens model from path specified in 'name' field of the request
        Returns model id of opened model

        Args:
            path (str): Path to the existing model to be opened

        Returns:
            dlubal.api.common.model_id_pb2.ModelId
        """
        request = dlubal.api.rfem.application_pb2.OpenModelRequest(path=path)
        return self.stub.open_model(request)

    def save_model(self, *, model_id: dlubal.api.common.model_id_pb2.ModelId | None = None, path: str | None = None):
        """
        Saves model specified by model id to optional specified path

        Args:
            model_id (dlubal.api.common.model_id_pb2.ModelId | None): Unique identifier of the model
            path (str | None): Path to the model
        """
        request = dlubal.api.rfem.application_pb2.SaveModelAsRequest(model_id=model_id, path=path)
        self.stub.save_model(request)

    def close_model(self, *, model_id: dlubal.api.common.model_id_pb2.ModelId | None = None, save_changes: bool):
        """
        Closes model specified by model id

        Args:
            model_id (dlubal.api.common.model_id_pb2.ModelId | None): Unique identifier of the model
            save_changes (bool): Specifies whether to save changes before closing the model
        """
        request = dlubal.api.rfem.application_pb2.CloseModelRequest(model_id=model_id, save_changes=save_changes)
        self.stub.close_model(request)

    def close_all_models(self, *, save_changes: bool):
        """
        Closes all open models

        Args:
            save_changes (bool): Specifies whether to save changes before closing the models
        """
        request = dlubal.api.rfem.application_pb2.CloseAllModelsRequest(save_changes=save_changes)
        self.stub.close_all_models(request)

    def get_active_model(self) -> dlubal.api.common.model_id_pb2.ModelId:
        """
        Returns model id of an active model

        Returns:
            dlubal.api.common.model_id_pb2.ModelId
        """
        return self.stub.get_active_model(google.protobuf.empty_pb2.Empty())

    def set_active_model(self, *, model_id: dlubal.api.common.model_id_pb2.ModelId):
        """
        Sets active model specified by model id

        Args:
            model_id (:ref:`ModelId` | None): Unique identifier of the model.
        """
        self.stub.set_active_model(model_id)

    def close_application(self):
        """
        Closes the whole application
        """
        self.stub.close_application(google.protobuf.empty_pb2.Empty())

    def get_model_list(self) -> dlubal.api.rfem.application_pb2.ModelList:
        """
        Returns list of models and information about them

        Returns:
            dlubal.api.rfem.application_pb2.ModelList
        """
        return self.stub.get_model_list(google.protobuf.empty_pb2.Empty())

    def get_application_info(self) -> dlubal.api.rfem.application_pb2.ApplicationInfo:
        """
        Returns information about the application

        Returns:
            dlubal.api.rfem.application_pb2.ApplicationInfo
        """
        return self.stub.get_application_info(google.protobuf.empty_pb2.Empty())

    def close_connection(self):
        """
        Closes connection to Webservice server
        """
        self.stub.close_connection(google.protobuf.empty_pb2.Empty())

    def delete_all_objects(self, *, model_id: dlubal.api.common.model_id_pb2.ModelId | None = None):
        """
        Deletes all objects.

        Args:
            model_id (:ref:`ModelId` | None): Unique identifier of the model.
            Active model is used if this field is not set.
        """
        request = (
            dlubal.api.common.model_id_pb2.OptionalModelId(guid=model_id.guid)
            if model_id
            else dlubal.api.common.model_id_pb2.OptionalModelId()
        )
        self.stub.delete_all_objects(request)

    def calculate_all(self, *, skip_warnings: bool, model_id: dlubal.api.common.model_id_pb2.ModelId | None = None) -> dlubal.api.rfem.application_pb2.OperationResult:
        """
        Performs a full calculation for all objects.
        Returns result of the operation, description of the result and possibly additional information about an error.

        Args:
            skip_warnings (bool): Specifies whether to skip warnings during the calculation.
            model_id (dlubal.api.common.model_id_pb2.ModelId | None): Unique identifier of the model.
        Active model is used if this field is not set.

        Returns:
            dlubal.api.rfem.application_pb2.OperationResult
        """
        request = dlubal.api.rfem.application_pb2.CalculateAllRequest(skip_warnings=skip_warnings, model_id=model_id)
        return self.stub.calculate_all(request)
