# -*- coding: utf-8 -*-
#
#*******************************************************************************
#
#  Copyright 2022 RIEGL Laser Measurement Systems
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.
#
#  SPDX-License-Identifier: Apache-2.0
#
#*******************************************************************************
#
"""
RDB library context
"""

from ctypes import byref, c_void_p, c_char_p, c_int32, c_uint32

from . import error
from . import library
from . import utilities

RDB_SUCCESS = 1  # API function terminated successfully
RDB_FAILURE = 0  # API function execution failed


class Context:
    """
    Library context
    """

    def __init__(self, log_level=None, log_path=None):
        """
        Create RDB context

        Use log_path to specify the target folder for RDB log files. If not
        defined (None), then system"s folder for temporary files is used
        (i.e. Windows: "C:\\Users\\*\\AppData\\Local\\Temp", Linux: "/tmp").
        If the path does not exist, it is not created and logging is disabled.

        Use log_level to specify a filter for log messages. Allowed values are

          | Level   | Description                |
          | ------- | -------------------------- |
          | TRACE   | many debug messages        |
          | DEBUG   | some debug messages        |
          | TEXT    | general messages = default |
          | INFO    | hints, information         |
          | WARNING | warning messages           |
          | ERROR   | error messages             |
          | FATAL   | fatal errors               |
          | NONE    | no log output at all       |

        Whereas "TRACE" is the highest log level and means to output everything
        and "NONE" is the lowest level and means to output nothing. Example:
        if log_level is set to "TEXT", debug messages are not output but info,
        warnings, errors and fatal errors are.

        Both logPath and logLevel may also be given as environment variables
        "RDB_LOG_PATH" and "RDB_LOG_LEVEL". Please note that those variables are
        used only if empty strings are passed to the constructor.

        Note: When the context is deleted, the log file is also deleted but
              only if it does not contain WARNING, ERROR or FATAL messages.
              To keep the context from deleting the log file, append "!" to
              the log_level, e.g. "TEXT!".
        """
        self.handle = c_void_p(None)
        self.check(library.handle.rdb_context_new(
            byref(self.handle),
            utilities.to_rdb_string(log_level),
            utilities.to_rdb_string(log_path)
        ))

    def __del__(self):
        """Delete RDB context"""
        if self.handle != c_void_p(None):
            # don't care about return value here as context is deleted
            library.handle.rdb_context_delete(byref(self.handle))
            self.handle = c_void_p(None)

    def __enter__(self):
        pass

    def __exit__(self, exc_type, exc_val, exc_tb):
        pass

    @property
    def library_name(self):
        """Return library name"""
        text = c_char_p(None)
        self.check(library.handle.rdb_library_name(
            self.handle, byref(text)
        ))
        return utilities.to_std_string(text)

    @property
    def library_filename(self):
        """Return the name of the file the library was loaded from"""
        text = c_char_p(None)
        self.check(library.handle.rdb_library_filename(
            self.handle, byref(text)
        ))
        return utilities.to_std_string(text)

    @property
    def library_version(self):
        """Return library version string"""
        text = c_char_p(None)
        self.check(library.handle.rdb_library_version(
            self.handle, byref(text)
        ))
        return utilities.to_std_string(text)

    @property
    def library_license(self):
        """Return library license text"""
        text = c_char_p(None)
        self.check(library.handle.rdb_library_license(
            self.handle, byref(text)
        ))
        return utilities.to_std_string(text)

    @property
    def database_file_type_title(self):
        """Database file type title"""
        text = c_char_p(None)
        self.check(library.handle.rdb_database_file_type_title(
            self.handle, byref(text)
        ))
        return utilities.to_std_string(text)

    @property
    def database_file_type_suffix(self):
        """Database file type suffix"""
        text = c_char_p(None)
        self.check(library.handle.rdb_database_file_type_suffix(
            self.handle, byref(text)
        ))
        return utilities.to_std_string(text)

    def database_file_type_check(self, location):
        """
        Check file type

        Returns True if the given location is a RDB 2 database file.
        """
        buffer = c_uint32(0)
        self.check(library.handle.rdb_database_file_type_check(
            self.handle,
            utilities.to_rdb_string(location),
            byref(buffer)
        ))
        return buffer.value == 1

    def check(self, code):
        """
        Check if RDB API call succeeded

        Args:
            code: API call return value
        """
        if code != RDB_SUCCESS:
            error_code = c_int32(1)
            error_text = c_char_p(None)
            error_details = c_char_p(None)
            return_value = library.handle.rdb_context_get_last_error(
                self.handle,
                byref(error_code),
                byref(error_text),
                byref(error_details)
            )
            if return_value == RDB_SUCCESS:
                raise error.Error(
                    error_code.value,
                    utilities.to_std_string(error_text),
                    utilities.to_std_string(error_details)
                )
            else:
                raise RuntimeError("Unknown error")

    @staticmethod
    def check_cf(result):
        """
        Check if context-free RDB API call succeeded

        Args:
            result: API call return value
        """
        check_cf = Context.check_cf
        result = c_void_p(result)
        error_code = c_int32(0)
        error_text = c_char_p(None)
        error_details = c_char_p(None)
        try:
            if result:
                check_cf(library.handle.rdb_result_get_error_code_cf   (result, byref(error_code)))
                check_cf(library.handle.rdb_result_get_error_text_cf   (result, byref(error_text)))
                check_cf(library.handle.rdb_result_get_error_details_cf(result, byref(error_details)))
                raise error.Error(
                    error_code.value,
                    utilities.to_std_string(error_text),
                    utilities.to_std_string(error_details)
                )
        finally:
            if error_details:
                check_cf(library.handle.rdb_string_delete_cf(byref(error_details)))
            if error_text:
                check_cf(library.handle.rdb_string_delete_cf(byref(error_text)))
            if result:
                check_cf(library.handle.rdb_result_delete_cf(byref(result)))
