/*
 * Copyright (c) 2014, 2020, Oracle and/or its affiliates. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License, version 2.0,
 * as published by the Free Software Foundation.
 *
 * This program is also distributed with certain software (including
 * but not limited to OpenSSL) that is licensed under separate terms, as
 * designated in a particular file or component or in included license
 * documentation.  The authors of MySQL hereby grant you an additional
 * permission to link the program and your derivative works with the
 * separately licensed software that they have included with MySQL.
 * 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, version 2.0, 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.,
 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 */

#ifndef MYSQLSHDK_INCLUDE_SHELLCORE_INTERRUPT_HANDLER_H_
#define MYSQLSHDK_INCLUDE_SHELLCORE_INTERRUPT_HANDLER_H_

#include <atomic>
#include <exception>
#include <functional>
#include <memory>
#include <mutex>
#include <thread>

#include "mysqlshdk/include/shellcore/sigint_event.h"

namespace shcore {

// Max number of SIGINT handlers to allow at a time in the stack
static constexpr int kMax_interrupt_handlers = 8;

class Interrupt_helper {
 public:
  virtual ~Interrupt_helper() = default;
  virtual void setup() = 0;
  virtual void block() = 0;
  virtual void unblock(bool clean_pending) = 0;
};

class Interrupts {
 public:
  static std::shared_ptr<Interrupts> create(Interrupt_helper *helper);
  void setup();

  bool in_creator_thread();

  void push_handler(const std::function<bool()> &handler);
  void pop_handler();

  void set_propagate_interrupt(bool flag);
  bool propagates_interrupt();

  void block();
  void unblock(bool clear_pending = false);

  void interrupt();

  void wait(uint32_t ms);

 private:
  explicit Interrupts(Interrupt_helper *helper);

  Interrupt_helper *m_helper;
  std::function<bool()> m_handlers[kMax_interrupt_handlers];
  std::atomic<int> m_num_handlers;
  std::mutex m_handler_mutex;
  bool m_propagates_interrupt;
  std::thread::id m_creator_thread_id;
  Sigint_event s_sigint_event;
};

std::shared_ptr<Interrupts> current_interrupt(bool allow_empty = false);

struct Block_interrupts {
  explicit Block_interrupts(bool clear_on_unblock = false)
      : m_clear_on_unblock(clear_on_unblock) {
    current_interrupt()->block();
  }

  ~Block_interrupts() { current_interrupt()->unblock(m_clear_on_unblock); }

  void clear_pending(bool flag = true) { m_clear_on_unblock = flag; }

  bool m_clear_on_unblock;
};

class Interrupt_handler final {
 public:
  Interrupt_handler() = delete;

  explicit Interrupt_handler(const std::function<bool()> &handler,
                             bool skip = false);

  Interrupt_handler(const Interrupt_handler &) = delete;
  Interrupt_handler(Interrupt_handler &&) = delete;

  Interrupt_handler &operator=(const Interrupt_handler &) = delete;
  Interrupt_handler &operator=(Interrupt_handler &&) = delete;

  ~Interrupt_handler();

 private:
  bool m_skip;
};

}  // namespace shcore

#endif  // MYSQLSHDK_INCLUDE_SHELLCORE_INTERRUPT_HANDLER_H_
