/**
 * Looking Glass
 * Copyright © 2017-2026 The Looking Glass Authors
 * https://looking-glass.io
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc., 59
 * Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

#pragma once

#include "CSRWLock.h"

#include <Windows.h>
#include <stdint.h>

enum : uint32_t
{
  FRAME_SCHEDULE_ACTIVE    = 0x1,
  FRAME_SCHEDULE_RELEASE   = 0x2,
  FRAME_SCHEDULE_RESET     = 0x4,
  FRAME_SCHEDULE_IMMEDIATE = 0x8,
};

struct FrameScheduleUpdate
{
  uint32_t clientID;
  uint32_t generation;
  uint32_t flags;
  uint64_t period;
  uint64_t targetSlack;
  int64_t  phaseError;
  uint32_t feedbackFrameSerial;
  uint32_t feedbackScheduleEpoch;
  uint32_t feedbackDeadlineSerial;
  uint32_t lease;
};

class CFrameScheduler
{
public:
  static const unsigned MAX_CLIENTS = 8;

  struct Schedule
  {
    uint32_t clientID;
    uint32_t generation;
    uint32_t epoch;
    uint64_t period;
    uint64_t targetSlack;
    uint64_t deadline;
    uint64_t forceTicket;
    uint64_t republishTicket;
    uint32_t deadlineSerial;
    uint32_t deliveryDeadlineSerial;
    bool     phaseEligible;
  };

private:
  struct Client
  {
    uint32_t clientID;
    uint32_t generation;
    uint64_t period;
    uint64_t targetSlack;
    uint64_t expiry;
    uint64_t nextDelivery;
    uint32_t lastFeedbackDeadlineSerial;
    uint32_t lastDeliveredFrameSerial;
    bool     subscribed;
    bool     ownerCapable;
    bool     subscriptionSeen;
    bool     active;
    bool     immediate;
    bool     deliveredFrameValid;
  };

  struct Publication
  {
    uint32_t generation;
    uint32_t epoch;
    uint32_t deadlineSerial;
    uint32_t frameSerial;
    uint64_t deadline;
    bool     committed;
    bool     completed;
    bool     phaseValid;
    bool     accepted;
  };

  static const unsigned PUBLICATION_HISTORY_SIZE  = 128;
  static const unsigned WORK_TIMING_HISTORY_SIZE  = 32;

  mutable CSRWLock m_lock;
  mutable CSRWLock m_wakeLock;
  HANDLE           m_wakeEvent            = nullptr;
  HANDLE           m_sharedWakeEvent      = nullptr;
  Client           m_clients[MAX_CLIENTS] = {};
  Schedule         m_schedule             = {};
  bool             m_scheduling           = false;
  uint32_t         m_epoch                = 0;

  // A result acknowledges only the request tickets captured by its attempt.
  uint64_t m_forceRequestTicket     = 0;
  uint64_t m_forceAckTicket         = 0;
  uint64_t m_republishRequestTicket = 0;
  uint64_t m_republishAckTicket     = 0;

  uint64_t m_lastArrival                          = 0;
  uint64_t m_guestPeriod                          = 0;
  uint64_t m_workEstimate                         = 0;
  uint64_t m_workTiming[WORK_TIMING_HISTORY_SIZE] = {};
  unsigned m_workTimingCount                      = 0;
  unsigned m_workTimingIndex                      = 0;
  uint64_t m_nextDeadline                         = 0;
  uint32_t m_deadlineSerial                       = 0;
  int64_t  m_pendingCorrection                    = 0;

  Publication m_publications[PUBLICATION_HISTORY_SIZE] = {};
  unsigned    m_publicationIndex                       = 0;

  int64_t  m_lastPhaseError   = 0;
  uint64_t m_acquiredFrames   = 0;
  uint64_t m_skippedFrames    = 0;
  uint64_t m_publishedFrames  = 0;
  uint64_t m_lastLog          = 0;
  uint64_t m_lastLogAcquired  = 0;
  uint64_t m_lastLogSkipped   = 0;
  uint64_t m_lastLogPublished = 0;

  Client * FindClient(uint32_t clientID);
  Client * FindOrAllocateClient(uint32_t clientID);
  Publication * FindPublication(const Schedule& schedule,
    uint32_t frameSerial);
  bool ElectOwner(uint64_t now, uint32_t resetClientID = 0);
  bool ApplyFeedback(Client& client, const FrameScheduleUpdate& schedule);
  void AdvanceCurrentDeadline();
  void AdvanceDeadlineSerial(uint64_t count);
  void AdvanceDeadline(uint64_t now);
  static void AdvanceDelivery(Client& client, uint64_t now);
  void WakePublisher() const;

public:
  CFrameScheduler();
  ~CFrameScheduler();

  static uint64_t Nanotime();

  void Reset();
  void UpdateSubscribers(const uint32_t * clientIDs, unsigned count,
    const uint32_t * ownerClientIDs, unsigned ownerCount, uint64_t now);
  bool UpdateSchedule(uint32_t sourceClientID,
    const FrameScheduleUpdate& schedule, uint64_t now);
  bool GetSchedule(Schedule& schedule) const;
  HANDLE GetWakeEvent() const { return m_wakeEvent; }
  void SetSharedWakeEvent(HANDLE event);
  void ObserveFrame(uint64_t now);
  void ForceFrame();
  bool GetPublishTarget(uint64_t now, uint64_t& target,
    Schedule& schedule, bool& periodic, bool& republish);
  void FrameMissed(const Schedule& schedule, uint64_t now, bool periodic);
  void FrameSuperseded();
  bool TryFrameSubmitted(const Schedule& schedule, uint32_t frameSerial);
  void FrameCommitted(const Schedule& schedule, uint32_t frameSerial,
    uint64_t now, bool periodic);
  void FramePublished(const Schedule& schedule, uint32_t frameSerial);
  void FrameRetained(const Schedule& schedule);
  void FrameFailed();
  void FrameRepublished(const Schedule& schedule, uint32_t frameSerial);
  bool TryFrameCompleted(const Schedule& schedule, uint32_t frameSerial,
    uint64_t completedAt);
  unsigned GetSecondaryRecipients(const uint32_t * clientIDs,
    unsigned count, uint32_t frameSerial, uint64_t now,
    uint32_t * recipients) const;
  bool GetSecondaryTarget(uint32_t frameSerial, uint64_t now,
    const uint32_t * blockedClientIDs, unsigned blockedCount,
    uint64_t& target) const;
  void FrameDelivered(const uint32_t * clientIDs, unsigned count,
    uint32_t frameSerial, uint64_t now);
  void RequestRepublish();
  void NotifyPublisher() const { WakePublisher(); }
  void TryRecordFrameTiming(uint64_t duration);
  void LogStatistics(uint64_t now);
};
