// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0

#include "progress_callback.h"
#include "assert.h"
#include "log.h"

#include <cmath>
#include <cstdio>
#include <limits>

LOG_CHANNEL(Host);

static ProgressCallback s_nullProgressCallbacks;
ProgressCallback* ProgressCallback::NullProgressCallback = &s_nullProgressCallbacks;

ProgressCallback::~ProgressCallback()
{
  std::unique_ptr<State> pNextState = std::move(m_saved_state);
  while (pNextState)
    pNextState = std::move(pNextState->next_saved_state);
}

void ProgressCallback::PushState()
{
  std::unique_ptr<State> pNewState = std::make_unique<State>();
  pNewState->cancellable = m_cancellable;
  pNewState->status_text = m_status_text;
  pNewState->progress_range = m_progress_range;
  pNewState->progress_value = m_progress_value;
  pNewState->base_progress_value = m_base_progress_value;
  pNewState->next_saved_state = std::move(m_saved_state);
  m_saved_state = std::move(pNewState);
}

void ProgressCallback::PopState()
{
  DebugAssert(m_saved_state);

  // impose the current position into the previous range
  const u32 new_progress_value =
    (m_progress_range != 0) ?
      static_cast<u32>(((float)m_progress_value / (float)m_progress_range) * (float)m_saved_state->progress_range) :
      m_saved_state->progress_value;

  const StateChange state_change = static_cast<StateChange>(
    ((m_status_text != m_saved_state->status_text) ? STATE_CHANGE_STATUS_TEXT : STATE_CHANGE_NONE) |
    ((m_progress_range != m_saved_state->progress_range || m_progress_value != new_progress_value) ?
       STATE_CHANGE_PROGRESS :
       STATE_CHANGE_NONE) |
    ((m_cancellable != m_saved_state->cancellable) ? STATE_CHANGE_CANCELLABLE : STATE_CHANGE_NONE));

  m_cancellable = m_saved_state->cancellable;
  m_status_text = std::move(m_saved_state->status_text);
  m_base_progress_value = m_saved_state->base_progress_value;
  m_progress_range = m_saved_state->progress_range;
  m_progress_value = new_progress_value;
  m_saved_state = std::move(m_saved_state->next_saved_state);

  if (state_change != STATE_CHANGE_NONE)
    StateChanged(state_change);
}

void ProgressCallback::SetState(u32 value, u32 range)
{
  SetState(m_status_text, value, range, m_cancellable);
}

void ProgressCallback::SetState(std::string_view status_text, u32 value, u32 range)
{
  SetState(status_text, value, range, m_cancellable);
}

void ProgressCallback::SetState(std::string_view status_text, u32 value, u32 range, bool cancellable)
{
  StateChange state_change = STATE_CHANGE_NONE;
  if (m_cancellable != cancellable)
  {
    m_cancellable = cancellable;
    state_change = static_cast<StateChange>(state_change | STATE_CHANGE_CANCELLABLE);
  }
  if (m_status_text != status_text)
  {
    m_status_text = status_text;
    state_change = static_cast<StateChange>(state_change | STATE_CHANGE_STATUS_TEXT);
  }

  const u32 prev_range = m_progress_range;
  const u32 prev_value = m_progress_value;

  if (m_saved_state)
  {
    // impose the previous range on this range
    m_progress_range = m_saved_state->progress_range * range;
    m_base_progress_value = m_progress_value = m_saved_state->progress_value * range;
  }
  else
  {
    m_progress_range = range;
    m_progress_value = value;
    m_base_progress_value = 0;
  }

  if (range != prev_range || value != prev_value)
    state_change = static_cast<StateChange>(state_change | STATE_CHANGE_PROGRESS);

  if (state_change != STATE_CHANGE_NONE)
    StateChanged(state_change);
}

bool ProgressCallback::IsCancellable() const
{
  return m_cancellable;
}

bool ProgressCallback::IsCancelled() const
{
  return m_cancelled;
}

void ProgressCallback::SetTitle(const std::string_view title)
{
}

void ProgressCallback::SetStatusText(const std::string_view text)
{
  if (m_status_text == text)
    return;

  INFO_LOG("Status: {}", text);

  m_status_text.assign(text);
  StateChanged(STATE_CHANGE_STATUS_TEXT);
}

void ProgressCallback::SetCancellable(bool cancellable)
{
  if (m_cancellable == cancellable)
    return;

  m_cancellable = cancellable;
  StateChanged(STATE_CHANGE_CANCELLABLE);
}

void ProgressCallback::SetProgressValue(u32 value)
{
  const u32 new_value = m_base_progress_value + value;
  if (m_progress_value != new_value)
  {
    m_progress_value = new_value;
    StateChanged(STATE_CHANGE_PROGRESS);
  }
}

void ProgressCallback::SetProgressRange(u32 range)
{
  const u32 prev_range = m_progress_range;
  const u32 prev_value = m_progress_value;

  if (m_saved_state)
  {
    // impose the previous range on this range
    m_progress_range = m_saved_state->progress_range * range;
    m_base_progress_value = m_progress_value = m_saved_state->progress_value * range;
  }
  else
  {
    m_progress_range = range;
    m_progress_value = 0;
    m_base_progress_value = 0;
  }

  if (m_progress_range != prev_range || m_progress_value != prev_value)
    StateChanged(STATE_CHANGE_PROGRESS);
}

void ProgressCallback::IncrementProgressValue()
{
  SetProgressValue((m_progress_value - m_base_progress_value) + 1);
}

void ProgressCallback::StateChanged(StateChange changed)
{
}

ProgressCallbackWithPrompt::~ProgressCallbackWithPrompt() = default;

void ProgressCallbackWithPrompt::AlertPrompt(PromptIcon icon, std::string_view message)
{
}

bool ProgressCallbackWithPrompt::ConfirmPrompt(PromptIcon icon, std::string_view message,
                                               std::string_view yes_text /*= {}*/, std::string_view no_text /*= {}*/)
{
  return false;
}

void ProgressCallbackWithPrompt::AppendMessage(std::string_view message)
{
  Log::Write(Log::PackCategory(Log::Channel::Host, Log::Level::Info, Log::Color::StrongOrange), message);
}

void ProgressCallbackWithPrompt::SetAutoClose(bool enabled)
{
}

void ProgressCallbackWithPrompt::SetStatusTextAndAppendMessage(std::string_view message)
{
  SetStatusText(message);
  AppendMessage(message);
}
