{
  Copyright (C)
  2026 - Uladzimir Karpenka, EW8BAK

  This program is free software; you can redistribute it and/or
  modify it under the terms of the GNU General Public License
  as published by the Free Software Foundation; either version 2
  of the License, or (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
}

unit DMRBindings;

{$IFDEF FPC}
  {$MODE Delphi}
{$ENDIF}

interface

uses
  Classes, SysUtils, DynLibs;

const
  DMR_ABI_VERSION = 1;
  DMR_AMBE_BITS = 96;
  DMR_PCM_SAMPLES = 160;

type
  TDMRMbeResult = packed record
    ErrorsC0: LongInt;
    ErrorsTotal: LongInt;
  end;
  PDMRMbeResult = ^TDMRMbeResult;

function DMRLoad: Boolean;
procedure DMRUnload;
function DMRLoaded: Boolean;
function DMRLastError: string;

function DMRMbeCreate(UVQuality: Integer): Pointer;
procedure DMRMbeDestroy(Decoder: Pointer);
procedure DMRMbeReset(Decoder: Pointer);
function DMRMbeDecode(Decoder: Pointer; Bits: PByte; PCM: PSingle;
  ResultInfo: PDMRMbeResult): Integer;

implementation

type
  TAbiVersion = function: LongWord; cdecl;
  TMbeCreate = function(UVQuality: LongInt): Pointer; cdecl;
  TMbeDestroy = procedure(Decoder: Pointer); cdecl;
  TMbeReset = procedure(Decoder: Pointer); cdecl;
  TMbeDecode = function(Decoder: Pointer; Bits: PByte; PCM: PSingle;
    ResultInfo: Pointer): LongInt; cdecl;

var
  GLib: TLibHandle = dynlibs.NilHandle;
  GLastError: string = '';
  GAbiVersion: TAbiVersion = nil;
  GMbeCreate: TMbeCreate = nil;
  GMbeDestroy: TMbeDestroy = nil;
  GMbeReset: TMbeReset = nil;
  GMbeDecode: TMbeDecode = nil;
  GLoadRefs: Integer = 0;

function Symbol(const Name: PChar): Pointer;
begin
  Result := GetProcedureAddress(GLib, Name);
  if Result = nil then
    GLastError := 'Missing DMR symbol: ' + string(Name);
end;

function DMRLoad: Boolean;
const
  {$IFDEF WINDOWS}
  Names: array[0..0] of string = ('ewsdr_dmr.dll');
  {$ELSE}
  {$IFDEF DARWIN}
  Names: array[0..0] of string = ('libewsdr_dmr.dylib');
  {$ELSE}
  Names: array[0..1] of string = ('libewsdr_dmr.so', 'libewsdr_dmr.so.1');
  {$ENDIF}
  {$ENDIF}
var
  i: Integer;
  LocalName, BuildName: string;
begin
  if GLib <> dynlibs.NilHandle then
  begin
    Inc(GLoadRefs);
    Exit(True);
  end;
  GLastError := '';
  for i := Low(Names) to High(Names) do
  begin
    LocalName := ExtractFilePath(ParamStr(0)) + Names[i];
    GLib := LoadLibrary(LocalName);
    // Lazarus debug binaries live in bin/<target>/ while the documented
    // optional adapter build lives in build/dmr. Make an in-tree build
    // immediately usable without a system-wide cmake --install.
    if GLib = dynlibs.NilHandle then
    begin
      BuildName := ExpandFileName(ExtractFilePath(ParamStr(0)) +
        '../../build/dmr/' + Names[i]);
      GLib := LoadLibrary(BuildName);
    end;
    if GLib = dynlibs.NilHandle then GLib := LoadLibrary(Names[i]);
    if GLib <> dynlibs.NilHandle then Break;
  end;
  if GLib = dynlibs.NilHandle then
  begin
    GLastError := 'Optional EWSDR DMR library not found';
    Exit(False);
  end;

  GAbiVersion := TAbiVersion(Symbol('ewsdr_dmr_abi_version'));
  GMbeCreate := TMbeCreate(Symbol('ewsdr_dmr_mbe_create'));
  GMbeDestroy := TMbeDestroy(Symbol('ewsdr_dmr_mbe_destroy'));
  GMbeReset := TMbeReset(Symbol('ewsdr_dmr_mbe_reset'));
  GMbeDecode := TMbeDecode(Symbol('ewsdr_dmr_mbe_decode'));
  if not Assigned(GAbiVersion) or not Assigned(GMbeCreate) or
     not Assigned(GMbeDestroy) or not Assigned(GMbeReset) or
     not Assigned(GMbeDecode) then
  begin
    DMRUnload;
    Exit(False);
  end;
  if GAbiVersion() <> DMR_ABI_VERSION then
  begin
    GLastError := Format('Unsupported EWSDR DMR ABI: %d', [GAbiVersion()]);
    DMRUnload;
    Exit(False);
  end;
  GLoadRefs := 1;
  Result := True;
end;

procedure DMRUnload;
begin
  if GLoadRefs > 1 then
  begin
    Dec(GLoadRefs);
    Exit;
  end;
  GLoadRefs := 0;
  GAbiVersion := nil;
  GMbeCreate := nil;
  GMbeDestroy := nil;
  GMbeReset := nil;
  GMbeDecode := nil;
  if GLib <> dynlibs.NilHandle then
  begin
    UnloadLibrary(GLib);
    GLib := dynlibs.NilHandle;
  end;
end;

function DMRLoaded: Boolean;
begin
  Result := GLib <> dynlibs.NilHandle;
end;

function DMRLastError: string;
begin
  Result := GLastError;
end;

function DMRMbeCreate(UVQuality: Integer): Pointer;
begin
  if Assigned(GMbeCreate) then Result := GMbeCreate(UVQuality)
  else Result := nil;
end;

procedure DMRMbeDestroy(Decoder: Pointer);
begin
  if Assigned(GMbeDestroy) and (Decoder <> nil) then GMbeDestroy(Decoder);
end;

procedure DMRMbeReset(Decoder: Pointer);
begin
  if Assigned(GMbeReset) and (Decoder <> nil) then GMbeReset(Decoder);
end;

function DMRMbeDecode(Decoder: Pointer; Bits: PByte; PCM: PSingle;
  ResultInfo: PDMRMbeResult): Integer;
begin
  if not Assigned(GMbeDecode) then Exit(-1);
  Result := GMbeDecode(Decoder, Bits, PCM, ResultInfo);
end;

end.
