//===-- ThreadList.h --------------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef LLDB_TARGET_THREADLIST_H
#define LLDB_TARGET_THREADLIST_H

#include <mutex>
#include <vector>

#include "lldb/Target/Thread.h"
#include "lldb/Target/ThreadCollection.h"
#include "lldb/Utility/Iterable.h"
#include "lldb/Utility/UserID.h"
#include "lldb/lldb-private.h"

namespace lldb_private {

// This is a thread list with lots of functionality for use only by the process
// for which this is the thread list.  A generic container class with iterator
// functionality is ThreadCollection.
class ThreadList : public ThreadCollection {
  friend class Process;

public:
  ThreadList(Process &process);

  ThreadList(const ThreadList &rhs);

  ~ThreadList() override;

  /// Precondition: both thread lists must be belong to the same process.
  const ThreadList &operator=(const ThreadList &rhs);

  uint32_t GetSize(bool can_update = true);

  // Return the selected thread if there is one.  Otherwise, return the thread
  // selected at index 0.
  lldb::ThreadSP GetSelectedThread();

  // Manage the thread to use for running expressions.  This is usually the
  // Selected thread, but sometimes (e.g. when evaluating breakpoint conditions
  // & stop hooks) it isn't.
  class ExpressionExecutionThreadPusher {
  public:
    ExpressionExecutionThreadPusher(ThreadList &thread_list, lldb::tid_t tid)
        : m_thread_list(&thread_list), m_tid(tid) {
      m_thread_list->PushExpressionExecutionThread(m_tid);
    }

    ExpressionExecutionThreadPusher(lldb::ThreadSP thread_sp);

    ~ExpressionExecutionThreadPusher() {
      if (m_thread_list && m_tid != LLDB_INVALID_THREAD_ID)
        m_thread_list->PopExpressionExecutionThread(m_tid);
    }

  private:
    ThreadList *m_thread_list;
    lldb::tid_t m_tid;
  };

  lldb::ThreadSP GetExpressionExecutionThread();

protected:
  void PushExpressionExecutionThread(lldb::tid_t tid);

  void PopExpressionExecutionThread(lldb::tid_t tid);

public:
  bool SetSelectedThreadByID(lldb::tid_t tid, bool notify = false);

  bool SetSelectedThreadByIndexID(uint32_t index_id, bool notify = false);

  void Clear();

  void Flush();

  void Destroy();

  // Note that "idx" is not the same as the "thread_index". It is a zero based
  // index to accessing the current threads, whereas "thread_index" is a unique
  // index assigned
  lldb::ThreadSP GetThreadAtIndex(uint32_t idx, bool can_update = true);

  lldb::ThreadSP FindThreadByID(lldb::tid_t tid, bool can_update = true);

  lldb::ThreadSP FindThreadByProtocolID(lldb::tid_t tid,
                                        bool can_update = true);

  lldb::ThreadSP RemoveThreadByID(lldb::tid_t tid, bool can_update = true);

  lldb::ThreadSP RemoveThreadByProtocolID(lldb::tid_t tid,
                                          bool can_update = true);

  lldb::ThreadSP FindThreadByIndexID(uint32_t index_id, bool can_update = true);

  lldb::ThreadSP GetThreadSPForThreadPtr(Thread *thread_ptr);

  bool ShouldStop(Event *event_ptr);

  Vote ShouldReportStop(Event *event_ptr);

  Vote ShouldReportRun(Event *event_ptr);

  void RefreshStateAfterStop();

  /// The thread list asks tells all the threads it is about to resume.
  /// If a thread can "resume" without having to resume the target, it
  /// will return false for WillResume, and then the process will not be
  /// restarted.
  /// Sets *direction to the run direction of the thread(s) that will
  /// be resumed. If threads that we want to run disagree about the
  /// direction, we execute forwards and pop any of the thread plans
  /// that requested reverse execution.
  ///
  /// \return
  ///    \b true instructs the process to resume normally,
  ///    \b false means start & stopped events will be generated, but
  ///    the process will not actually run.  The thread must then return
  ///    the correct StopInfo when asked.
  ///
  bool WillResume(lldb::RunDirection &direction);

  void DidResume();

  void DidStop();

  void DiscardThreadPlans();

  uint32_t GetStopID() const;

  void SetStopID(uint32_t stop_id);

  std::recursive_mutex &GetMutex() const override;

  /// Precondition: both thread lists must be belong to the same process.
  void Update(ThreadList &rhs);

protected:
  void SetShouldReportStop(Vote vote);

  void NotifySelectedThreadChanged(lldb::tid_t tid);

  // Classes that inherit from Process can see and modify these
  Process &m_process; ///< The process that manages this thread list.
  uint32_t
      m_stop_id; ///< The process stop ID that this thread list is valid for.
  lldb::tid_t
      m_selected_tid; ///< For targets that need the notion of a current thread.
  std::vector<lldb::tid_t> m_expression_tid_stack;

private:
  ThreadList() = delete;
};

} // namespace lldb_private

#endif // LLDB_TARGET_THREADLIST_H
