// Copyright (c) 2021 Giorgos Vougioukas
//
// The license can be found in the LICENSE file.

#include "thalia/preferences.h"
#include "thalia/definitions.h"
#include "thalia/thalia_plugin.h"
#include "thalia/app_info.h"
#include "thalia/main_wnd.h"
#include "third_party/libebur128/ebur128/ebur128.h"

#include "WDL/fileread.h"
#include "WDL/filewrite.h"
#include "WDL/aggarray.h"

static const char *default_preferences[] =
{
  "//",
  "// Welcome to " THA_NAME_MARKETING " " THA_ARCH " Audio System, Version " THA_NAKED_VERSION,
  "//",
  "// " THA_NAME_MARKETING " preferences",
  "// Your changes will take effect the next time you relaunch " THA_NAME_MARKETING "",
  "//",
  "",
  "{",
  "  // Display preferences with Curses interface",
  "  // (supports only ASCII for text input).",
  "  // If set to false it uses an external editor.",
  "  \"curses_preferences\": true,",
  "  \"curses_right_focus\": true,",
  "",
  "  // Set the audio system (if available).",
#if defined(_WIN32)
  "  // 0: WaveOut (legacy)",
  "  // 1: DirectSound",
  "  // 2: WASAPI (Windows Vista/7/8/10)",
  "  \"audio_system\": 0,",
#elif defined(__linux__)
  "  // 0: PulseAudio",
  "  \"audio_system\": 0,",
#endif
  "",
  "  // Set the samplerate for the audio device (if available).",
  "  // Most common values (Hz): 8000, 11025, 16000,",
  "  // 22050, 32000, 44100 (default), 48000, 88200,",
  "  // 96000, 176400, 192000.",
  "  \"audio_device_samplerate\": 44100,",
  "",
  "  // Set the bit depth for the audio device (if available).",
  "  // 0: 16-bit, 1: 24-bit, 2: 32-bit, 3: 32-bit FP.",
  "  \"audio_device_bit_depth\": 0,",
#if 0
  "",
  "  // Set the output channels for the audio device (if available).",
  "  \"audio_device_output_channels\": 2,",
#endif
#if defined(_WIN32)
  "",
  "  // Set WASAPI exclusive mode (advanced).",
  "  // When is set to false it overrides \"audio_device_samplerate\"",
  "  // and \"audio_device_bit_depth\" and opens the device in",
  "  // shared mode with the settings in Windows preferences",
  "  // related to: Audio Device Default Format.",
  "  \"wasapi_exclusive_mode\": false,",
#elif defined(__linux__)

#endif
  "",
  "  // Media buffer size (ms).",
  "  \"media_buffer_size\": 1200,",
  "",
  "  // Disk read mode.",
  "  // 1: Asynchronous unbuffered",
  "  // 2: Asynchronous buffered",
  "  // -1: Synchronous unbuffered",
  "  // Set read_buffer_size (bytes)",
  "  \"disk_read_mode\": 2,",
  "  \"read_buffer_size\": 262144,",
  "  \"read_buffers\": 3,",
  "",
  "  // Disk write mode.",
  "  // 1: Asynchronous buffered",
  "  // 2: Asynchronous write-through",
  "  // 3: Asynchronous unbuffered",
  "  // 0: Synchronous",
  "  // Set write_buffer_size (bytes)",
  "  \"disk_write_mode\": 1,",
  "  \"write_buffer_size\": 65536,",
  "  \"write_buffers_min\": 16,",
  "  \"write_buffers_max\": 128,",
  "",
  "  // Disk I/O thread priority.",
  "  // - \"idle\"",
  "  // - \"below_normal\"",
  "  // - \"normal\"",
  "  // - \"above_normal\"",
  "  // - \"highest\"",
  "  // - \"time_critical\"",
  "  \"disk_io_priority\": \"normal\",",
  "  \"disk_io_sleepstep\": 20,",
  "",
  "  // Resample mode.",
  "  // 0: Linear interpolation",
  "  // 1: 16pt sinc",
  "  // 2: 64pt sinc",
  "  // 3: 192pt sinc",
  "  // 4: 384pt sinc",
  "  // 5: 512pt sinc",
  "  // 6: 768pt sinc",
  "  \"resample_mode\": 2,",
  "",
  "  // Render system thread priority.",
  "  // - \"idle\"",
  "  // - \"below_normal\"",
  "  // - \"normal\"",
  "  // - \"above_normal\"",
  "  // - \"highest\"",
  "  // - \"time_critical\"",
  "  \"render_system_priority\": \"highest\",",
  "  \"render_system_sleepstep\": 20,",
  "",
  "  // Set value for volume dB step (increase/decrease).",
  "  \"volume_step\": 2.0,",
  //"",
  //"  // EBU R128 standard for loudness normalization.",
  //"  // Loudness reference.",
  //"  //   0: -23 LUFS (standard)",
  //"  //   1: -18 LUFS (RG2 - backwards compatible with RG1)",
  //"  //   2: -16 LUFS",
  //"  //   3: -14 LUFS",
  //"  //   4: -11 LUFS",
  //"  // Loudness mode.",
  //"  //   0: M (momentary)",
  //"  //   1: S (shortterm)",
  //"  //   2: I (integrated)",
  //"  //   3: LRA (range)",
  //"  //   4: sample peak",
  //"  //   5: true peak",
  //"  \"ebur128_normalization\": false,",
  //"  \"ebur128_downward_only\": true,",
  //"  \"ebur128_reference\": 0,",
  //"  \"ebur128_mode\": 2,",
  "",
  "  // File dialog.",
  "  \"preserve_directory\": true,",
  "",
  "  // Presentation.",
  "  // Colors in hexadecimal format (hex triplet).",
  "  \"background_color\": \"FFFFFF\",",
  "  \"active_text_color\": \"378DF7\",",
  "  \"text_color\": \"000000\",",
  "",
  "  // Media library paths.",
  "  // An array of strings which are valid paths",
  "  // e.g. \"media_library_paths\": [\"D:\\\\music_a\", \"D:\\\\music_b\"]",
  "  //      Note that escape character is needed for windows paths",
  "  \"media_library_paths\": [],",
  "",
  "  // Ensure active track in playlist is visible",
  "  \"track_visible\": false,",
  "",
  "  // Expand database tree",
  "  \"expand_db_tree\": false,",
  "",
  "  // Media library query.",
  "  // Limit media library query to N rows",
  "  \"media_library_query_limit\": 300",
  "}",
};

static WDL_PtrArray<const char *, WDL_ARRAYCOUNT(default_preferences)> dpref(default_preferences);

THA_Preferences::THA_Preferences()
  : m_pf(NULL)
  , m_relaunch(false)
{}

THA_Preferences::~THA_Preferences()
{
  cJSON_Delete(m_pf);
}

void THA_Preferences::Create()
{
  WDL_FastString defpref, usrpref;

  for (int i = 0; i < dpref.GetSize(); i++)
  {
    defpref.AppendFormatted(2048, "%s\n", dpref.Get()[i]);
  }

  const char *user_preferences[] =
  {
    "{",
    "  // Add your personal preferences here.",
    "}"
  };

  WDL_PtrArray<const char *, WDL_ARRAYCOUNT(user_preferences)> upref(user_preferences);

  for (int j = 0; j < upref.GetSize(); j++)
  {
    usrpref.AppendFormatted(2048, "%s\n", upref.Get()[j]);
  }

  WDL_FastString def(g_setpath), usr(g_setpath);
  def.Append("preferences-default.txt");
  usr.Append("preferences.txt");

  // Create default preferences file
  WDL_FileWrite *cdf = new WDL_FileWrite(def.Get());
  if (cdf && cdf->IsOpen())
  {
    cdf->Write(defpref.Get(), defpref.GetLength());
  }

  if (cdf) { delete cdf; cdf = NULL; }

  // Check if user preferences exist
  WDL_FileRead *uf = new WDL_FileRead(usr.Get());
  if (uf && !uf->IsOpen())
  {
    // Create user preferences file
    WDL_FileWrite *cuf = new WDL_FileWrite(usr.Get());
    if (cuf && cuf->IsOpen())
    {
      cuf->Write(usrpref.Get(), usrpref.GetLength());
    }

    if (cuf) { delete cuf; cuf = NULL; }
  }

  if (uf) { delete uf; uf = NULL; }
}

void THA_Preferences::Open(bool default_file)
{
  WDL_FastString def(g_setpath), usr(g_setpath);
  def.Append("preferences-default.txt");
  usr.Append("preferences.txt");

  ShellExecute(g_mainwnd->Handle(), "", "notepad.exe", default_file ? def.Get() : usr.Get(), "", SW_SHOWNORMAL);
}

bool THA_Preferences::Parse(bool default_file)
{
  WDL_FastString def(g_setpath), usr(g_setpath);
  def.Append("preferences-default.txt");
  usr.Append("preferences.txt");

  WDL_FileRead fr(default_file ? def.Get() : usr.Get());
  WDL_TypedBuf<char> strbuf;
  strbuf.Resize((int)fr.GetSize());
  fr.Read(strbuf.Get(), strbuf.GetSize());
  strbuf.Add('\0');

  cJSON_Minify(strbuf.Get());
  m_pf = cJSON_Parse(strbuf.Get());

  if (!m_pf)
  {
    const char *error_ptr = cJSON_GetErrorPtr();
    if (error_ptr)
    {
      m_err.SetFormatted(2048, "Parse error before: %s\n", error_ptr);
      wdl_log(m_err.Get());
    }

    return false;
  }

  return true;
}

bool THA_Preferences::WantCurses() const
{
  const cJSON *curses = NULL;
  curses = cJSON_GetObjectItemCaseSensitive(m_pf, "curses_preferences");
  if (cJSON_IsTrue(curses)) return true;
  else if (cJSON_IsFalse(curses)) return false;
  else return true;
}

bool THA_Preferences::WantCursesRightFocus() const
{
  const cJSON *curses_right_focus = NULL;
  curses_right_focus = cJSON_GetObjectItemCaseSensitive(m_pf, "curses_right_focus");
  if (cJSON_IsTrue(curses_right_focus)) return true;
  else if (cJSON_IsFalse(curses_right_focus)) return false;
  else return true;
}

int THA_Preferences::GetAudioSystem() const
{
  const cJSON *audio_system = NULL;
  audio_system = cJSON_GetObjectItemCaseSensitive(m_pf, "audio_system");
  if (cJSON_IsNumber(audio_system))
  {
#if defined(_WIN32)
    // 0: WaveOut, 1: DirectSound, 2: WASAPI.
    if (audio_system->valueint >= 0 && audio_system->valueint <= 2)
#elif defined(__linux__)
    // 0: PulseAudio.
    if (audio_system->valueint == 0)
#endif
    {
      return audio_system->valueint;
    }
    else return 0;
  }
  else return 0;
}

double THA_Preferences::GetAudioDeviceSamplerate() const
{
  if (GetAudioSystem() == 2 && !GetWASAPIExclusiveMode())
  {
    if (g_audiostreamer) return g_audiostreamer->GetSampleRate();
  }

  const cJSON *dev_srate = NULL;
  dev_srate = cJSON_GetObjectItemCaseSensitive(m_pf, "audio_device_samplerate");
  if (cJSON_IsNumber(dev_srate))
  {
    if (dev_srate->valueint == 8000 || dev_srate->valueint == 11025 ||
      dev_srate->valueint == 16000 || dev_srate->valueint == 22050 ||
      dev_srate->valueint == 32000 || dev_srate->valueint == 44100 ||
      dev_srate->valueint == 48000 || dev_srate->valueint == 88200 ||
      dev_srate->valueint == 96000 || dev_srate->valueint == 176400 ||
      dev_srate->valueint == 192000)
    {
      return dev_srate->valuedouble;
    }
    else return 44100.0;
  }
  else return 44100.0;
}

int THA_Preferences::GetAudioDeviceBitDepth() const
{
  const cJSON *dev_bit_depth = NULL;
  dev_bit_depth = cJSON_GetObjectItemCaseSensitive(m_pf, "audio_device_bit_depth");
  if (cJSON_IsNumber(dev_bit_depth))
  {
    // 0: 16-bit, 1: 24-bit, 2: 32-bit, 3: 32-bit FP.
    if (dev_bit_depth->valueint >= 0 && dev_bit_depth->valueint <= 3)
    {
      return dev_bit_depth->valueint;
    }
    else return 0;
  }
  else return 0;
}

int THA_Preferences::GetAudioDeviceOutputChannels() const
{
  //if (!GetWASAPIExclusiveMode())
  //{
  //  if (g_audiostreamer) return g_audiostreamer->GetChannels();
  //}

  //const cJSON *nch = NULL;
  //nch = cJSON_GetObjectItemCaseSensitive(m_pf, "audio_device_output_channels");
  //if (cJSON_IsNumber(nch))
  //{
  //  if (nch->valueint == 2 || nch->valueint == 4) return nch->valueint;
  //  else return 2;
  //}
  //else return 2;
  return 2;
}

int THA_Preferences::GetMediaBufferSize() const
{
  const cJSON *mbs = NULL;
  mbs = cJSON_GetObjectItemCaseSensitive(m_pf, "media_buffer_size");
  if (cJSON_IsNumber(mbs))
  {
    if (mbs->valueint > 0) return mbs->valueint;
    else return 200;
  }
  else return 200;
}

void THA_Preferences::GetDiskReadMode(int *rmode, int *rbufsize, int *rnbufs) const
{
  const cJSON *disk_read_mode = NULL;
  const cJSON *read_buffer_size = NULL;
  const cJSON *read_buffers = NULL;
  disk_read_mode = cJSON_GetObjectItemCaseSensitive(m_pf, "disk_read_mode");
  if (cJSON_IsNumber(disk_read_mode))
  {
    if (disk_read_mode->valueint >= -1 && disk_read_mode->valueint <= 2)
    {
      *rmode = disk_read_mode->valueint;
    }
    else *rmode = 2;
  }
  else *rmode = 2;

  read_buffer_size = cJSON_GetObjectItemCaseSensitive(m_pf, "read_buffer_size");
  if (cJSON_IsNumber(read_buffer_size))
  {
    if (read_buffer_size->valueint > 0)
    {
      *rbufsize = read_buffer_size->valueint;
    }
    else *rbufsize = 262144;
  }
  else *rbufsize = 262144;

  read_buffers = cJSON_GetObjectItemCaseSensitive(m_pf, "read_buffers");
  if (cJSON_IsNumber(read_buffers))
  {
    if (read_buffers->valueint > 0)
    {
      *rnbufs = read_buffers->valueint;
    }
    else *rnbufs = 3;
  }
  else *rnbufs = 3;
}

void THA_Preferences::GetDiskWriteMode(int *wmode, int *wbufsize, int *wminbufs, int *wmaxbufs) const
{
  const cJSON *disk_write_mode = NULL;
  const cJSON *write_buffer_size = NULL;
  const cJSON *write_buffers_min = NULL;
  const cJSON *write_buffers_max = NULL;
  disk_write_mode = cJSON_GetObjectItemCaseSensitive(m_pf, "disk_write_mode");
  if (cJSON_IsNumber(disk_write_mode))
  {
    if (disk_write_mode->valueint >= 0 && disk_write_mode->valueint <= 3)
    {
      *wmode = disk_write_mode->valueint;
    }
    else *wmode = 1;
  }
  else *wmode = 1;

  write_buffer_size = cJSON_GetObjectItemCaseSensitive(m_pf, "write_buffer_size");
  if (cJSON_IsNumber(write_buffer_size))
  {
    if (write_buffer_size->valueint > 0)
    {
      *wbufsize = write_buffer_size->valueint;
    }
    else *wbufsize = 65536;
  }
  else *wbufsize = 65536;

  write_buffers_min = cJSON_GetObjectItemCaseSensitive(m_pf, "write_buffers_min");
  if (cJSON_IsNumber(write_buffers_min))
  {
    if (write_buffers_min->valueint > 0)
    {
      *wminbufs = write_buffers_min->valueint;
    }
    else *wminbufs = 16;
  }
  else *wminbufs = 16;

  write_buffers_max = cJSON_GetObjectItemCaseSensitive(m_pf, "write_buffers_max");
  if (cJSON_IsNumber(write_buffers_max))
  {
    if (write_buffers_max->valueint > 0)
    {
      *wmaxbufs = write_buffers_max->valueint;
    }
    else *wmaxbufs = 128;
  }
  else *wmaxbufs = 128;

  if (*wminbufs > *wmaxbufs) *wminbufs = *wmaxbufs;
}

int THA_Preferences::GetDiskIOPriority() const
{
  const cJSON *disk_io_priority = NULL;
  disk_io_priority = cJSON_GetObjectItemCaseSensitive(m_pf, "disk_io_priority");
  if (cJSON_IsString(disk_io_priority) && (disk_io_priority->valuestring != NULL))
  {
    if (!strcmp(disk_io_priority->valuestring, "idle"))
    {
      return THREAD_PRIORITY_IDLE;
    }
    else if (!strcmp(disk_io_priority->valuestring, "below_normal"))
    {
      return THREAD_PRIORITY_BELOW_NORMAL;
    }
    else if (!strcmp(disk_io_priority->valuestring, "normal"))
    {
      return THREAD_PRIORITY_NORMAL;
    }
    else if (!strcmp(disk_io_priority->valuestring, "above_normal"))
    {
      return THREAD_PRIORITY_ABOVE_NORMAL;
    }
    else if (!strcmp(disk_io_priority->valuestring, "highest"))
    {
      return THREAD_PRIORITY_HIGHEST;
    }
    else if (!strcmp(disk_io_priority->valuestring, "time_critical"))
    {
      return THREAD_PRIORITY_TIME_CRITICAL;
    }
    else return THREAD_PRIORITY_NORMAL;
  }
  else return THREAD_PRIORITY_NORMAL;
}

int THA_Preferences::GetDiskIOSleepStep() const
{
  const cJSON *disk_io_sleepstep = NULL;
  disk_io_sleepstep = cJSON_GetObjectItemCaseSensitive(m_pf, "disk_io_sleepstep");
  if (cJSON_IsNumber(disk_io_sleepstep))
  {
    if (disk_io_sleepstep->valueint >= 0) return disk_io_sleepstep->valueint;
    else return 20;
  }
  else return 20;
}

int THA_Preferences::GetAntidoteBitDepth() const
{
  const cJSON *antidote_bit_depth = NULL;
  antidote_bit_depth = cJSON_GetObjectItemCaseSensitive(m_pf, "antidote_bit_depth");
  if (cJSON_IsNumber(antidote_bit_depth))
  {
    // 0: 16-bit, 1: 24-bit, 2: 32-bit, 3: 32-bit FP, 4: 64-bit FP.
    if (antidote_bit_depth->valueint >= 0 && antidote_bit_depth->valueint <= 4)
    {
      return antidote_bit_depth->valueint;
    }
    else return 0;
  }
  else return 0;
}

double THA_Preferences::GetAntidotePreAllocate() const
{
  const cJSON *antidote_pre_allocate = NULL;
  antidote_pre_allocate = cJSON_GetObjectItemCaseSensitive(m_pf, "antidote_pre_allocate");
  if (cJSON_IsNumber(antidote_pre_allocate))
  {
    if (antidote_pre_allocate->valueint >= 0)
    {
      return antidote_pre_allocate->valuedouble;
    }
    else return 0.0;
  }
  else return 0.0;
}

void THA_Preferences::GetResampleMode(bool *interp, int *filtercnt, bool *sinc,
  int *sinc_size, int *sinc_interpsize) const
{
  const cJSON *resample_mode = NULL;
  resample_mode = cJSON_GetObjectItemCaseSensitive(m_pf, "resample_mode");
  if (cJSON_IsNumber(resample_mode))
  {
    if (resample_mode->valueint >= 0 && resample_mode->valueint <= 6)
    {
      switch (resample_mode->valueint)
      {
      case 0:
        *interp = true; *filtercnt = 1;
        *sinc = false; *sinc_size = 64; *sinc_interpsize = 32;
        break;
      case 1:
        *interp = true; *filtercnt = 1;
        *sinc = true; *sinc_size = 16; *sinc_interpsize = 8;
        break;
      case 2:
        *interp = true; *filtercnt = 1;
        *sinc = true; *sinc_size = 64; *sinc_interpsize = 32;
        break;
      case 3:
        *interp = true; *filtercnt = 1;
        *sinc = true; *sinc_size = 192; *sinc_interpsize = 96;
        break;
      case 4:
        *interp = true; *filtercnt = 1;
        *sinc = true; *sinc_size = 384; *sinc_interpsize = 192;
        break;
      case 5:
        *interp = true; *filtercnt = 1;
        *sinc = true; *sinc_size = 512; *sinc_interpsize = 256;
        break;
      case 6:
        *interp = true; *filtercnt = 1;
        *sinc = true; *sinc_size = 768; *sinc_interpsize = 384;
        break;
      }
    }
    else
    {
      *interp = true; *filtercnt = 1;
      *sinc = true; *sinc_size = 64; *sinc_interpsize = 32;
    }
  }
  else
  {
    *interp = true; *filtercnt = 1;
    *sinc = true; *sinc_size = 64; *sinc_interpsize = 32;
  }
}

int THA_Preferences::GetRenderSystemPriority() const
{
  const cJSON *render_system_priority = NULL;
  render_system_priority = cJSON_GetObjectItemCaseSensitive(m_pf, "render_system_priority");
  if (cJSON_IsString(render_system_priority) && (render_system_priority->valuestring != NULL))
  {
    if (!strcmp(render_system_priority->valuestring, "idle"))
    {
      return THREAD_PRIORITY_IDLE;
    }
    else if (!strcmp(render_system_priority->valuestring, "below_normal"))
    {
      return THREAD_PRIORITY_BELOW_NORMAL;
    }
    else if (!strcmp(render_system_priority->valuestring, "normal"))
    {
      return THREAD_PRIORITY_NORMAL;
    }
    else if (!strcmp(render_system_priority->valuestring, "above_normal"))
    {
      return THREAD_PRIORITY_ABOVE_NORMAL;
    }
    else if (!strcmp(render_system_priority->valuestring, "highest"))
    {
      return THREAD_PRIORITY_HIGHEST;
    }
    else if (!strcmp(render_system_priority->valuestring, "time_critical"))
    {
      return THREAD_PRIORITY_TIME_CRITICAL;
    }
    else return THREAD_PRIORITY_NORMAL;
  }
  else return THREAD_PRIORITY_NORMAL;
}

int THA_Preferences::GetRenderSystemSleepStep() const
{
  const cJSON *render_system_sleepstep = NULL;
  render_system_sleepstep = cJSON_GetObjectItemCaseSensitive(m_pf, "render_system_sleepstep");
  if (cJSON_IsNumber(render_system_sleepstep))
  {
    if (render_system_sleepstep->valueint >= 0) return render_system_sleepstep->valueint;
    else return 20;
  }
  else return 20;
}

bool THA_Preferences::GetWASAPIExclusiveMode() const
{
  const cJSON *wasapi_exclusive_mode = NULL;
  wasapi_exclusive_mode = cJSON_GetObjectItemCaseSensitive(m_pf, "wasapi_exclusive_mode");
  if (cJSON_IsTrue(wasapi_exclusive_mode)) return true;
  else if (cJSON_IsFalse(wasapi_exclusive_mode)) return false;
  else return false;
}

double THA_Preferences::GetVolumeStep() const
{
  const cJSON *volume_step = NULL;
  volume_step = cJSON_GetObjectItemCaseSensitive(m_pf, "volume_step");
  if (cJSON_IsNumber(volume_step))
  {
    return wdl_abs(volume_step->valuedouble);
  }
  else return 2.0;
}

int THA_Preferences::GetBackgroundColor() const
{
  const cJSON *bg_color = NULL;
  bg_color = cJSON_GetObjectItemCaseSensitive(m_pf, "background_color");
  if (cJSON_IsString(bg_color) && (bg_color->valuestring != NULL))
  {
    int len = (int)strlen(bg_color->valuestring);
    if (len != 6) return 0xFFFFFF;
    int bgc = (int)strtol(bg_color->valuestring, NULL, 16);
    if (bgc < 0x000000 || bgc > 0xFFFFFF) return 0xFFFFFF;
    return bgc;
  }
  else return 0xFFFFFF;
}

int THA_Preferences::GetActiveTextColor() const
{
  const cJSON *txt_color = NULL;
  txt_color = cJSON_GetObjectItemCaseSensitive(m_pf, "active_text_color");
  if (cJSON_IsString(txt_color) && (txt_color->valuestring != NULL))
  {
    int len = (int)strlen(txt_color->valuestring);
    if (len != 6) return 0x378DF7;
    int txtc = (int)strtol(txt_color->valuestring, NULL, 16);
    if (txtc < 0x000000 || txtc > 0xFFFFFF) return 0x378DF7;
    return txtc;
  }
  else return 0x378DF7;
}

int THA_Preferences::GetTextColor() const
{
  const cJSON *txt_color = NULL;
  txt_color = cJSON_GetObjectItemCaseSensitive(m_pf, "text_color");
  if (cJSON_IsString(txt_color) && (txt_color->valuestring != NULL))
  {
    int len = (int)strlen(txt_color->valuestring);
    if (len != 6) return 0x000000;
    int txtc = (int)strtol(txt_color->valuestring, NULL, 16);
    if (txtc < 0x000000 || txtc > 0xFFFFFF) return 0x000000;
    return txtc;
  }
  else return 0x000000;
}

bool THA_Preferences::WantEBUR128() const
{
  const cJSON *ebur128_normalization = NULL;
  ebur128_normalization = cJSON_GetObjectItemCaseSensitive(m_pf, "ebur128_normalization");
  if (cJSON_IsTrue(ebur128_normalization)) return true;
  else if (cJSON_IsFalse(ebur128_normalization)) return false;
  else return false;
}

bool THA_Preferences::WantDownwardOnly() const
{
  const cJSON *ebur128_downward_only = NULL;
  ebur128_downward_only = cJSON_GetObjectItemCaseSensitive(m_pf, "ebur128_downward_only");
  if (cJSON_IsTrue(ebur128_downward_only)) return true;
  else if (cJSON_IsFalse(ebur128_downward_only)) return false;
  else return true;
}

double THA_Preferences::GetEBUR128LUFS() const
{
  const cJSON *ebur128_reference = NULL;
  ebur128_reference = cJSON_GetObjectItemCaseSensitive(m_pf, "ebur128_reference");
  if (cJSON_IsNumber(ebur128_reference))
  {
    if (ebur128_reference->valueint >= 0 &&
      ebur128_reference->valueint <= 4)
    {
      switch (ebur128_reference->valueint)
      {
      case 0: return -23.0;
      case 1: return -18.0;
      case 2: return -16.0;
      case 3: return -14.0;
      case 4: return -11.0;
      default: return -23.0;
      }
    }
    else return -23.0;
  }
  else return -23.0;
}

int THA_Preferences::GetEBUR128Mode() const
{
  const cJSON *ebur128_mode = NULL;
  ebur128_mode = cJSON_GetObjectItemCaseSensitive(m_pf, "ebur128_mode");
  if (cJSON_IsNumber(ebur128_mode))
  {
    if (ebur128_mode->valueint >= 0 &&
      ebur128_mode->valueint <= 5)
    {
      switch (ebur128_mode->valueint)
      {
      case 0: return EBUR128_MODE_M;
      case 1: return EBUR128_MODE_M | EBUR128_MODE_S;
      case 2: return EBUR128_MODE_M | EBUR128_MODE_S | EBUR128_MODE_I;
      case 3: return EBUR128_MODE_M | EBUR128_MODE_S | EBUR128_MODE_I |
                EBUR128_MODE_LRA;
      case 4: return EBUR128_MODE_M | EBUR128_MODE_S | EBUR128_MODE_I |
                EBUR128_MODE_LRA | EBUR128_MODE_SAMPLE_PEAK;
      case 5: return EBUR128_MODE_M | EBUR128_MODE_S | EBUR128_MODE_I |
                EBUR128_MODE_LRA | EBUR128_MODE_SAMPLE_PEAK |
                EBUR128_MODE_TRUE_PEAK;
      //case 6: return EBUR128_MODE_M | EBUR128_MODE_S | EBUR128_MODE_I |
      //          EBUR128_MODE_LRA | EBUR128_MODE_SAMPLE_PEAK |
      //          EBUR128_MODE_TRUE_PEAK | EBUR128_MODE_HISTOGRAM;
      default: return EBUR128_MODE_I;
      }
    }
    else return EBUR128_MODE_I;
  }
  else return EBUR128_MODE_I;
}

bool THA_Preferences::WantPreserveDirectory() const
{
  const cJSON *preserve_directory = NULL;
  preserve_directory = cJSON_GetObjectItemCaseSensitive(m_pf, "preserve_directory");
  if (cJSON_IsTrue(preserve_directory)) return true;
  else if (cJSON_IsFalse(preserve_directory)) return false;
  else return true;
}

void THA_Preferences::GetMediaLibraryPaths(WDL_PtrList_DeleteOnDestroy<WDL_FastString> **paths) const
{
  const cJSON *media_library_path = NULL;
  const cJSON *media_library_paths = NULL;
  WDL_PtrList_DeleteOnDestroy<WDL_FastString> *mlp = new WDL_PtrList_DeleteOnDestroy<WDL_FastString>;
  media_library_paths = cJSON_GetObjectItemCaseSensitive(m_pf, "media_library_paths");
  cJSON_ArrayForEach(media_library_path, media_library_paths)
  {
    if (cJSON_IsString(media_library_path) && (media_library_path->valuestring != NULL))
    {
      mlp->Add(new WDL_FastString(media_library_path->valuestring));
    }
  }
  *paths = mlp;
}

int THA_Preferences::GetMediaLibraryQueryLimit() const
{
  const cJSON *media_library_query_limit = NULL;
  media_library_query_limit = cJSON_GetObjectItemCaseSensitive(m_pf, "media_library_query_limit");
  if (cJSON_IsNumber(media_library_query_limit))
  {
    return media_library_query_limit->valueint;
  }
  else return 300;
}

bool THA_Preferences::WantTrackVisible() const
{
  const cJSON *track_visible = NULL;
  track_visible = cJSON_GetObjectItemCaseSensitive(m_pf, "track_visible");
  if (cJSON_IsTrue(track_visible)) return true;
  else if (cJSON_IsFalse(track_visible)) return false;
  else return false;
}

bool THA_Preferences::WantExpandedTree() const
{
  const cJSON *expand_db_tree = NULL;
  expand_db_tree = cJSON_GetObjectItemCaseSensitive(m_pf, "expand_db_tree");
  if (cJSON_IsTrue(expand_db_tree)) return true;
  else if (cJSON_IsFalse(expand_db_tree)) return false;
  else return false;
}
