// SPDX-License-Identifier: Unlicense

#include "tunerThread.h"

#ifdef SHR3D_SFX_CORE_HEXFIN

#include "hexfin.h"
#include <thread>

#ifdef PLATFORM_WINDOWS
#include <Windows.h>
#endif // PLATFORM_WINDOWS

f32 TunerThread::inBlock[Const::audioMaximumPossibleBlockSize];
#ifdef SHR3D_SFX_CORE_HEXFIN_DIVIDED
f32 TunerThread::inBlock0[Const::audioMaximumPossibleBlockSize];
f32 TunerThread::inBlock1[Const::audioMaximumPossibleBlockSize];
f32 TunerThread::inBlock2[Const::audioMaximumPossibleBlockSize];
f32 TunerThread::inBlock3[Const::audioMaximumPossibleBlockSize];
f32 TunerThread::inBlock4[Const::audioMaximumPossibleBlockSize];
f32 TunerThread::inBlock5[Const::audioMaximumPossibleBlockSize];
#endif // SHR3D_SFX_CORE_HEXFIN_DIVIDED

static const f32 frequencyLowestPossible = noteFrequency(MidiNote::A_0, 440.0f);
static const f32 frequencyHighestPossible = noteFrequency(MidiNote::G_6, 440.0f);
#ifdef SHR3D_SFX_CORE_HEXFIN_DIVIDED
static f32 frequency0Low = noteFrequency(MidiNote::E_4, 440.0f);
static f32 frequency0High = noteFrequency(MidiNote::E_6, 440.0f);
static f32 frequency1Low = noteFrequency(MidiNote::B_3, 440.0f);
static f32 frequency1High = noteFrequency(MidiNote::B_5, 440.0f);
static f32 frequency2Low = noteFrequency(MidiNote::G_3, 440.0f);
static f32 frequency2High = noteFrequency(MidiNote::G_5, 440.0f);
static f32 frequency3Low = noteFrequency(MidiNote::D_3, 440.0f);
static f32 frequency3High = noteFrequency(MidiNote::D_5, 440.0f);
static f32 frequency4Low = noteFrequency(MidiNote::A_2, 440.0f);
static f32 frequency4High = noteFrequency(MidiNote::A_4, 440.0f);
static f32 frequency5Low = noteFrequency(MidiNote::E_2, 440.0f);
static f32 frequency5High = noteFrequency(MidiNote::E_4, 440.0f);
#endif // SHR3D_SFX_CORE_HEXFIN_DIVIDED

// these are read and written by two threads. I guess it is not important to make it threadsafe
static bool work = false;
#ifdef SHR3D_SFX_CORE_HEXFIN_DIVIDED
static bool work0 = false;
static bool work1 = false;
static bool work2 = false;
static bool work3 = false;
static bool work4 = false;
static bool work5 = false;
#endif // SHR3D_SFX_CORE_HEXFIN_DIVIDED

#if defined(PLATFORM_WINDOWS) || defined(PLATFORM_LINUX)
static void setThreadPriorityProAudio()
{
#ifdef PLATFORM_WINDOWS
  typedef HANDLE(__stdcall* TAvSetMmThreadCharacteristicsPtr)(LPCWSTR TaskName, LPDWORD TaskIndex);
  HMODULE AvrtDll = LoadLibraryW(L"AVRT.dll");
  if (AvrtDll)
  {
    TAvSetMmThreadCharacteristicsPtr AvSetMmThreadCharacteristicsPtr = (TAvSetMmThreadCharacteristicsPtr)(void(*)()) GetProcAddress(AvrtDll, "AvSetMmThreadCharacteristicsW");
    DWORD taskIndex = 0;
    HANDLE hTask = AvSetMmThreadCharacteristicsPtr(L"Pro Audio", &taskIndex);
    ASSERT(hTask != NULL);
    FreeLibrary(AvrtDll);
  }
#endif // PLATFORM_WINDOWS
#ifdef PLATFORM_LINUX
  //struct sched_param param; // untested
  //param.sched_priority = sched_get_priority_max(SCHED_FIFO);  // Highest priority
  //if (sched_setscheduler(0, SCHED_FIFO, &param) == -1) {
  //  perror("sched_setscheduler failed");
  //}
#endif // PLATFORM_LINUX
}
#endif // PLATFORM_WINDOWS || PLATFORM_LINUX

static void Worker()
{
#if defined(PLATFORM_WINDOWS) || defined(PLATFORM_LINUX)
  setThreadPriorityProAudio();
#endif // PLATFORM_WINDOWS || PLATFORM_LINUX
  hexfin_context ctx = hexfin_create_context(f32(sampleRate()), frequencyLowestPossible, frequencyHighestPossible);
  for (;;)
  {
    if (work)
    {
      Global::frequencyMono = hexfin_processBlock(&ctx, TunerThread::inBlock, blockSize(), f32(sampleRate()), frequencyLowestPossible, frequencyHighestPossible);
      work = false;
    }
    std::this_thread::sleep_for(std::chrono::microseconds(100));
  }
}

#ifdef SHR3D_SFX_CORE_HEXFIN_DIVIDED
static void string0Worker()
{
  hexfin_context ctx = hexfin_create_context(f32(sampleRate()), frequencyLowestPossible, frequencyHighestPossible);
#if defined(PLATFORM_WINDOWS) || defined(PLATFORM_LINUX)
  setThreadPriorityProAudio();
#endif // PLATFORM_WINDOWS || PLATFORM_LINUX
  for (;;)
  {
    if (work0)
    {
      Global::frequency[0] = hexfin_processBlock(&ctx, TunerThread::inBlock0, blockSize(), f32(sampleRate()), frequency0Low, frequency0High);
      Global::volume[0] = ctx.volume;
      work0 = false;
    }
    std::this_thread::sleep_for(std::chrono::microseconds(100));
  }
}

static void string1Worker()
{
  hexfin_context ctx = hexfin_create_context(f32(sampleRate()), frequencyLowestPossible, frequencyHighestPossible);
#if defined(PLATFORM_WINDOWS) || defined(PLATFORM_LINUX)
  setThreadPriorityProAudio();
#endif // PLATFORM_WINDOWS || PLATFORM_LINUX
  for (;;)
  {
    if (work1)
    {
      Global::frequency[1] = hexfin_processBlock(&ctx, TunerThread::inBlock1, blockSize(), f32(sampleRate()), frequency1Low, frequency1High);
      Global::volume[1] = ctx.volume;
      work1 = false;
    }
    std::this_thread::sleep_for(std::chrono::microseconds(100));
  }
}

static void string2Worker()
{
  hexfin_context ctx = hexfin_create_context(f32(sampleRate()), frequencyLowestPossible, frequencyHighestPossible);
#if defined(PLATFORM_WINDOWS) || defined(PLATFORM_LINUX)
  setThreadPriorityProAudio();
#endif // PLATFORM_WINDOWS || PLATFORM_LINUX
  for (;;)
  {
    if (work2)
    {
      Global::frequency[2] = hexfin_processBlock(&ctx, TunerThread::inBlock2, blockSize(), f32(sampleRate()), frequency2Low, frequency2High);
      Global::volume[2] = ctx.volume;
      work2 = false;
    }
    std::this_thread::sleep_for(std::chrono::microseconds(100));
  }
}

static void string3Worker()
{
  hexfin_context ctx = hexfin_create_context(f32(sampleRate()), frequencyLowestPossible, frequencyHighestPossible);
#if defined(PLATFORM_WINDOWS) || defined(PLATFORM_LINUX)
  setThreadPriorityProAudio();
#endif // PLATFORM_WINDOWS || PLATFORM_LINUX
  for (;;)
  {
    if (work3)
    {
      Global::frequency[3] = hexfin_processBlock(&ctx, TunerThread::inBlock3, blockSize(), f32(sampleRate()), frequency3Low, frequency3High);
      Global::volume[3] = ctx.volume;
      work3 = false;
    }
    std::this_thread::sleep_for(std::chrono::microseconds(100));
  }
}

static void string4Worker()
{
  hexfin_context ctx = hexfin_create_context(f32(sampleRate()), frequencyLowestPossible, frequencyHighestPossible);
#if defined(PLATFORM_WINDOWS) || defined(PLATFORM_LINUX)
  setThreadPriorityProAudio();
#endif // PLATFORM_WINDOWS || PLATFORM_LINUX
  for (;;)
  {
    if (work4)
    {
      Global::frequency[4] = hexfin_processBlock(&ctx, TunerThread::inBlock4, blockSize(), f32(sampleRate()), frequency4Low, frequency4High);
      Global::volume[4] = ctx.volume;
      work4 = false;
    }
    std::this_thread::sleep_for(std::chrono::microseconds(100));
  }
}

static void string5Worker()
{
  hexfin_context ctx = hexfin_create_context(f32(sampleRate()), frequencyLowestPossible, frequencyHighestPossible);
#if defined(PLATFORM_WINDOWS) || defined(PLATFORM_LINUX)
  setThreadPriorityProAudio();
#endif // PLATFORM_WINDOWS || PLATFORM_LINUX
  for (;;)
  {
    if (work5)
    {
      Global::frequency[5] = hexfin_processBlock(&ctx, TunerThread::inBlock5, blockSize(), f32(sampleRate()), frequency5Low, frequency5High);
      Global::volume[5] = ctx.volume;
      work5 = false;
    }
    std::this_thread::sleep_for(std::chrono::microseconds(100));
  }
}
#endif // SHR3D_SFX_CORE_HEXFIN_DIVIDED

void TunerThread::tick()
{
  static std::thread worker(Worker);
  work = true;

#ifdef SHR3D_SFX_CORE_HEXFIN_DIVIDED
  if (IS_DIVIDED_PICKUP_ENABLED)
  {
    {
      static std::thread stringWorker0(string0Worker);
      const MidiNote lowestNote = getStringNoteZeroTuning(Global::highwayCtx, 0);
      frequency0Low = noteFrequency(MidiNote(to_underlying_(lowestNote)), Global::a4ReferenceFrequency);
      const MidiNote highestNote = MidiNote(to_underlying_(getStringNoteZeroTuning(Global::highwayCtx, 0)) + 24);
      frequency0High = noteFrequency(highestNote, Global::a4ReferenceFrequency);
      work0 = true;
    }
    {
      static std::thread stringWorker1(string1Worker);
      const MidiNote lowestNote = getStringNoteZeroTuning(Global::highwayCtx, 1);
      frequency1Low = noteFrequency(MidiNote(to_underlying_(lowestNote)), Global::a4ReferenceFrequency);
      const MidiNote highestNote = MidiNote(to_underlying_(getStringNoteZeroTuning(Global::highwayCtx, 1)) + 24);
      frequency1High = noteFrequency(highestNote, Global::a4ReferenceFrequency);
      work1 = true;
    }
    {
      static std::thread stringWorker2(string2Worker);
      const MidiNote lowestNote = getStringNoteZeroTuning(Global::highwayCtx, 2);
      frequency2Low = noteFrequency(MidiNote(to_underlying_(lowestNote)), Global::a4ReferenceFrequency);
      const MidiNote highestNote = MidiNote(to_underlying_(getStringNoteZeroTuning(Global::highwayCtx, 2)) + 24);
      frequency2High = noteFrequency(highestNote, Global::a4ReferenceFrequency);
      work2 = true;
    }
    {
      static std::thread stringWorker3(string3Worker);
      const MidiNote lowestNote = getStringNoteZeroTuning(Global::highwayCtx, 3);
      frequency3Low = noteFrequency(MidiNote(to_underlying_(lowestNote)), Global::a4ReferenceFrequency);
      const MidiNote highestNote = MidiNote(to_underlying_(getStringNoteZeroTuning(Global::highwayCtx, 3)) + 24);
      frequency3High = noteFrequency(highestNote, Global::a4ReferenceFrequency);
      work3 = true;
    }
    if (Global::highwayCtx.instrumentStringCount >= 5)
    {
      static std::thread stringWorker4(string4Worker);
      const MidiNote lowestNote = getStringNoteZeroTuning(Global::highwayCtx, 4);
      frequency4Low = noteFrequency(MidiNote(to_underlying_(lowestNote)), Global::a4ReferenceFrequency);
      const MidiNote highestNote = MidiNote(to_underlying_(getStringNoteZeroTuning(Global::highwayCtx, 4)) + 24);
      frequency4High = noteFrequency(highestNote, Global::a4ReferenceFrequency);
      work4 = true;
    }
    if (Global::highwayCtx.instrumentStringCount >= 6)
    {
      static std::thread stringWorker5(string5Worker);
      const MidiNote lowestNote = getStringNoteZeroTuning(Global::highwayCtx, 5);
      frequency5Low = noteFrequency(MidiNote(to_underlying_(lowestNote)), Global::a4ReferenceFrequency);
      const MidiNote highestNote = MidiNote(to_underlying_(getStringNoteZeroTuning(Global::highwayCtx, 5)) + 24);
      frequency5High = noteFrequency(highestNote, Global::a4ReferenceFrequency);
      work5 = true;
    }
  }
#endif // SHR3D_SFX_CORE_HEXFIN_DIVIDED
}

#endif // SHR3D_SFX_CORE_HEXFIN
