#include "hexfin.h"

#include <stdio.h>
#include <stdlib.h>

#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#define dlsym(x, y) (void*)GetProcAddress(x, y)
#else // _WIN32
#include <dlfcn.h>
#endif // _WIN32

#define BLOCK_SIZE 128

static const float lowest_frequency = 27.5f; // Note A0
static const float highest_frequency = 1567.98206f; // Note G6

enum JackOptions {
  JackNullOption = 0x00,
  JackNoStartServer = 0x01,
  JackUseExactName = 0x02,
  JackServerName = 0x04,
  JackLoadName = 0x08,
  JackLoadInit = 0x10,
  JackSessionID = 0x20
};

enum JackStatus {
  JackFailure = 0x01,
  JackInvalidOption = 0x02,
  JackNameNotUnique = 0x04,
  JackServerStarted = 0x08,
  JackServerFailed = 0x10,
  JackServerError = 0x20,
  JackNoSuchClient = 0x40,
  JackLoadFailure = 0x80,
  JackInitFailure = 0x100,
  JackShmFailure = 0x200,
  JackVersionError = 0x400,
  JackBackendError = 0x800,
  JackClientZombie = 0x1000
};

enum JackPortFlags {
  JackPortIsInput = 0x1,
  JackPortIsOutput = 0x2,
  JackPortIsPhysical = 0x4,
  JackPortCanMonitor = 0x8,
  JackPortIsTerminal = 0x10,
};

typedef enum JackStatus jack_status_t;
typedef enum JackOptions jack_options_t;
typedef struct _jack_port jack_port_t;
typedef struct _jack_client jack_client_t;
typedef unsigned int jack_nframes_t;
typedef float jack_default_audio_sample_t;
typedef int (*JackProcessCallback)(jack_nframes_t nframes, void* arg);

typedef jack_client_t* (*jack_client_openFunc)(const char*, jack_options_t, jack_status_t*, ...);
static jack_client_openFunc jack_client_open;
typedef const char** (*jack_get_portsFunc)(jack_client_t* client, const char* port_name_pattern, const char* type_name_pattern, unsigned int flags);
static jack_get_portsFunc jack_get_ports;
typedef int(*jack_set_process_callbackFunc)(jack_client_t* client, JackProcessCallback process_callback, void* arg);
static jack_set_process_callbackFunc jack_set_process_callback;
typedef const char* (*jack_port_nameFunc)(const jack_port_t* port);
static jack_port_nameFunc jack_port_name;
typedef int(*jack_connectFunc)(jack_client_t* client, const char* source_port, const char* destination_port);
static jack_connectFunc jack_connect;
typedef void* (*jack_port_get_bufferFunc)(jack_port_t* port, jack_nframes_t);
static jack_port_get_bufferFunc jack_port_get_buffer;
typedef jack_port_t* (*jack_port_registerFunc)(jack_client_t* client, const char* port_name, const char* port_type, unsigned int flags, unsigned int buffer_size);
static jack_port_registerFunc jack_port_register;
typedef int(*jack_activateFunc)(jack_client_t* client);
static jack_activateFunc jack_activate;

static jack_port_t* input_in0;

static int process(const jack_nframes_t blockSize, void* arg)
{
  struct hexfin_context* ctx = (struct hexfin_context*)arg;

  const float* in0 = (jack_default_audio_sample_t*)jack_port_get_buffer(input_in0, blockSize);
  hexfin_processBlock(ctx, in0, blockSize, 48000.0f, lowest_frequency, highest_frequency);

  return 0;
}

static void initJack(void* arg)
{
#ifdef _WIN32
  HMODULE hinstLib = LoadLibraryA("libjack64.dll");
#else // _WIN32
  void* hinstLib = dlopen("libjack.so.0", RTLD_LAZY | RTLD_LOCAL);
#endif // _WIN32

  if (hinstLib == NULL)
  {
    fprintf(stderr, "Failed to load libjack.\n");
    exit(-1);
  }

  {
    jack_client_open = (jack_client_openFunc)dlsym(hinstLib, "jack_client_open");
    jack_get_ports = (jack_get_portsFunc)dlsym(hinstLib, "jack_get_ports");
    jack_set_process_callback = (jack_set_process_callbackFunc)dlsym(hinstLib, "jack_set_process_callback");
    jack_port_name = (jack_port_nameFunc)dlsym(hinstLib, "jack_port_name");
    jack_connect = (jack_connectFunc)dlsym(hinstLib, "jack_connect");
    jack_port_get_buffer = (jack_port_get_bufferFunc)dlsym(hinstLib, "jack_port_get_buffer");
    jack_port_register = (jack_port_registerFunc)dlsym(hinstLib, "jack_port_register");
    jack_activate = (jack_activateFunc)dlsym(hinstLib, "jack_activate");
  }

  jack_options_t options = JackNullOption;
  jack_status_t status;
  jack_client_t* client = jack_client_open("Hexfin", options, &status, NULL); // if you got a crash here on Windows, something is wrong with JACK. Delete %temp%/jack_db or temporarily rename %systemroot%/libjack64_.dll

  if (client == NULL)
  {
    fprintf(stderr, "jack_client_open failed.\n");
    exit(-2);
  }

  jack_set_process_callback(client, process, arg);

  {
    input_in0 = jack_port_register(client, "Tuner", "32 bit float mono audio", JackPortIsInput, 0);
  }

  jack_activate(client);

  { // setup connection
    const char** capturePorts = jack_get_ports(client, NULL, NULL, JackPortIsPhysical | JackPortIsOutput);
    if (capturePorts[0] != NULL)
      jack_connect(client, capturePorts[0], jack_port_name(input_in0));
  }
}

int main(int argc, char* argv[])
{
  struct hexfin_context ctx = hexfin_create_context(48000.0f, lowest_frequency, highest_frequency);

  initJack(&ctx);
  for (;;)
    printf("%f\n", ctx.loudEnough ? ctx.frequency : 0.0f);

  hexfin_destroy_context(&ctx);

  return 0;
}
