# 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, convert_table_data_to_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
import sys
from pkg_resources import get_distribution, DistributionNotFound
import dlubal.api.common.model_id_pb2
import dlubal.api.rfem.application_pb2
import dlubal.api.rfem.application_pb2_grpc
import dlubal.api.rfem.object_type_pb2
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
        self.__check_connection()

    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

    def __check_connection(self):
        '''
        Check connection and package version compatibility.
        '''

        # Check connection
        try:
            application_info = self.get_application_info()
        except grpc._channel._InactiveRpcError as e:
            sys.tracebacklimit = 0
            raise RuntimeError(f"gRPC Connection error: {e.code().name} - {e.details()}") from None

        # Check package version compatibility
        try:
            client_version = get_distribution('dlubal.api').version
            client_major, client_minor = map(int, client_version.split('.')[1:3])
            app_major, app_minor = map(int, application_info.version.split('.')[1:3])

            # If the versions differ, print a warning
            if app_major != client_major or app_minor != client_minor:
                print('\033[93mWARNING:\033[0m')
                print(f'Version mismatch between dlubal.api client package \033[91m{client_version}\033[0m and server application {application_info.name}.')
                print(f'To ensure full compatibility, please use the client version 2.{app_major}.{app_minor}.')
                print(f'To update, run: \033[92mpython.exe -m pip install --upgrade dlubal.api==2.{app_major}.{app_minor}\033[0m\n')

        except DistributionNotFound:
            return

    # 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: list, model_id: dlubal.api.common.model_id_pb2.ModelId | None = None):
        '''
        Retrieves a list of objects from the model.

        Args:
            objs (list[obj]):
                An object list 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 list of retrieved objects.
        '''
        result = self.stub.get_object_list_impl(pack_object_list(objs, model_id))
        type_list=[]
        for o in objs:
            type_list.append(type(o))
        return unpack_object_list(result, type_list)

    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:
           None
        '''
        return self.stub.create_object_impl(pack_object(obj, model_id))

    def create_object_list(self, objs:list, model_id: dlubal.api.common.model_id_pb2.ModelId | None = None):
        '''
        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.
        Returns:
            None
        '''
        return self.stub.create_object_list_impl(pack_object_list(objs, model_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: list, 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: list, 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):
        '''
        Returns Table, which is just a convenience wrapper around a Pandas Dataframe.
        The Dataframe can be directly accessed as .data

        Args:
            results_id (:ref:`ResultsId` | None):
                Unique identifier of the result category type.
            filters (:ref:`ResultsFilter`, optional):
                Filter to get only relevant results

        Returns:
            Table(data_frame):
                Requested results in form of table.
        '''

        all_filters = list(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=all_filters))

        return convert_table_data_to_table(table_data=results)

    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
        Active model is used if this field is not set.
            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
        Active model is used if this field is not set.
            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 (dlubal.api.common.model_id_pb2.ModelId): 
        """
        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 API server
        """
        self.stub.close_connection(google.protobuf.empty_pb2.Empty())

    def delete_all_objects(self, *, optional_model_id: dlubal.api.common.model_id_pb2.OptionalModelId = dlubal.api.common.model_id_pb2.OptionalModelId()):
        """
        Deletes all objects

        Args:
            optional_model_id (dlubal.api.common.model_id_pb2.OptionalModelId): 
        """
        self.stub.delete_all_objects(optional_model_id)

    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)

    def plausibility_check(self, *, type: dlubal.api.rfem.application_pb2.PlausibilityCheckType, skip_warnings: bool, model_id: dlubal.api.common.model_id_pb2.ModelId | None = None) -> dlubal.api.rfem.application_pb2.OperationResult:
        """
        Performs validation based on the specified validation type.
        Returns result of the operation, description of the result and possibly additional information about an error.

        Args:
            type (dlubal.api.rfem.application_pb2.PlausibilityCheckType): Specifies various validation checks that can be performed before generating a mesh, running a calculation, or verifying plausibility.
            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.PlausibilityCheckRequest(type=type, skip_warnings=skip_warnings, model_id=model_id)
        return self.stub.plausibility_check(request)

    def get_object_id_list(self, *, object_type: dlubal.api.rfem.object_type_pb2.ObjectType | None = None, parent_no: int | None = None, model_id: dlubal.api.common.model_id_pb2.ModelId | None = None) -> dlubal.api.rfem.application_pb2.ObjectIdList:
        """
        Retrieves object ids filtered by a specific type.
        If you want to retrieve all object ids, do not specify the object_type.

        Args:
            object_type (dlubal.api.rfem.object_type_pb2.ObjectType | None): The type of objects to retrieve. To get all object ids, omit it.
            parent_no (int | None): Unique identifier of the parent object.
        Omit this parameter if the object type does not have a parent or you want to get result for all parent objects.
            model_id (dlubal.api.common.model_id_pb2.ModelId | None): Unique identifier of the model.
        If not specified, the active model is used.

        Returns:
            dlubal.api.rfem.application_pb2.ObjectIdList
        """
        request = dlubal.api.rfem.application_pb2.GetObjectIdListRequest(object_type=object_type, parent_no=parent_no, model_id=model_id)
        return self.stub.get_object_id_list(request)

    def get_model_main_parameters(self, *, optional_model_id: dlubal.api.common.model_id_pb2.OptionalModelId = dlubal.api.common.model_id_pb2.OptionalModelId()) -> dlubal.api.rfem.application_pb2.ModelMainParameters:
        """
        Retrieves the main parameters of a model, including its ID, name, description,
        comments, file path, and associated project details.

        Args:
            optional_model_id (dlubal.api.common.model_id_pb2.OptionalModelId): 

        Returns:
            dlubal.api.rfem.application_pb2.ModelMainParameters
        """
        return self.stub.get_model_main_parameters(optional_model_id)

    def generate_combinations(self, *, optional_model_id: dlubal.api.common.model_id_pb2.OptionalModelId = dlubal.api.common.model_id_pb2.OptionalModelId()):
        """
        Generates combinations

        Args:
            optional_model_id (dlubal.api.common.model_id_pb2.OptionalModelId): 
        """
        self.stub.generate_combinations(optional_model_id)
