#include <stdio.h>
#include <dlfcn.h>
#include <string.h>
#include "ownet.h"
#include <gmodule.h>
#include "config.h"

void (*msDelay)(int len);
int (*msGettick)(void);
void (*owShowVersion)(void);

SMALLINT (*owFirst)(int portnum, SMALLINT do_reset, SMALLINT alarm_only);
SMALLINT (*owNext)(int portnum, SMALLINT do_reset, SMALLINT alarm_only);
void (*owSerialNum)(int portnum, uchar *serialnum_buf, SMALLINT do_read);
void (*owFamilySearchSetup)(int portnum, SMALLINT search_family);
void (*owSkipFamily)(int portnum);
SMALLINT (*owAccess)(int portnum);
SMALLINT (*owVerify)(int portnum, SMALLINT alarm_only);
SMALLINT (*owOverdriveAccess)(int portnum);

// external One Wire functions defined in owsesu.c
SMALLINT (*owAcquire__)(int portnum, char *port_zstr);
int (*owAcquireEx__)(char *port_zstr);
void (*owRelease)(int portnum);
SMALLINT (*owProgramByte)
(int portnum, SMALLINT write_byte, int addr, SMALLINT write_cmd, SMALLINT crc_type, SMALLINT do_access);

// external One Wire functions from link layer owllu.c
SMALLINT (*owTouchReset)(int portnum);
SMALLINT (*owTouchBit)(int portnum, SMALLINT sendbit);
SMALLINT (*owTouchByte)(int portnum, SMALLINT sendbyte);
SMALLINT (*owWriteByte)(int portnum, SMALLINT sendbyte);
SMALLINT (*owReadByte)(int portnum);
SMALLINT (*owSpeed)(int portnum, SMALLINT new_speed);
SMALLINT (*owLevel)(int portnum, SMALLINT new_level);
SMALLINT (*owProgramPulse)(int portnum);
SMALLINT (*owWriteBytePower)(int portnum, SMALLINT sendbyte);
SMALLINT (*owReadBytePower)(int portnum);
SMALLINT (*owHasPowerDelivery)(int portnum);
SMALLINT (*owHasProgramPulse)(int portnum);
SMALLINT (*owHasOverDrive)(int portnum);
SMALLINT (*owReadBitPower)(int portnum, SMALLINT applyPowerResponse);

// external One Wire functions from transaction layer in owtrnu.c
SMALLINT (*owBlock)(int portnum, SMALLINT do_reset, uchar *tran_buf, SMALLINT tran_len);
SMALLINT owReadPacketStd(int portnum, SMALLINT do_access, int start_page, uchar *read_buf);
SMALLINT owWritePacketStd(int portnum, int start_page, uchar *write_buf, SMALLINT write_len, SMALLINT is_eprom,
                          SMALLINT crc_type);
SMALLINT (*owProgramByte)
(int portnum, SMALLINT write_byte, int addr, SMALLINT write_cmd, SMALLINT crc_type, SMALLINT do_access);

SMALLINT FAMILY_CODE_04_ALARM_TOUCHRESET_COMPLIANCE;
static void *handle;

char *w1_module_build_path(const char *dir, const char *name) {
// Once GLIB actually provides / documents a replacement, the pragmas can be removed
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
  return g_module_build_path(dir, name);
#pragma GCC diagnostic push
}

static void loadiolib(char *port) {
  char *name;

  if (strncmp(port, "DS2490", sizeof("DS2490") - 1) == 0
      || strncmp(port, "USB", sizeof("USB") - 1) == 0) {
    name = "libw1usb.so";
    FAMILY_CODE_04_ALARM_TOUCHRESET_COMPLIANCE = TRUE;
  } else {
    name = "libw1serial.so";
    FAMILY_CODE_04_ALARM_TOUCHRESET_COMPLIANCE = FALSE;
  }

  char *sofile = w1_module_build_path(PACKAGE_LIB_DIR, name);

  handle = g_module_open(sofile, G_MODULE_BIND_LOCAL);
  if (handle == NULL) {
    const gchar *gerr;
    gerr = g_module_error();
    fprintf(stderr,
            "Can't open the device library %s\n"
            "System returned:\n%s\n"
            "This is typically a build dependency or installation error\n",
            name, gerr);
    exit(1);
  }
  g_free(sofile);
  g_module_symbol(handle, "owShowVersion_", (void *)&owShowVersion);
  g_module_symbol(handle, "msDelay_", (void *)&msDelay);
  g_module_symbol(handle, "msGettick_", (void *)&msGettick);
  g_module_symbol(handle, "owHasOverDrive_", (void *)&owHasOverDrive);
  g_module_symbol(handle, "owHasPowerDelivery_", (void *)&owHasPowerDelivery);
  g_module_symbol(handle, "owHasProgramPulse_", (void *)&owHasProgramPulse);
  g_module_symbol(handle, "owLevel_", (void *)&owLevel);
  g_module_symbol(handle, "owProgramPulse_", (void *)&owProgramPulse);
  g_module_symbol(handle, "owReadBitPower_", (void *)&owReadBitPower);
  g_module_symbol(handle, "owReadByte_", (void *)&owReadByte);
  g_module_symbol(handle, "owReadBytePower_", (void *)&owReadBytePower);
  g_module_symbol(handle, "owSpeed_", (void *)&owSpeed);
  g_module_symbol(handle, "owTouchBit_", (void *)&owTouchBit);
  g_module_symbol(handle, "owTouchByte_", (void *)&owTouchByte);
  g_module_symbol(handle, "owTouchReset_", (void *)&owTouchReset);
  g_module_symbol(handle, "owWriteByte_", (void *)&owWriteByte);
  g_module_symbol(handle, "owWriteBytePower_", (void *)&owWriteBytePower);
  g_module_symbol(handle, "owAccess_", (void *)&owAccess);
  g_module_symbol(handle, "owFamilySearchSetup_", (void *)&owFamilySearchSetup);
  g_module_symbol(handle, "owFirst_", (void *)&owFirst);
  g_module_symbol(handle, "owNext_", (void *)&owNext);
  g_module_symbol(handle, "owOverdriveAccess_", (void *)&owOverdriveAccess);
  g_module_symbol(handle, "owSerialNum_", (void *)&owSerialNum);
  g_module_symbol(handle, "owSkipFamily_", (void *)&owSkipFamily);
  g_module_symbol(handle, "owVerify_", (void *)&owVerify);
  g_module_symbol(handle, "owAcquire_", (void *)&owAcquire__);
  g_module_symbol(handle, "owAcquireEx_", (void *)&owAcquireEx__);
  g_module_symbol(handle, "owRelease_", (void *)&owRelease);
  g_module_symbol(handle, "owBlock_", (void *)&owBlock);
  g_module_symbol(handle, "owProgramByte_", (void *)&owProgramByte);
}

int owAcquireEx(char *port_zstr) {
  if (handle == NULL) {
    loadiolib(port_zstr);
  }
  return owAcquireEx__(port_zstr);
}

SMALLINT owAcquire(int portnum, char *port_zstr) {
  if (handle == NULL) {
    loadiolib(port_zstr);
  }
  return owAcquire__(portnum, port_zstr);
}
