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

#include "euterpe/main_loop.h"
#include "euterpe/definitions.h"
#include "euterpe/plugin.h"
#include "euterpe/app_info.h"
#include "euterpe/main_wnd.h"

#if defined(_WIN32)
#include "WDL/win32_printf.h"
#endif

#include "WDL/fpcmp.h"
#include "WDL/db2val.h"
#include "WDL/wdlcstring.h"

RSE_MainLoop::RSE_MainLoop()
  : m_is_paused(false)
  , m_db(0.0)
  , m_last_db(0.0)
  , m_is_muted(false)
  , m_audio_input(NULL)
  , m_running_time(0.0)
  , m_eof(false)
  , m_bq_max_duration_ms(1200)
  , m_duration(0.0)
  , m_playlist(NULL)
  , m_current_track(0)
  , m_is_repeat_on(false)
  , m_is_shuffle_on(false)
  , m_is_time_elapsed(true)
  , m_has_stop_after_current(false)
  , m_kill_region(NULL)
{
  m_playlist = new WDL_PtrList_DeleteOnDestroy<char>(free);
}

RSE_MainLoop::~RSE_MainLoop()
{
  if (m_audio_input) delete m_audio_input;
  if (m_playlist) delete m_playlist;
  if (m_kill_region) delete m_kill_region;
  m_playback_queue.Empty(true, free);
}

void RSE_MainLoop::AddTrack(const char *path)
{
  m_playlist->Add(strdup(path));
}

void RSE_MainLoop::Seek(double time)
{
  if (m_audio_input)
  {
    m_bq.Flush();
    m_audio_input->Seek(time);
  }
}

double RSE_MainLoop::GetVolume()
{
  return m_db;
}

void RSE_MainLoop::SetVolume(double db)
{
  if (m_is_muted) return;

  if (WDL_DefinitelyLessThan(db, -150.0))
  {
    m_db = -150.0;
  }
  else if (WDL_DefinitelyGreaterThan(db, 0.0))
  {
    m_db = 0.0;
  }
  else
  {
    m_db = db;
  }
}

void RSE_MainLoop::ToggleMute()
{
  if (WDL_EssentiallyEqual(m_db, -150.0))
  {
    m_db = m_last_db;
    m_is_muted = false;
  }
  else
  {
    m_last_db = m_db;
    m_db = -150.0;
    m_is_muted = true;
  }
}

void RSE_MainLoop::Pause()
{
  if (!g_main_wnd->IsEngineOnline()) return;
  if (!IsPlaying()) return;
  m_is_paused = !m_is_paused;
}

void RSE_MainLoop::Previous()
{
  if (!g_main_wnd->IsEngineOnline()) return;

  m_has_stop_after_current = false;

  if (!g_audio_streamer) return;

  int total_tracks = m_playlist->GetSize();

  if (total_tracks == 0)
  {
    m_current_track = 0;
    return;
  }

  WDL_ASSERT(total_tracks > 0);

  if (IsPaused()) Pause();

  if (m_playback_queue.GetSize())
  {
    if (IsPlaying()) Play();
    return;
  }
  else
  {
    if (!m_is_shuffle_on)
    {
      if (m_current_track - 1 >= 0)
      {
        m_current_track--;
      }
      else
      {
        if (m_is_repeat_on)
        {
          m_current_track = total_tracks > 0 ? total_tracks - 1 : 0;
        }
        else
        {
          return;
        }
      }
    }
    else
    {
      m_current_track = total_tracks > 0 ? m_mt.randInt(total_tracks - 1) : 0;
    }
  }

  if (IsPlaying()) Play(m_current_track);

  if (m_is_paused) m_is_paused = !m_is_paused;
}

void RSE_MainLoop::Next()
{
  if (!g_main_wnd->IsEngineOnline()) return;

  m_has_stop_after_current = false;

  if (!g_audio_streamer) return;

  int total_tracks = m_playlist->GetSize();

  if (total_tracks == 0)
  {
    m_current_track = 0;
    return;
  }

  WDL_ASSERT(total_tracks > 0);

  if (IsPaused()) Pause();

  if (m_playback_queue.GetSize())
  {
    if (IsPlaying()) Play();
    return;
  }
  else
  {
    if (!m_is_shuffle_on)
    {
      if (m_current_track + 1 < total_tracks)
      {
        m_current_track++;
      }
      else
      {
        if (m_is_repeat_on)
        {
          m_current_track = 0;
        }
        else
        {
          return;
        }
      }
    }
    else
    {
      m_current_track = total_tracks > 0 ? m_mt.randInt(total_tracks - 1) : 0;
    }
  }

  if (IsPlaying()) Play(m_current_track);

  if (m_is_paused) m_is_paused = !m_is_paused;
}

void RSE_MainLoop::Play(int track_num, bool reset_stop_after_current)
{
  if (!g_main_wnd->IsEngineOnline()) return;

  if (reset_stop_after_current)
  {
    m_has_stop_after_current = false;
  }

  if (track_num >= 0)
  {
    m_current_track = track_num;
  }

  WDL_ASSERT(m_current_track >= 0);

  if (!m_playlist->GetSize())
  {
    Stop();
    return;
  }

  WDL_ASSERT(m_current_track < m_playlist->GetSize());

  if (IsPaused())
  {
    Pause();
    return;
  }

  if (!g_audio_streamer->IsRunning())
  {
    g_audio_streamer->Start(&AudioOnSamples);
  }

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

  m_bq.Flush();
  m_running_time = 0.0;

  if (m_playback_queue.GetSize() && track_num < 0)
  {
    m_audio_input = CreateAudioInput(m_playback_queue.Get(0));

    m_file.Set(m_playback_queue.Get(0));

    for (int i = 0; i < m_playlist->GetSize(); i++)
    {
      if (!strcmp(m_file.Get(), m_playlist->Get(i)))
      {
        m_current_track = i; break;
      }
    }

    m_playback_queue.Delete(0, true, free);
  }
  else
  {
    m_audio_input = CreateAudioInput(m_playlist->Get(m_current_track));

    m_file.Set(m_playlist->Get(m_current_track));
  }

  if (!m_audio_input)
  {
    Next();
    return;
  }

  m_duration = m_audio_input->GetLength();

  if (m_audio_input) m_eof = false;
}

void RSE_MainLoop::Stop()
{
  if (!g_main_wnd->IsEngineOnline()) return;

  m_has_stop_after_current = false;

  if (g_audio_streamer->IsRunning())
  {
    g_audio_streamer->Stop();
  }

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

  m_bq.Flush();
  m_eof = false;
  m_running_time = 0.0;

  if (m_is_paused) m_is_paused = !m_is_paused;
}

void RSE_MainLoop::StopAfterCurrent()
{
  m_has_stop_after_current = !m_has_stop_after_current;
}

bool RSE_MainLoop::HasStopAfterCurrent() const
{
  return m_has_stop_after_current;
}

bool RSE_MainLoop::IsPlaying() const
{
  return m_audio_input != NULL;
}

bool RSE_MainLoop::IsPaused() const
{
  return m_is_paused;
}

bool RSE_MainLoop::IsMuted() const
{
  return m_is_muted;
}

bool RSE_MainLoop::IsDrained() const
{
  return m_eof && m_bq.GetSize() == 0;
}

bool RSE_MainLoop::WantMore() const
{
  if (!m_eof && m_audio_input)
  {
    // duration = filesize in bytes /
    // (samplerate * num of channels * (bits per sample / eight))
    int duration_ms = (int)(((double)m_bq.GetSize() / (m_audio_input->GetChannels() *
      GetHardwareSampleRate() * sizeof(SAM))) * 1000);

    if (duration_ms < m_bq_max_duration_ms) return true;
  }

  return false;
}

bool RSE_MainLoop::DoBuffering(int buffer_size)
{
  if (m_eof) return false;

  if (m_audio_input)
  {
    m_buffer.Resize(buffer_size);
    int nsam = m_audio_input->GetSamples(
      m_buffer.Get(), m_buffer.GetSize());

    if (nsam > 0)
    {
      m_bq.AddBlock(&m_buffer);

      return true;
    }
    else
    {
      m_eof = true;
    }
  }

  return false;
}

double RSE_MainLoop::GetDuration() const
{
  return m_duration;
}

double RSE_MainLoop::GetRunningTime() const
{
  if (m_audio_input)
  {
    int bytes = m_bq.GetSize();
    double fileinf = m_audio_input->GetChannels() *
      GetHardwareSampleRate() * sizeof(SAM);

    double bqms = bytes / fileinf;

    m_running_time = m_audio_input->GetPosition() - bqms;
  }
  else
  {
    m_running_time = 0.0;
  }

  return m_running_time;
}

void RSE_MainLoop::UpdatePlayList(WDL_PtrList_DeleteOnDestroy<char> *pl)
{
  WDL_PtrList_DeleteOnDestroy<char> *p = m_playlist;
  m_playlist = pl;
  delete p;

  //for (int i = 0; i < m_playlist->GetSize(); i++)
  //{
  //  if (!strcmp(m_file.Get(), m_playlist->Get(i)))
  //  {
  //    m_current_track = i; return;
  //  }
  //}

  if (m_current_track >= m_playlist->GetSize())
  {
    m_current_track = m_playlist->GetSize() > 0 ? m_playlist->GetSize() - 1 : 0;
  }
}

void RSE_MainLoop::SetKillRegion(WDL_HeapBuf *kill_region)
{
  if (m_kill_region) { delete m_kill_region; m_kill_region = NULL; }

  m_kill_region = kill_region;
}

void RSE_MainLoop::GetKillRegion(WDL_HeapBuf **kill_region)
{
  if (m_kill_region)
  {
    *kill_region = m_kill_region;
  }
  else
  {
    *kill_region = NULL;
  }
}

void RSE_MainLoop::AddToQueue(const char *track)
{
  if (m_playback_queue.GetSize() < RSE_PLAYBACK_MAX_QUEUE)
  {
    m_playback_queue.Add(strdup(track));
  }
}

void RSE_MainLoop::RemoveFromQueue()
{
  if (m_playback_queue.GetSize())
  {
    m_playback_queue.Delete(m_playback_queue.GetSize() - 1, true, free);
  }
}

void RSE_MainLoop::ClearQueue()
{
  m_playback_queue.Empty(true, free);
}

int RSE_MainLoop::TracksInQueue() const
{
  return m_playback_queue.GetSize();
}

int RSE_MainLoop::NextInQueue() const
{
  if (m_playback_queue.GetSize())
  {
    for (int i = 0; i < m_playlist->GetSize(); i++)
    {
      if (!strcmp(m_playback_queue.Get(0), m_playlist->Get(i)))
      {
        return i;
      }
    }
  }

  return 0;
}

void RSE_MainLoop::RemoveAll()
{
  m_playlist->Empty(true, free);
  m_current_track = 0;
}

void RSE_MainLoop::SetCurrentTrack(int index)
{
  m_current_track = 0;

  if (index < m_playlist->GetSize())
  {
    m_file.Set(m_playlist->Get(index));
    m_current_track = index;
  }
}

int RSE_MainLoop::GetCurrentTrack() const
{
  return m_current_track;
}

void RSE_MainLoop::ToggleShuffle()
{
  m_is_shuffle_on = !m_is_shuffle_on;
}

void RSE_MainLoop::ToggleRepeat()
{
  m_is_repeat_on = !m_is_repeat_on;
}

bool RSE_MainLoop::IsShuffleOn() const
{
  return m_is_shuffle_on;
}

bool RSE_MainLoop::IsRepeatOn() const
{
  return m_is_repeat_on;
}

const char *RSE_MainLoop::GetCurrentFilename() const
{
  return m_file.Get();
}

void RSE_MainLoop::ToggleTime()
{
  m_is_time_elapsed = !m_is_time_elapsed;
}

void RSE_MainLoop::SetTimeElapsed(bool elapsed)
{
  m_is_time_elapsed = elapsed;
}

bool RSE_MainLoop::IsTimeElapsed() const
{
  return m_is_time_elapsed;
}

void RSE_MainLoop::EmptyBufferQueue()
{
  m_bq.Empty();
}

void RSE_MainLoop::AudioOutput(WDL_TypedBuf<SAM> *output, int frame_count, int nch)
{
  if (m_is_paused || !m_audio_input || IsDrained())
  {
    memset(output->Get(), 0, frame_count * sizeof(SAM));
    return;
  }

  memset(output->Get(), 0, frame_count * sizeof(SAM));

  int written = 0;
  WDL_TypedQueue<SAM> *block = NULL;

  while (written < frame_count)
  {
    if (m_bq.GetBlock(&block))
    {
      if (block->Available() / 2 * nch > frame_count - written)
      {
        int sz = frame_count - written;

        for (int i = 0, j = 0; i < sz; i += nch, j += 2)
        {
          output->Get()[written + i] = block->Get()[j];
          output->Get()[written + i + 1] = block->Get()[j + 1];
        }

        written += sz;

        block->Advance(2 * sz / nch);
        block->Compact();

        if (block->Available()) m_bq.ReturnBlock(block);
        else m_bq.DisposeBlock(block);
      }
      else
      {
        int sz = block->GetSize() / 2 * nch;

        for (int i = 0, j = 0; i < sz; i += nch, j += 2)
        {
          output->Get()[written + i] = block->Get()[j];
          output->Get()[written + i + 1] = block->Get()[j + 1];
        }

        written += sz;

        block->Clear();
        m_bq.DisposeBlock(block);
      }
    }
    else
    {
      break;
    }
  }

  if (m_db < 0.0)
  {
    SAM *out = output->Get();
    for (int i = 0; i < frame_count; i++)
    {
      out[i] *= (SAM)DB2VAL(m_db);
    }
  }
}

int RSE_MainLoop::Run()
{
  if (m_is_paused || !m_audio_input) return 1;

  while (WantMore())
  {
    if (!DoBuffering()) break;
  }

  if (IsDrained())
  {
    int track_count = m_playlist->GetSize();

    if (m_has_stop_after_current && !m_playback_queue.GetSize())
    {
      Stop();
      return 1;
    }

    if (m_playback_queue.GetSize())
    {
      Play(-1, false);
      return 1;
    }

    if (m_current_track + 1 < track_count || m_is_repeat_on || m_is_shuffle_on)
    {
      if (!m_is_shuffle_on)
      {
        if (m_current_track + 1 >= track_count) // repeat
        {
          m_current_track = 0;
        }
        else
        {
          m_current_track++;
        }
      }
      else
      {
        m_current_track = m_mt.randInt(track_count - 1);
      }

      Play(m_current_track);
    }
    else
    {
      Stop();
    }
  }

  return 1;
}
