#pragma once

#include <std/fwd/string.hpp>
#include <std/fwd/vector.hpp>
#include <optional>
#include <cstdint>
#include <utility>
#include <variant>

namespace euler::syscall {

  enum class stream_result : uint64_t {
    success,
    bad_handle,
    io_error,
    out_of_bounds,
    does_not_exist,
    not_a_regular_file,
    not_an_executable,
    not_writable,
    not_seekable,
    socket_id_already_used,
    socket_id_not_in_use,
    socket_listener_closed,
    other_end_closed,
    already_exists,
    not_sized,
    not_readable
  };

  enum class seek_from : uint8_t {
    beginning,
    end,
    current_position
  };

  struct mouse_packet {
    bool left_button_down;
    bool right_button_down;
    bool middle_button_down;
    int16_t x_changed;
    int16_t y_changed;
  };

  struct key_packet {
    bool was_key_up_event;
    bool num_lock;
    bool caps_lock;
    bool right_win;
    bool left_win;
    bool right_alt;
    bool left_alt;
    bool right_ctrl;
    bool left_ctrl;
    bool right_shift;
    bool left_shift;
    uint8_t key_code;
  };

  typedef uint32_t encoded_color;
  typedef uint64_t stream_handle;
  typedef uint64_t listener_handle;
  typedef uint64_t process_handle;

  encoded_color encode_color(
    uint8_t r, uint8_t g, uint8_t b);

  void get_framebuffer(
    encoded_color *&buffer_out, uint32_t &width_out,
    uint32_t &height_out, uint32_t &pitch_out);

  stream_result open_file(
    const std::string &file_path, bool allow_creation,
    bool only_allow_creation, stream_handle &handle_out);

  [[noreturn]] void end_this_thread(int32_t exit_code);

  void *get_new_pages(uint64_t n_pages);

  std::variant<mouse_packet, key_packet> get_input_packet();

  void create_private_socket(
    stream_handle &end_1_out, stream_handle &end_2_out);

  stream_result create_socket_listener(
    const std::string &id, listener_handle &handle_out);

  void stop_socket_listener(listener_handle handle);

  stream_result accept_socket_connection(
    listener_handle listener, stream_handle &stream_out);

  stream_result connect_to_socket(
    const std::string &id, stream_handle &handle_out);

  void close_stream(stream_handle handle);

  stream_result seek_stream(
    stream_handle handle, seek_from from, int64_t offset);

  stream_result read_from_stream(
    stream_handle handle, uint64_t bytes, void *into);

  stream_result write_to_stream(
    stream_handle handle, uint64_t bytes, const void *from);

  stream_result get_stream_length(
    stream_handle handle, uint64_t &length_out);

  stream_result start_process(
    const std::string &file_path,
    const std::vector<std::pair<std::string, std::string>> &environment_variables,
    const std::vector<std::pair<stream_handle, stream_result>> &gifted_streams,
    process_handle &handle_out);

  [[noreturn]] void end_this_process(int32_t exit_code);

  stream_result set_stream_length(
    stream_handle handle, uint64_t new_length);

  stream_result get_other_end_process_handle(
    stream_handle stream, process_handle &process_out);

  //entry_point must not return
  void start_thread(void (*entry_point)(uint64_t), uint64_t arg);

  //entry_point must not return
  template <class obj_t>
  void start_thread(void (*entry_point)(obj_t *), obj_t *arg) {
    start_thread((void (*)(uint64_t))entry_point, (uint64_t)arg);
  }

  //entry_point must not return
  template <class obj_t>
  void start_thread(void (*entry_point)(const obj_t *), const obj_t *arg) {
    start_thread((void (*)(uint64_t))entry_point, (uint64_t)arg);
  }

  //entry_point must not return
  template <class obj_t>
  void start_thread(void (*entry_point)(obj_t &), obj_t &arg) {
    start_thread((void (*)(uint64_t))entry_point, (uint64_t)&arg);
  }

  //entry_point must not return
  template <class obj_t>
  void start_thread(void (*entry_point)(const obj_t &), const obj_t &arg) {
    start_thread((void (*)(uint64_t))entry_point, (uint64_t)&arg);
  }

  //entry_point must not return
  void start_thread(void (*entry_point)());

  //return value is number of bytes cleared
  uint64_t clear_socket_read_queue(stream_handle handle);

  std::optional<std::string> try_get_environment_variable(
    const std::string &name);

  void set_thread_name(const std::string &name);

}

#include <string>
#include <vector>
