// Copyright 2008 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.

#include <cinttypes>
#include <mutex>
#include <string>
#include <vector>

#include "Common/ChunkFile.h"
#include "Common/FifoQueue.h"
#include "Common/StringUtil.h"
#include "Common/Thread.h"

#include "Core/ConfigManager.h"
#include "Core/Core.h"
#include "Core/CoreTiming.h"
#include "Core/PowerPC/PowerPC.h"

#include "VideoCommon/Fifo.h"
#include "VideoCommon/VideoBackendBase.h"

#define MAX_SLICE_LENGTH 20000

namespace CoreTiming
{
struct EventType
{
  TimedCallback callback;
  std::string name;
};

static std::vector<EventType> event_types;

struct BaseEvent
{
  s64 time;
  u64 userdata;
  int type;
};

typedef LinkedListItem<BaseEvent> Event;

// STATE_TO_SAVE
static Event* first;
static std::mutex tsWriteLock;
static Common::FifoQueue<BaseEvent, false> tsQueue;

// event pools
static Event* eventPool = nullptr;

static float s_lastOCFactor;
float g_lastOCFactor_inverted;
int g_slicelength;
static int maxslicelength = MAX_SLICE_LENGTH;

static s64 idledCycles;
static u32 fakeDecStartValue;
static u64 fakeDecStartTicks;

// Are we in a function that has been called from Advance()
static bool globalTimerIsSane;

s64 g_globalTimer;
u64 g_fakeTBStartValue;
u64 g_fakeTBStartTicks;

static int ev_lost;

static Event* GetNewEvent()
{
  if (!eventPool)
    return new Event;

  Event* ev = eventPool;
  eventPool = ev->next;
  return ev;
}

static void FreeEvent(Event* ev)
{
  ev->next = eventPool;
  eventPool = ev;
}

static void EmptyTimedCallback(u64 userdata, s64 cyclesLate)
{
}

// Changing the CPU speed in Dolphin isn't actually done by changing the physical clock rate,
// but by changing the amount of work done in a particular amount of time. This tends to be more
// compatible because it stops the games from actually knowing directly that the clock rate has
// changed, and ensures that anything based on waiting a specific number of cycles still works.
//
// Technically it might be more accurate to call this changing the IPC instead of the CPU speed,
// but the effect is largely the same.
static int DowncountToCycles(int downcount)
{
  return (int)(downcount * g_lastOCFactor_inverted);
}

static int CyclesToDowncount(int cycles)
{
  return (int)(cycles * s_lastOCFactor);
}

int RegisterEvent(const std::string& name, TimedCallback callback)
{
  EventType type;
  type.name = name;
  type.callback = callback;

  // check for existing type with same name.
  // we want event type names to remain unique so that we can use them for serialization.
  for (auto& event_type : event_types)
  {
    if (name == event_type.name)
    {
      WARN_LOG(
          POWERPC,
          "Discarded old event type \"%s\" because a new type with the same name was registered.",
          name.c_str());
      // we don't know if someone might be holding on to the type index,
      // so we gut the old event type instead of actually removing it.
      event_type.name = "_discarded_event";
      event_type.callback = &EmptyTimedCallback;
    }
  }

  event_types.push_back(type);
  return (int)event_types.size() - 1;
}

void UnregisterAllEvents()
{
  if (first)
    PanicAlert("Cannot unregister events with events pending");
  event_types.clear();
}

void Init()
{
  s_lastOCFactor = SConfig::GetInstance().m_OCEnable ? SConfig::GetInstance().m_OCFactor : 1.0f;
  g_lastOCFactor_inverted = 1.0f / s_lastOCFactor;
  PowerPC::ppcState.downcount = CyclesToDowncount(maxslicelength);
  g_slicelength = maxslicelength;
  g_globalTimer = 0;
  idledCycles = 0;
  globalTimerIsSane = true;

  ev_lost = RegisterEvent("_lost_event", &EmptyTimedCallback);
}

void Shutdown()
{
  std::lock_guard<std::mutex> lk(tsWriteLock);
  MoveEvents();
  ClearPendingEvents();
  UnregisterAllEvents();

  while (eventPool)
  {
    Event* ev = eventPool;
    eventPool = ev->next;
    delete ev;
  }
}

static void EventDoState(PointerWrap& p, BaseEvent* ev)
{
  p.Do(ev->time);

  // this is why we can't have (nice things) pointers as userdata
  p.Do(ev->userdata);

  // we can't savestate ev->type directly because events might not get registered in the same order
  // (or at all) every time.
  // so, we savestate the event's type's name, and derive ev->type from that when loading.
  std::string name;
  if (p.GetMode() != PointerWrap::MODE_READ)
    name = event_types[ev->type].name;

  p.Do(name);
  if (p.GetMode() == PointerWrap::MODE_READ)
  {
    bool foundMatch = false;
    for (unsigned int i = 0; i < event_types.size(); ++i)
    {
      if (name == event_types[i].name)
      {
        ev->type = i;
        foundMatch = true;
        break;
      }
    }
    if (!foundMatch)
    {
      WARN_LOG(POWERPC,
               "Lost event from savestate because its type, \"%s\", has not been registered.",
               name.c_str());
      ev->type = ev_lost;
    }
  }
}

void DoState(PointerWrap& p)
{
  std::lock_guard<std::mutex> lk(tsWriteLock);
  p.Do(g_slicelength);
  p.Do(g_globalTimer);
  p.Do(idledCycles);
  p.Do(fakeDecStartValue);
  p.Do(fakeDecStartTicks);
  p.Do(g_fakeTBStartValue);
  p.Do(g_fakeTBStartTicks);
  p.Do(s_lastOCFactor);
  if (p.GetMode() == PointerWrap::MODE_READ)
    g_lastOCFactor_inverted = 1.0f / s_lastOCFactor;

  p.DoMarker("CoreTimingData");

  MoveEvents();

  p.DoLinkedList<BaseEvent, GetNewEvent, FreeEvent, EventDoState>(first);
  p.DoMarker("CoreTimingEvents");
}

// This should only be called from the CPU thread, if you are calling it any other thread, you are
// doing something evil
u64 GetTicks()
{
  u64 ticks = (u64)g_globalTimer;
  if (!globalTimerIsSane)
  {
    int downcount = DowncountToCycles(PowerPC::ppcState.downcount);
    ticks += g_slicelength - downcount;
  }
  return ticks;
}

u64 GetIdleTicks()
{
  return (u64)idledCycles;
}

// This is to be called when outside threads, such as the graphics thread, wants to
// schedule things to be executed on the main thread.
void ScheduleEvent_Threadsafe(s64 cyclesIntoFuture, int event_type, u64 userdata)
{
  _assert_msg_(POWERPC, !Core::IsCPUThread(), "ScheduleEvent_Threadsafe from wrong thread");
  if (Core::g_want_determinism)
  {
    ERROR_LOG(POWERPC,
              "Someone scheduled an off-thread \"%s\" event while netplay or movie play/record "
              "was active.  This is likely to cause a desync.",
              event_types[event_type].name.c_str());
  }
  std::lock_guard<std::mutex> lk(tsWriteLock);
  Event ne;
  ne.time = g_globalTimer + cyclesIntoFuture;
  ne.type = event_type;
  ne.userdata = userdata;
  tsQueue.Push(ne);
}

// Executes an event immediately, then returns.
void ScheduleEvent_Immediate(int event_type, u64 userdata)
{
  _assert_msg_(POWERPC, Core::IsCPUThread(), "ScheduleEvent_Immediate from wrong thread");
  event_types[event_type].callback(userdata, 0);
}

// Same as ScheduleEvent_Threadsafe(0, ...) EXCEPT if we are already on the CPU thread
// in which case this is the same as ScheduleEvent_Immediate.
void ScheduleEvent_Threadsafe_Immediate(int event_type, u64 userdata)
{
  if (Core::IsCPUThread())
  {
    event_types[event_type].callback(userdata, 0);
  }
  else
  {
    ScheduleEvent_Threadsafe(0, event_type, userdata);
  }
}

// To be used from any thread, including the CPU thread
void ScheduleEvent_AnyThread(s64 cyclesIntoFuture, int event_type, u64 userdata)
{
  if (Core::IsCPUThread())
    ScheduleEvent(cyclesIntoFuture, event_type, userdata);
  else
    ScheduleEvent_Threadsafe(cyclesIntoFuture, event_type, userdata);
}

void ClearPendingEvents()
{
  while (first)
  {
    Event* e = first->next;
    FreeEvent(first);
    first = e;
  }
}

static void AddEventToQueue(Event* ne)
{
  Event* prev = nullptr;
  Event** pNext = &first;
  for (;;)
  {
    Event*& next = *pNext;
    if (!next || ne->time < next->time)
    {
      ne->next = next;
      next = ne;
      break;
    }
    prev = next;
    pNext = &prev->next;
  }
}

// This must be run ONLY from within the CPU thread
// cyclesIntoFuture may be VERY inaccurate if called from anything else
// than Advance
void ScheduleEvent(s64 cyclesIntoFuture, int event_type, u64 userdata)
{
  _assert_msg_(POWERPC, Core::IsCPUThread() || Core::GetState() == Core::CORE_PAUSE,
               "ScheduleEvent from wrong thread");

  Event* ne = GetNewEvent();
  ne->userdata = userdata;
  ne->type = event_type;
  ne->time = GetTicks() + cyclesIntoFuture;

  // If this event needs to be scheduled before the next advance(), force one early
  if (!globalTimerIsSane)
    ForceExceptionCheck(cyclesIntoFuture);

  AddEventToQueue(ne);
}

void RemoveEvent(int event_type)
{
  while (first && first->type == event_type)
  {
    Event* next = first->next;
    FreeEvent(first);
    first = next;
  }

  if (!first)
    return;

  Event* prev = first;
  Event* ptr = prev->next;
  while (ptr)
  {
    if (ptr->type == event_type)
    {
      prev->next = ptr->next;
      FreeEvent(ptr);
      ptr = prev->next;
    }
    else
    {
      prev = ptr;
      ptr = ptr->next;
    }
  }
}

void RemoveAllEvents(int event_type)
{
  MoveEvents();
  RemoveEvent(event_type);
}

void ForceExceptionCheck(s64 cycles)
{
  if (s64(DowncountToCycles(PowerPC::ppcState.downcount)) > cycles)
  {
    // downcount is always (much) smaller than MAX_INT so we can safely cast cycles to an int here.
    g_slicelength -=
        (DowncountToCycles(PowerPC::ppcState.downcount) -
         (int)cycles);  // Account for cycles already executed by adjusting the g_slicelength
    PowerPC::ppcState.downcount = CyclesToDowncount((int)cycles);
  }
}

void MoveEvents()
{
  BaseEvent sevt;
  while (tsQueue.Pop(sevt))
  {
    Event* evt = GetNewEvent();
    evt->time = sevt.time;
    evt->userdata = sevt.userdata;
    evt->type = sevt.type;
    AddEventToQueue(evt);
  }
}

void Advance()
{
  MoveEvents();

  int cyclesExecuted = g_slicelength - DowncountToCycles(PowerPC::ppcState.downcount);
  g_globalTimer += cyclesExecuted;
  s_lastOCFactor = SConfig::GetInstance().m_OCEnable ? SConfig::GetInstance().m_OCFactor : 1.0f;
  g_lastOCFactor_inverted = 1.0f / s_lastOCFactor;
  g_slicelength = maxslicelength;

  globalTimerIsSane = true;

  while (first && first->time <= g_globalTimer)
  {
    // LOG(POWERPC, "[Scheduler] %s     (%lld, %lld) ",
    //             event_types[first->type].name ? event_types[first->type].name : "?",
    //             (u64)g_globalTimer, (u64)first->time);
    Event* evt = first;
    first = first->next;
    event_types[evt->type].callback(evt->userdata, (int)(g_globalTimer - evt->time));
    FreeEvent(evt);
  }

  globalTimerIsSane = false;

  if (first)
  {
    g_slicelength = (int)(first->time - g_globalTimer);
    if (g_slicelength > maxslicelength)
      g_slicelength = maxslicelength;
  }

  PowerPC::ppcState.downcount = CyclesToDowncount(g_slicelength);

  // Check for any external exceptions.
  // It's important to do this after processing events otherwise any exceptions will be delayed
  // until the next slice:
  //        Pokemon Box refuses to boot if the first exception from the audio DMA is received late
  PowerPC::CheckExternalExceptions();
}

void LogPendingEvents()
{
  Event* ptr = first;
  while (ptr)
  {
    INFO_LOG(POWERPC, "PENDING: Now: %" PRId64 " Pending: %" PRId64 " Type: %d", g_globalTimer,
             ptr->time, ptr->type);
    ptr = ptr->next;
  }
}

void Idle()
{
  if (SConfig::GetInstance().bSyncGPUOnSkipIdleHack)
  {
    // When the FIFO is processing data we must not advance because in this way
    // the VI will be desynchronized. So, We are waiting until the FIFO finish and
    // while we process only the events required by the FIFO.
    Fifo::FlushGpu();
  }

  idledCycles += DowncountToCycles(PowerPC::ppcState.downcount);
  PowerPC::ppcState.downcount = 0;
}

std::string GetScheduledEventsSummary()
{
  Event* ptr = first;
  std::string text = "Scheduled events\n";
  text.reserve(1000);
  while (ptr)
  {
    unsigned int t = ptr->type;
    if (t >= event_types.size())
      PanicAlertT("Invalid event type %i", t);

    const std::string& name = event_types[ptr->type].name;

    text += StringFromFormat("%s : %" PRIi64 " %016" PRIx64 "\n", name.c_str(), ptr->time,
                             ptr->userdata);
    ptr = ptr->next;
  }
  return text;
}

u32 GetFakeDecStartValue()
{
  return fakeDecStartValue;
}

void SetFakeDecStartValue(u32 val)
{
  fakeDecStartValue = val;
}

u64 GetFakeDecStartTicks()
{
  return fakeDecStartTicks;
}

void SetFakeDecStartTicks(u64 val)
{
  fakeDecStartTicks = val;
}

u64 GetFakeTBStartValue()
{
  return g_fakeTBStartValue;
}

void SetFakeTBStartValue(u64 val)
{
  g_fakeTBStartValue = val;
}

u64 GetFakeTBStartTicks()
{
  return g_fakeTBStartTicks;
}

void SetFakeTBStartTicks(u64 val)
{
  g_fakeTBStartTicks = val;
}

}  // namespace
