{*
 *******************************************************************************
 *
 *  Copyright 2026 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
 *
 *******************************************************************************
 *}
{*!
 *******************************************************************************
 *
 * \file    riegl.rdb.context.pas
 * \author  RIEGL LMS GmbH, Austria
 * \brief   RDB library context (Pascal wrapper code)
 * \version 2015-10-14/AW: Initial version
 * \version 2017-01-30/AW: Added function to check if a file is a RDB 2 file
 * \version 2020-07-03/AW: Added context-free function result check function
 * \version 2025-01-29/AW: Added function rdb_context_new_with_api() (#5474)
 *
 *******************************************************************************
 *}

unit riegl.rdb.context;

{******************************************************************************}
{***} INTERFACE {**************************************************************}
{******************************************************************************}

//---< INCLUDES >---------------------------------------------------------------

uses
  riegl.rdb;

//---< RDB LIBRARY IMPORTS >----------------------------------------------------

//______________________________________________________________________________
{*!
 * \brief Create new library context
 *
 * You need to create a new context before you can call any library function.
 *
 * Use _logPath_ to specify the target folder for RDB log files. If not
 * defined (empty string), then system's folder for temporary files is
 * used (i.e. Windows: "C:\Users\*\AppData\Local\Temp", Linux: "/tmp"). If
 * the given path does not exist, it is not created and logging is disabled.
 *
 * Use _logLevel_ 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 _logLevel_ 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 _logLevel_, e.g. "TEXT!".
 *}
function rdb_context_new(
  var Handle : TRDBContextHandle; //!< [out] handle of created object
  LogLevel   : TRDBString;        //!< [in] log level (filter), see description
  LogPath    : TRDBString         //!< [in] target folder for RDB log files
) : TRDBResult; cdecl; external riegl.rdb.RDB_LIBRARY_FILENAME;
//
function rdb_context_new_with_api(
  var Handle : TRDBContextHandle; //!< [out] handle of created object
  ApiVersion : TRDBString;        //!< [in] API version string, e.g. "RDB Interface 2.5.2-389b3d29 (C++, Jan 28 2025, 13:15:17)"
  LogLevel   : TRDBString;        //!< [in] log level (filter), see description
  LogPath    : TRDBString         //!< [in] target folder for RDB log files
) : TRDBResult; cdecl; external riegl.rdb.RDB_LIBRARY_FILENAME;

//______________________________________________________________________________
{*!
 * \brief Delete library context
 *}
function rdb_context_delete(
  var Handle : TRDBContextHandle //!< [in] handle of object to delete
) : TRDBResult; cdecl; external riegl.rdb.RDB_LIBRARY_FILENAME;

//______________________________________________________________________________
{*!
 * \brief Last error details
 *
 * Use this function to query the code and message of the last error.
 *}
function rdb_context_get_last_error(
  Handle  : TRDBContextHandle; //!< [in] library context
  Code    : PLongint;          //!< [out] error code
  Text    : PRDBString = nil;  //!< [out] error text, set to nil if not needed
  Details : PRDBString = nil   //!< [out] error details, set to nil if not needed
) : TRDBResult; cdecl; external riegl.rdb.RDB_LIBRARY_FILENAME;

//______________________________________________________________________________
{*!
 * \brief Returns library name
 *}
function rdb_library_name(
  Handle   : TRDBContextHandle; //!< [in] library context
  var Name : TRDBString         //!< [out] library name
) : TRDBResult; cdecl; external riegl.rdb.RDB_LIBRARY_FILENAME;

//______________________________________________________________________________
{*!
 * \brief Returns the name of the file the library was loaded from
 * \since 2.3.0
 *}
function rdb_library_filename(
  Handle       : TRDBContextHandle; //!< [in] library context
  var Filename : TRDBString         //!< [out] library name
) : TRDBResult; cdecl; external riegl.rdb.RDB_LIBRARY_FILENAME;

//______________________________________________________________________________
{*!
 * \brief Returns library version string
 *}
function rdb_library_version(
  Handle      : TRDBContextHandle; //!< [in] library context
  var Version : TRDBString         //!< [out] libary version
) : TRDBResult; cdecl; external riegl.rdb.RDB_LIBRARY_FILENAME;

//______________________________________________________________________________
{*!
 * \brief Returns library license text
 *}
function rdb_library_license(
  Handle      : TRDBContextHandle; //!< [in] library context
  var License : TRDBString         //!< [out] library license text
) : TRDBResult; cdecl; external riegl.rdb.RDB_LIBRARY_FILENAME;

//______________________________________________________________________________
{*!
 * \brief Database file type title
 *
 * \returns "RDB 2 Database File"
 *}
function rdb_database_file_type_title(
  Handle    : TRDBContextHandle; //!< [in] library context
  var Title : TRDBString         //!< [out] database file type title
) : TRDBResult; cdecl; external riegl.rdb.RDB_LIBRARY_FILENAME;

//______________________________________________________________________________
{*!
 * \brief Database file type suffix
 *
 * \returns "rdbx"
 *}
function rdb_database_file_type_suffix(
  Handle     : TRDBContextHandle; //!< [in] library context
  var Suffix : TRDBString         //!< [out] database file type suffix
) : TRDBResult; cdecl; external riegl.rdb.RDB_LIBRARY_FILENAME;

//______________________________________________________________________________
{*!
 * \brief Check file type
 *
 * \returns true if the given location is a RDB 2 database file.
 *}
function rdb_database_file_type_check(
  Handle    : TRDBContextHandle; //!< [in] library context
  Location  : TRDBString;        //!< [in] database location (filename)
  var isRDB : Cardinal           //!< [out] 0: false, 1: true
) : TRDBResult; cdecl; external riegl.rdb.RDB_LIBRARY_FILENAME;

//---< CLASS TRDBContext >------------------------------------------------------
{*
 * \brief Library context
 *}
type
  TRDBContext = class(System.TObject)
  public
    Handle : TRDBContextHandle;

    {*!
     * \brief Constructor
     *
     * Use _logPath_ to specify the target folder for RDB log files. If not
     * defined (empty string), then system's folder for temporary files is
     * used (i.e. Windows: "C:\Users\*\AppData\Local\Temp", Linux: "/tmp").
     *
     * Use _logLevel_ 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 _logLevel_ 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 _logLevel_, e.g. "TEXT!".
     *}
    constructor Create(
      const LogLevel : String = ''; //!< [in] log level (filter), see description
      const LogPath  : String = ''  //!< [in] target folder for RDB log files
    ); reintroduce;

    {*!
     * \brief Destructor
     *}
    destructor Destroy(); override;

    {*!
     * \brief Check return code of C-API call
     *}
    function Check(
      const Code   : riegl.rdb.TRDBResult;  //!< C-API call return value
      const Silent : System.Boolean = FALSE //!< FALSE: raise exception (ERDBError), TRUE: set return-value only
    ) : System.Boolean; overload;

    {*!
     * \brief Check return value of context-free C-API call
     *}
    class function Check(
      Value  : riegl.rdb.TRDBResultCF; //!< C-API context-free function call return value
      Silent : System.Boolean = FALSE  //!< FALSE: raise exception (ERDBError), TRUE: set return-value only
    ) : System.Boolean; overload;

    {*!
     * \brief Returns library name
     *}
    function LibraryName() : System.String;

    {*!
     * \brief Returns the name of the file the library was loaded from
     * \since 2.3.0
     *}
    function LibraryFilename() : System.String;

    {*!
     * \brief Returns library version string
     *}
    function LibraryVersion() : System.String;

    {*!
     * \brief Returns library license text
     *}
    function LibraryLicense() : System.String;

    {*!
     * \brief Database file type title
     *
     * \returns "RDB 2 Database File"
     *}
    function DatabaseFileTypeTitle() : System.String;

    {*!
     * \brief Database file type suffix
     *
     * \returns "rdbx"
     *}
    function DatabaseFileTypeSuffix() : System.String;

    {*!
     * \brief Check file type
     *
     * \returns true if the given location is a RDB 2 database file.
     *}
    function DatabaseFileTypeCheck(const Location : System.String) : Boolean;
  end;

{******************************************************************************}
{***} IMPLEMENTATION {*********************************************************}
{******************************************************************************}

//---< INCLUDES >---------------------------------------------------------------

uses
  riegl.rdb.error,
  riegl.rdb.result,
  riegl.rdb.version;

//---< TRDBContext::PUBLIC >----------------------------------------------------

constructor TRDBContext.Create(
  const LogLevel : String;
  const LogPath  : String
);
var
  ApiVersion : String;
begin
  inherited Create;
  ApiVersion := RIEGL_RDB_INTERFACE_NAME + ' ' + RIEGL_RDB_INTERFACE_VERSION;
  rdb_context_new_with_api(Handle, TRDBString(AnsiToUtf8(ApiVersion)), TRDBString(AnsiToUtf8(LogLevel)), TRDBString(AnsiToUtf8(LogPath)));
end;

destructor TRDBContext.Destroy();
begin
  rdb_context_delete(Handle);
  inherited;
end;

function TRDBContext.Check(
  const Code   : riegl.rdb.TRDBResult;
  const Silent : System.Boolean
) : System.Boolean;
var
  Error : record
    Code    : Longint;
    Text    : TRDBString;
    Details : TRDBString;
  end;
begin
  Result := (Code = RDB_SUCCESS);
  if (not Result) and (not Silent) then
  begin
    FillChar(Error, SizeOf(Error), 0); // = unknown error
    rdb_context_get_last_error(Handle, @Error.Code, @Error.Text, @Error.Details);
    raise ERDBError.Create(Error.Code, AsSTDString(Error.Text), AsSTDString(Error.Details));
  end;
end;

class function TRDBContext.Check(
  Value  : riegl.rdb.TRDBResultCF;
  Silent : System.Boolean
) : System.Boolean;
var
  Error : record
    Code    : Longint;
    Text    : TRDBString;
    Details : TRDBString;
  end;
begin
  FillChar(Error, SizeOf(Error), 0); // = unknown error
  try
    Result := (Value = nil);
    if (not Result) and (not Silent) then
    begin
      Check(rdb_result_get_error_code_cf   (Value, Error.Code),    TRUE);
      Check(rdb_result_get_error_text_cf   (Value, Error.Text),    TRUE);
      Check(rdb_result_get_error_details_cf(Value, Error.Details), TRUE);
      raise ERDBError.Create(Error.Code, AsSTDString(Error.Text), AsSTDString(Error.Details));
    end;
  finally
    if (Error.Details <> nil) then Check(rdb_string_delete_cf(Error.Details), TRUE);
    if (Error.Text    <> nil) then Check(rdb_string_delete_cf(Error.Text),    TRUE);
    if (Value         <> nil) then Check(rdb_result_delete_cf(Value),         TRUE);
  end;
end;

function TRDBContext.LibraryName() : System.String;
var
  Value : TRDBString;
begin
  Check(rdb_library_name(Handle, Value));
  Result := AsSTDString(Value);
end;

function TRDBContext.LibraryFilename() : System.String;
var
  Value : TRDBString;
begin
  Check(rdb_library_filename(Handle, Value));
  Result := AsSTDString(Value);
end;

function TRDBContext.LibraryVersion() : System.String;
var
  Value : TRDBString;
begin
  Check(rdb_library_version(Handle, Value));
  Result := AsSTDString(Value);
end;

function TRDBContext.LibraryLicense() : System.String;
var
  Value : TRDBString;
begin
  Check(rdb_library_license(Handle, Value));
  Result := AsSTDString(Value);
end;

function TRDBContext.DatabaseFileTypeTitle() : System.String;
var
  Value : TRDBString;
begin
  Check(rdb_database_file_type_title(Handle, Value));
  Result := AsSTDString(Value);
end;

function TRDBContext.DatabaseFileTypeSuffix() : System.String;
var
  Value : TRDBString;
begin
  Check(rdb_database_file_type_suffix(Handle, Value));
  Result := AsSTDString(Value);
end;

function TRDBContext.DatabaseFileTypeCheck(const Location : System.String) : Boolean;
var
  Value : Cardinal;
begin
  Value := 0;
  Check(rdb_database_file_type_check(Handle, TRDBString(AnsiToUtf8(Location)), Value));
  Result := (not (Value = 0));
end;

end.