<commit_before>// This Source Code Form is licensed MPL-2.0: http://mozilla.org/MPL/2.0

#include <websocketpp/config/asio_no_tls.hpp>
#include <websocketpp/server.hpp>
typedef websocketpp::server<websocketpp::config::asio> server;

#include <jsonipc/jsonipc.hh>

#include <bse/bseenums.hh>      // enums API interfaces, etc
#include <bse/platform.hh>
#include <bse/randomhash.hh>
#include <bse/regex.hh>
#include <bse/bse.hh>   // Bse::init_async
#include <limits.h>
#include <stdlib.h>

// == Aida Workarounds ==
// Manually convert between Aida Handle types (that Jsonipc cannot know about)
// and shared_ptr to Iface types.
// Bse::*Seq as std::vector
template<typename Bse_Seq, typename Bse_IfaceP>
struct ConvertSeq {
  static Bse_Seq
  from_json (const Jsonipc::JsonValue &jarray)
  {
    std::vector<Bse_IfaceP> pointers = Jsonipc::from_json<std::vector<Bse_IfaceP>> (jarray);
    Bse_Seq seq;
    seq.reserve (pointers.size());
    for (auto &ptr : pointers)
      seq.emplace_back (ptr->__handle__());
    return seq;
  }
  static Jsonipc::JsonValue
  to_json (const Bse_Seq &vec, Jsonipc::JsonAllocator &allocator)
  {
    std::vector<Bse_IfaceP> pointers;
    pointers.reserve (vec.size());
    for (auto &itemhandle : vec)
      pointers.emplace_back (itemhandle.__iface__()->template as<Bse_IfaceP>());
    return Jsonipc::to_json (pointers, allocator);
  }
};

/// Convert between Aida::Any and Jsonipc::JsonValue
struct ConvertAny {
  static Aida::Any
  from_json (const Jsonipc::JsonValue &v, const Aida::Any &fallback = Aida::Any())
  {
    Aida::Any any;
    switch (v.GetType())
      {
      case rapidjson::kNullType:
        return fallback;
      case rapidjson::kFalseType:
        any.set<bool> (false);
        break;
      case rapidjson::kTrueType:
        any.set<bool> (true);
        break;
      case rapidjson::kStringType:
        any.set<std::string> (Jsonipc::from_json<std::string> (v));
        break;
      case rapidjson::kNumberType:
        if      (v.IsInt())     any.set<int32_t> (v.GetInt());
        else if (v.IsUint())    any.set<int64_t> (v.GetUint());
        else if (v.IsInt64())   any.set<int64_t> (v.GetInt64());
        else                    any.set<double>  (v.GetDouble());
      case rapidjson::kArrayType:
        sequence_from_json_array (any, v);
        break;
      case rapidjson::kObjectType:
        record_from_json_object (any, v);
        break;
    };
    return any;
  }
  static Jsonipc::JsonValue
  to_json (const Aida::Any &any, Jsonipc::JsonAllocator &allocator)
  {
    using namespace Jsonipc;
    switch (int (any.kind()))
      {
      case Aida::BOOL:          return JsonValue (any.get<bool>());
      case Aida::INT32:         return JsonValue (any.get<int32_t>());
      case Aida::INT64:         return JsonValue (any.get<int64_t>());
      case Aida::FLOAT64:       return JsonValue (any.get<double>());
      case Aida::ENUM:          return Jsonipc::to_json (any.get<std::string>(), allocator);
      case Aida::STRING:        return Jsonipc::to_json (any.get<std::string>(), allocator);
      case Aida::SEQUENCE:      return sequence_to_json_array (any.get<const Aida::AnySeq&>(), allocator);
      case Aida::RECORD:        return record_to_json_object (any.get<const Aida::AnyRec&>(), allocator);
      case Aida::INSTANCE:      return instance_to_json_object (any, allocator);
      }
    return JsonValue(); // null
  }
  static void
  sequence_from_json_array (Aida::Any &any, const Jsonipc::JsonValue &v)
  {
    const size_t l = v.Size();
    Aida::AnySeq s;
    for (size_t i = 0; i < l; ++i)
      s.push_back (ConvertAny::from_json (v[i]));
    any.set (s);
  }
  static Jsonipc::JsonValue
  sequence_to_json_array (const Aida::AnySeq &seq, Jsonipc::JsonAllocator &allocator)
  {
    const size_t l = seq.size();
    Jsonipc::JsonValue jarray (rapidjson::kArrayType);
    jarray.Reserve (l, allocator);
    for (size_t i = 0; i < l; ++i)
      jarray.PushBack (ConvertAny::to_json (seq[i], allocator).Move(), allocator);
    return jarray;
  }
  static void
  record_from_json_object (Aida::Any &any, const Jsonipc::JsonValue &v)
  {
    Aida::AnyRec r;
    for (const auto &field : v.GetObject())
      {
        const std::string key = field.name.GetString();
        if (key == "$class" || key == "$id")    // actually Aida::INSTANCE
          return instance_from_json_object (any, v);
        r[key] = ConvertAny::from_json (field.value);
      }
    any.set (r);
  }
  static Jsonipc::JsonValue
  record_to_json_object (const Aida::AnyRec &r, Jsonipc::JsonAllocator &allocator)
  {
    Jsonipc::JsonValue jobject (rapidjson::kObjectType);
    jobject.MemberReserve (r.size(), allocator);
    for (auto const &field : r)
      jobject.AddMember (Jsonipc::JsonValue (field.name.c_str(), allocator),
                         ConvertAny::to_json (field, allocator).Move(), allocator);
    return jobject;
  }
  static void
  instance_from_json_object (Aida::Any &any, const Jsonipc::JsonValue &v)
  {
    Aida::ImplicitBaseP basep = Jsonipc::Convert<Aida::ImplicitBaseP>::from_json (v);
    if (!basep)
      return;
    // FIXME: remove special casing of base object types once RemoteHandle is gone
    Bse::ObjectIfaceP objectp = std::dynamic_pointer_cast<Bse::ObjectIface> (basep);
    if (objectp)
      any.set (objectp);
    Bse::SignalMonitorIfaceP signalmonitorp = std::dynamic_pointer_cast<Bse::SignalMonitorIface> (basep);
    if (signalmonitorp)
      any.set (signalmonitorp);
  }
  static Jsonipc::JsonValue
  instance_to_json_object (const Aida::Any &any, Jsonipc::JsonAllocator &allocator)
  {
    Aida::ImplicitBaseP basep = any.get<Aida::ImplicitBaseP> ();
    return Jsonipc::Convert<Aida::ImplicitBaseP>::to_json (basep, allocator);
  }
};


namespace Jsonipc {
// Bse::ItemSeq as std::vector
template<>      struct Convert<Bse::ItemSeq>    : ConvertSeq<Bse::ItemSeq, Bse::ItemIfaceP> {};
// Bse::PartSeq as std::vector
template<>      struct Convert<Bse::PartSeq>    : ConvertSeq<Bse::PartSeq, Bse::PartIfaceP> {};
// Bse::SuperSeq as std::vector
template<>      struct Convert<Bse::SuperSeq>   : ConvertSeq<Bse::SuperSeq, Bse::SuperIfaceP> {};
// Bse::WaveOscSeq as std::vector
template<>      struct Convert<Bse::WaveOscSeq> : ConvertSeq<Bse::WaveOscSeq, Bse::WaveOscIfaceP> {};

// Aida::Any
template<>      struct Convert<Aida::Any> : ConvertAny {};

// Bse::PartHandle as Bse::PartIfaceP (in records)
template<>
struct Convert<Aida::RemoteMember<Bse::PartHandle>> {
  static Aida::RemoteMember<Bse::PartHandle>
  from_json (const Jsonipc::JsonValue &jvalue)
  {
    Bse::PartIfaceP ptr = Jsonipc::from_json<Bse::PartIfaceP> (jvalue);
    return ptr->__handle__();
  }
  static Jsonipc::JsonValue
  to_json (const Aida::RemoteMember<Bse::PartHandle> &handle, Jsonipc::JsonAllocator &allocator)
  {
    Bse::PartIfaceP ptr = handle.__iface__()->template as<Bse::PartIfaceP>();
    return Jsonipc::to_json (ptr, allocator);
  }
};

} // Jsonipc


#include "bsebus.hh"
#include "bsecontextmerger.hh"
#include "bsecsynth.hh"
#include "bseeditablesample.hh"
#include "bsemidinotifier.hh"
#include "bsemidisynth.hh"
#include "bsepart.hh"
#include "bsepcmwriter.hh"
#include "bseproject.hh"
#include "bseserver.hh"
#include "bsesnet.hh"
#include "bsesong.hh"
#include "bsesong.hh"
#include "bsesoundfont.hh"
#include "bsesoundfontrepo.hh"
#include "bsetrack.hh"
#include "bsewave.hh"
#include "bsewaveosc.hh"
#include "bsewaverepo.hh"
#include "monitor.hh"
#include "bse/bseapi_jsonipc.cc"    // Bse_jsonipc_stub

#undef B0 // undo pollution from termios.h

static Bse::ServerH bse_server;
static Jsonipc::IpcDispatcher *dispatcher = NULL;
static bool verbose = false;
static GPollFD embedding_pollfd = { -1, 0, 0 };
static std::string authenticated_subprotocol = "";

// Configure websocket server
struct CustomServerConfig : public websocketpp::config::asio {
  static const size_t connection_read_buffer_size = 16384;
};
using ServerEndpoint = websocketpp::server<CustomServerConfig>;
static ServerEndpoint websocket_server;

static std::string
handle_jsonipc (const std::string &message, const websocketpp::connection_hdl &hdl)
{
  ptrdiff_t conid = 0;
  if (verbose)
    {
      conid = ptrdiff_t (websocket_server.get_con_from_hdl (hdl).get());
      Bse::printerr ("%p: REQUEST: %s\n", conid, message);
    }
  const std::string reply = dispatcher->dispatch_message (message);
  if (verbose)
    {
      const bool iserror = bool (Bse::Re::search (R"(^\{("id":[0-9]+,)?"error":)", reply));
      if (iserror)
        {
          using namespace Bse::AnsiColors;
          auto R1 = color (BOLD) + color (FG_RED), R0 = color (FG_DEFAULT) + color (BOLD_OFF);
          Bse::printerr ("%p: %sREPLY:%s   %s\n", conid, R1, R0, reply);
        }
      else
        Bse::printerr ("%p: REPLY:   %s\n", conid, reply);
    }
  return reply;
}

static std::string
user_agent_nick (const std::string &useragent)
{
  using namespace Bse;
  std::string nick;
  if      (Re::search (R"(\bFirefox/)", useragent))
    nick += "Firefox";
  else if (Re::search (R"(\bElectron/)", useragent))
    nick += "Electron";
  else if (Re::search (R"(\bChrome/)", useragent))
    nick += "Chrome";
  else if (Re::search (R"(\bSafari/)", useragent))
    nick += "Safari";
  else
    nick += "Unknown";
  return nick;
}

static bool
ws_validate_connection (websocketpp::connection_hdl hdl)
{
  ServerEndpoint::connection_ptr con = websocket_server.get_con_from_hdl (hdl);
  // using subprotocol as auth string
  const std::vector<std::string> &subprotocols = con->get_requested_subprotocols();
  if (subprotocols.size() == 0 && authenticated_subprotocol.empty())
    return true;
  if (subprotocols.size() == 1)
    {
      if (subprotocols[0] == authenticated_subprotocol)
        {
          con->select_subprotocol (subprotocols[0]);
          return true;
        }
    }
  return false;
}

static void
ws_open_connection (websocketpp::connection_hdl hdl)
{
  ServerEndpoint::connection_ptr con = websocket_server.get_con_from_hdl (hdl);
  // https://github.com/zaphoyd/websocketpp/issues/694#issuecomment-454623641
  const auto &socket = con->get_raw_socket();
  const auto &address = socket.remote_endpoint().address();
  const int rport = socket.remote_endpoint().port();
  const websocketpp::http::parser::request &rq = con->get_request();
  const websocketpp::http::parser::header_list &headermap = rq.get_headers();
  std::string useragent;
  for (auto it : headermap) // request headers
    if (it.first == "User-Agent")
      useragent = it.second;
  std::string nick = user_agent_nick (useragent);
  if (!nick.empty())
    nick = "(" + nick + ")";
  using namespace Bse::AnsiColors;
  auto B1 = color (BOLD);
  auto B0 = color (BOLD_OFF);
  Bse::printout ("%p: %sACCEPT:%s %s:%d/ %s\n", ptrdiff_t (con.get()), B1, B0, address.to_string().c_str(), rport, nick);
  // Bse::printout ("User-Agent: %s\n", useragent);
}

static websocketpp::connection_hdl *bse_current_websocket_hdl = NULL;

static void
ws_message (websocketpp::connection_hdl hdl, server::message_ptr msg)
{
  const std::string &message = msg->get_payload();
  // send message to BSE thread and block until its been handled
  Aida::ScopedSemaphore sem;
  auto handle_wsmsg = [&message, &hdl, &sem] () {
    bse_current_websocket_hdl = &hdl;
    std::string reply = handle_jsonipc (message, hdl);
    bse_current_websocket_hdl = NULL;
    if (!reply.empty())
      websocket_server.send (hdl, reply, websocketpp::frame::opcode::text);
    sem.post();
  };
  bse_server.__iface_ptr__()->__execution_context_mt__().enqueue_mt (handle_wsmsg);
  sem.wait();
}

static std::string
simplify_path (const std::string &path)
{
  using namespace Bse;
  std::vector<std::string> dirs = Bse::string_split (path, "/");
  for (ssize_t i = 0; i < dirs.size(); i++)
    if (dirs[i].empty() || dirs[i] == ".")
      dirs.erase (dirs.begin() + i--);
    else if (dirs[i] == "..")
      {
        dirs.erase (dirs.begin() + i--);
        if (i >= 0)
          dirs.erase (dirs.begin() + i--);
      }
  return "/" + string_join ("/", dirs);
}

static std::string
app_path (const std::string &path)
{
  using namespace Bse;
  static const char *apppath = realpath ((runpath (RPath::INSTALLDIR) + "/app/").c_str(), NULL);
  static size_t apppath_length = strlen (apppath ? apppath : "");
  static const char *docpath = realpath ((runpath (RPath::INSTALLDIR) + "/doc/").c_str(), NULL);
  static size_t docpath_length = strlen (docpath ? docpath : "");
  if (apppath && docpath)
    {
      char *uripath = realpath ((apppath + std::string ("/") + path).c_str(), NULL);
      std::string dest = uripath ? uripath : "";
      free (uripath);
      if ((dest.compare (0, apppath_length, apppath) == 0 && dest[apppath_length] == '/') ||
          (dest.compare (0, docpath_length, docpath) == 0 && dest[docpath_length] == '/'))
        return dest;
    }
  return ""; // 404
}

static void
http_request (websocketpp::connection_hdl hdl)
{
  using namespace Bse;
  ServerEndpoint::connection_ptr con = websocket_server.get_con_from_hdl (hdl);
  const ptrdiff_t conid = ptrdiff_t (con.get());
  const auto &parts = Bse::string_split (con->get_resource(), "?");
  std::string simplepath = simplify_path (parts[0]);
  // root
  if (simplepath == "/")
    {
      con->append_header ("Content-Type", "text/html; charset=utf-8");
      con->set_body ("<!DOCTYPE html>\n"
                     "<html><head><title>Beast-Sound-Engine</title></head><body>\n"
                     "<h1>Beast-Sound-Engine</h1>\n"
                     "<a href='/app.html'>app.html</a><br/>\n"
                     "Resource: " + con->get_resource() + "<br/>\n"
                     "URI: " + con->get_uri()->str() + "<br/>\n"
                     "</body></html>\n");
      con->set_status (websocketpp::http::status_code::ok);
      if (verbose)
        Bse::printerr ("%p: GET:     %s\n", conid, simplepath);
      return;
    }
  // file
  std::string filepath = app_path (simplepath);
  if (!filepath.empty() && Path::check (filepath, "drx"))
    filepath = Path::join (filepath, "index.html");
  if (Path::check (filepath, "fr"))
    {
      if (string_endswith (filepath, ".html") || string_endswith (filepath, ".htm"))
        con->append_header ("Content-Type", "text/html; charset=utf-8");
      else if (string_endswith (filepath, ".js") || string_endswith (filepath, ".mjs"))
        con->append_header ("Content-Type", "application/javascript");
      else if (string_endswith (filepath, ".css"))
        con->append_header ("Content-Type", "text/css");
      else if (string_endswith (filepath, ".ico"))
        con->append_header ("Content-Type", "image/x-icon");
      else if (string_endswith (filepath, ".gif"))
        con->append_header ("Content-Type", "image/gif");
      else if (string_endswith (filepath, ".svg"))
        con->append_header ("Content-Type", "image/svg+xml");
      else if (string_endswith (filepath, ".png"))
        con->append_header ("Content-Type", "image/png");
      else if (string_endswith (filepath, ".jpg"))
        con->append_header ("Content-Type", "image/jpeg");
      else
        con->append_header ("Content-Type", "application/octet-stream");
      Blob blob = Blob::from_file (filepath);
      con->set_body (blob.string());
      con->set_status (websocketpp::http::status_code::ok);
      if (verbose)
        Bse::printerr ("%p: FILE:    %s\n", conid, simplepath);
      return;
    }
  // 404
  con->append_header ("Content-Type", "text/html; charset=utf-8");
  con->set_status (websocketpp::http::status_code::not_found);
  con->set_body ("<!DOCTYPE html>\n"
                 "<html><head><title>404 Not Found</title></head><body>\n"
                 "<h1>Not Found</h1>\n"
                 "<p>The requested URL was not found: <tt>" + con->get_uri()->str() + "</tt></p>\n"
                 "<hr><address>" "beast-sound-engine/" + Bse::version() + "</address>\n"
                 "<hr></body></html>\n");
  if (verbose)
    Bse::printerr ("%p: 404:     %s\n", conid, simplepath);
}

class EventHub {
  static ssize_t new_id () { static ssize_t idgen = -1000000; return --idgen; }
  struct EventHandler {
    websocketpp::connection_hdl weak_hdl;       // connection weak_ptr
    Bse::ObjectIfaceW           weak_obj;       // ObjectIface weak_ptr
    Aida::IfaceEventConnection  event_con;      // internally weak_ptr<commit_msg>BSE: beast-sound-engine.cc: print 404 errors unconditionally<commit_after>// This Source Code Form is licensed MPL-2.0: http://mozilla.org/MPL/2.0

#include <websocketpp/config/asio_no_tls.hpp>
#include <websocketpp/server.hpp>
typedef websocketpp::server<websocketpp::config::asio> server;

#include <jsonipc/jsonipc.hh>

#include <bse/bseenums.hh>      // enums API interfaces, etc
#include <bse/platform.hh>
#include <bse/randomhash.hh>
#include <bse/regex.hh>
#include <bse/bse.hh>   // Bse::init_async
#include <limits.h>
#include <stdlib.h>

// == Aida Workarounds ==
// Manually convert between Aida Handle types (that Jsonipc cannot know about)
// and shared_ptr to Iface types.
// Bse::*Seq as std::vector
template<typename Bse_Seq, typename Bse_IfaceP>
struct ConvertSeq {
  static Bse_Seq
  from_json (const Jsonipc::JsonValue &jarray)
  {
    std::vector<Bse_IfaceP> pointers = Jsonipc::from_json<std::vector<Bse_IfaceP>> (jarray);
    Bse_Seq seq;
    seq.reserve (pointers.size());
    for (auto &ptr : pointers)
      seq.emplace_back (ptr->__handle__());
    return seq;
  }
  static Jsonipc::JsonValue
  to_json (const Bse_Seq &vec, Jsonipc::JsonAllocator &allocator)
  {
    std::vector<Bse_IfaceP> pointers;
    pointers.reserve (vec.size());
    for (auto &itemhandle : vec)
      pointers.emplace_back (itemhandle.__iface__()->template as<Bse_IfaceP>());
    return Jsonipc::to_json (pointers, allocator);
  }
};

/// Convert between Aida::Any and Jsonipc::JsonValue
struct ConvertAny {
  static Aida::Any
  from_json (const Jsonipc::JsonValue &v, const Aida::Any &fallback = Aida::Any())
  {
    Aida::Any any;
    switch (v.GetType())
      {
      case rapidjson::kNullType:
        return fallback;
      case rapidjson::kFalseType:
        any.set<bool> (false);
        break;
      case rapidjson::kTrueType:
        any.set<bool> (true);
        break;
      case rapidjson::kStringType:
        any.set<std::string> (Jsonipc::from_json<std::string> (v));
        break;
      case rapidjson::kNumberType:
        if      (v.IsInt())     any.set<int32_t> (v.GetInt());
        else if (v.IsUint())    any.set<int64_t> (v.GetUint());
        else if (v.IsInt64())   any.set<int64_t> (v.GetInt64());
        else                    any.set<double>  (v.GetDouble());
      case rapidjson::kArrayType:
        sequence_from_json_array (any, v);
        break;
      case rapidjson::kObjectType:
        record_from_json_object (any, v);
        break;
    };
    return any;
  }
  static Jsonipc::JsonValue
  to_json (const Aida::Any &any, Jsonipc::JsonAllocator &allocator)
  {
    using namespace Jsonipc;
    switch (int (any.kind()))
      {
      case Aida::BOOL:          return JsonValue (any.get<bool>());
      case Aida::INT32:         return JsonValue (any.get<int32_t>());
      case Aida::INT64:         return JsonValue (any.get<int64_t>());
      case Aida::FLOAT64:       return JsonValue (any.get<double>());
      case Aida::ENUM:          return Jsonipc::to_json (any.get<std::string>(), allocator);
      case Aida::STRING:        return Jsonipc::to_json (any.get<std::string>(), allocator);
      case Aida::SEQUENCE:      return sequence_to_json_array (any.get<const Aida::AnySeq&>(), allocator);
      case Aida::RECORD:        return record_to_json_object (any.get<const Aida::AnyRec&>(), allocator);
      case Aida::INSTANCE:      return instance_to_json_object (any, allocator);
      }
    return JsonValue(); // null
  }
  static void
  sequence_from_json_array (Aida::Any &any, const Jsonipc::JsonValue &v)
  {
    const size_t l = v.Size();
    Aida::AnySeq s;
    for (size_t i = 0; i < l; ++i)
      s.push_back (ConvertAny::from_json (v[i]));
    any.set (s);
  }
  static Jsonipc::JsonValue
  sequence_to_json_array (const Aida::AnySeq &seq, Jsonipc::JsonAllocator &allocator)
  {
    const size_t l = seq.size();
    Jsonipc::JsonValue jarray (rapidjson::kArrayType);
    jarray.Reserve (l, allocator);
    for (size_t i = 0; i < l; ++i)
      jarray.PushBack (ConvertAny::to_json (seq[i], allocator).Move(), allocator);
    return jarray;
  }
  static void
  record_from_json_object (Aida::Any &any, const Jsonipc::JsonValue &v)
  {
    Aida::AnyRec r;
    for (const auto &field : v.GetObject())
      {
        const std::string key = field.name.GetString();
        if (key == "$class" || key == "$id")    // actually Aida::INSTANCE
          return instance_from_json_object (any, v);
        r[key] = ConvertAny::from_json (field.value);
      }
    any.set (r);
  }
  static Jsonipc::JsonValue
  record_to_json_object (const Aida::AnyRec &r, Jsonipc::JsonAllocator &allocator)
  {
    Jsonipc::JsonValue jobject (rapidjson::kObjectType);
    jobject.MemberReserve (r.size(), allocator);
    for (auto const &field : r)
      jobject.AddMember (Jsonipc::JsonValue (field.name.c_str(), allocator),
                         ConvertAny::to_json (field, allocator).Move(), allocator);
    return jobject;
  }
  static void
  instance_from_json_object (Aida::Any &any, const Jsonipc::JsonValue &v)
  {
    Aida::ImplicitBaseP basep = Jsonipc::Convert<Aida::ImplicitBaseP>::from_json (v);
    if (!basep)
      return;
    // FIXME: remove special casing of base object types once RemoteHandle is gone
    Bse::ObjectIfaceP objectp = std::dynamic_pointer_cast<Bse::ObjectIface> (basep);
    if (objectp)
      any.set (objectp);
    Bse::SignalMonitorIfaceP signalmonitorp = std::dynamic_pointer_cast<Bse::SignalMonitorIface> (basep);
    if (signalmonitorp)
      any.set (signalmonitorp);
  }
  static Jsonipc::JsonValue
  instance_to_json_object (const Aida::Any &any, Jsonipc::JsonAllocator &allocator)
  {
    Aida::ImplicitBaseP basep = any.get<Aida::ImplicitBaseP> ();
    return Jsonipc::Convert<Aida::ImplicitBaseP>::to_json (basep, allocator);
  }
};


namespace Jsonipc {
// Bse::ItemSeq as std::vector
template<>      struct Convert<Bse::ItemSeq>    : ConvertSeq<Bse::ItemSeq, Bse::ItemIfaceP> {};
// Bse::PartSeq as std::vector
template<>      struct Convert<Bse::PartSeq>    : ConvertSeq<Bse::PartSeq, Bse::PartIfaceP> {};
// Bse::SuperSeq as std::vector
template<>      struct Convert<Bse::SuperSeq>   : ConvertSeq<Bse::SuperSeq, Bse::SuperIfaceP> {};
// Bse::WaveOscSeq as std::vector
template<>      struct Convert<Bse::WaveOscSeq> : ConvertSeq<Bse::WaveOscSeq, Bse::WaveOscIfaceP> {};

// Aida::Any
template<>      struct Convert<Aida::Any> : ConvertAny {};

// Bse::PartHandle as Bse::PartIfaceP (in records)
template<>
struct Convert<Aida::RemoteMember<Bse::PartHandle>> {
  static Aida::RemoteMember<Bse::PartHandle>
  from_json (const Jsonipc::JsonValue &jvalue)
  {
    Bse::PartIfaceP ptr = Jsonipc::from_json<Bse::PartIfaceP> (jvalue);
    return ptr->__handle__();
  }
  static Jsonipc::JsonValue
  to_json (const Aida::RemoteMember<Bse::PartHandle> &handle, Jsonipc::JsonAllocator &allocator)
  {
    Bse::PartIfaceP ptr = handle.__iface__()->template as<Bse::PartIfaceP>();
    return Jsonipc::to_json (ptr, allocator);
  }
};

} // Jsonipc


#include "bsebus.hh"
#include "bsecontextmerger.hh"
#include "bsecsynth.hh"
#include "bseeditablesample.hh"
#include "bsemidinotifier.hh"
#include "bsemidisynth.hh"
#include "bsepart.hh"
#include "bsepcmwriter.hh"
#include "bseproject.hh"
#include "bseserver.hh"
#include "bsesnet.hh"
#include "bsesong.hh"
#include "bsesong.hh"
#include "bsesoundfont.hh"
#include "bsesoundfontrepo.hh"
#include "bsetrack.hh"
#include "bsewave.hh"
#include "bsewaveosc.hh"
#include "bsewaverepo.hh"
#include "monitor.hh"
#include "bse/bseapi_jsonipc.cc"    // Bse_jsonipc_stub

#undef B0 // undo pollution from termios.h

static Bse::ServerH bse_server;
static Jsonipc::IpcDispatcher *dispatcher = NULL;
static bool verbose = false;
static GPollFD embedding_pollfd = { -1, 0, 0 };
static std::string authenticated_subprotocol = "";

// Configure websocket server
struct CustomServerConfig : public websocketpp::config::asio {
  static const size_t connection_read_buffer_size = 16384;
};
using ServerEndpoint = websocketpp::server<CustomServerConfig>;
static ServerEndpoint websocket_server;

static std::string
handle_jsonipc (const std::string &message, const websocketpp::connection_hdl &hdl)
{
  ptrdiff_t conid = 0;
  if (verbose)
    {
      conid = ptrdiff_t (websocket_server.get_con_from_hdl (hdl).get());
      Bse::printerr ("%p: REQUEST: %s\n", conid, message);
    }
  const std::string reply = dispatcher->dispatch_message (message);
  if (verbose)
    {
      const bool iserror = bool (Bse::Re::search (R"(^\{("id":[0-9]+,)?"error":)", reply));
      if (iserror)
        {
          using namespace Bse::AnsiColors;
          auto R1 = color (BOLD) + color (FG_RED), R0 = color (FG_DEFAULT) + color (BOLD_OFF);
          Bse::printerr ("%p: %sREPLY:%s   %s\n", conid, R1, R0, reply);
        }
      else
        Bse::printerr ("%p: REPLY:   %s\n", conid, reply);
    }
  return reply;
}

static std::string
user_agent_nick (const std::string &useragent)
{
  using namespace Bse;
  std::string nick;
  if      (Re::search (R"(\bFirefox/)", useragent))
    nick += "Firefox";
  else if (Re::search (R"(\bElectron/)", useragent))
    nick += "Electron";
  else if (Re::search (R"(\bChrome/)", useragent))
    nick += "Chrome";
  else if (Re::search (R"(\bSafari/)", useragent))
    nick += "Safari";
  else
    nick += "Unknown";
  return nick;
}

static bool
ws_validate_connection (websocketpp::connection_hdl hdl)
{
  ServerEndpoint::connection_ptr con = websocket_server.get_con_from_hdl (hdl);
  // using subprotocol as auth string
  const std::vector<std::string> &subprotocols = con->get_requested_subprotocols();
  if (subprotocols.size() == 0 && authenticated_subprotocol.empty())
    return true;
  if (subprotocols.size() == 1)
    {
      if (subprotocols[0] == authenticated_subprotocol)
        {
          con->select_subprotocol (subprotocols[0]);
          return true;
        }
    }
  return false;
}

static void
ws_open_connection (websocketpp::connection_hdl hdl)
{
  ServerEndpoint::connection_ptr con = websocket_server.get_con_from_hdl (hdl);
  // https://github.com/zaphoyd/websocketpp/issues/694#issuecomment-454623641
  const auto &socket = con->get_raw_socket();
  const auto &address = socket.remote_endpoint().address();
  const int rport = socket.remote_endpoint().port();
  const websocketpp::http::parser::request &rq = con->get_request();
  const websocketpp::http::parser::header_list &headermap = rq.get_headers();
  std::string useragent;
  for (auto it : headermap) // request headers
    if (it.first == "User-Agent")
      useragent = it.second;
  std::string nick = user_agent_nick (useragent);
  if (!nick.empty())
    nick = "(" + nick + ")";
  using namespace Bse::AnsiColors;
  auto B1 = color (BOLD);
  auto B0 = color (BOLD_OFF);
  Bse::printout ("%p: %sACCEPT:%s %s:%d/ %s\n", ptrdiff_t (con.get()), B1, B0, address.to_string().c_str(), rport, nick);
  // Bse::printout ("User-Agent: %s\n", useragent);
}

static websocketpp::connection_hdl *bse_current_websocket_hdl = NULL;

static void
ws_message (websocketpp::connection_hdl hdl, server::message_ptr msg)
{
  const std::string &message = msg->get_payload();
  // send message to BSE thread and block until its been handled
  Aida::ScopedSemaphore sem;
  auto handle_wsmsg = [&message, &hdl, &sem] () {
    bse_current_websocket_hdl = &hdl;
    std::string reply = handle_jsonipc (message, hdl);
    bse_current_websocket_hdl = NULL;
    if (!reply.empty())
      websocket_server.send (hdl, reply, websocketpp::frame::opcode::text);
    sem.post();
  };
  bse_server.__iface_ptr__()->__execution_context_mt__().enqueue_mt (handle_wsmsg);
  sem.wait();
}

static std::string
simplify_path (const std::string &path)
{
  using namespace Bse;
  std::vector<std::string> dirs = Bse::string_split (path, "/");
  for (ssize_t i = 0; i < dirs.size(); i++)
    if (dirs[i].empty() || dirs[i] == ".")
      dirs.erase (dirs.begin() + i--);
    else if (dirs[i] == "..")
      {
        dirs.erase (dirs.begin() + i--);
        if (i >= 0)
          dirs.erase (dirs.begin() + i--);
      }
  return "/" + string_join ("/", dirs);
}

static std::string
app_path (const std::string &path)
{
  using namespace Bse;
  static const char *apppath = realpath ((runpath (RPath::INSTALLDIR) + "/app/").c_str(), NULL);
  static size_t apppath_length = strlen (apppath ? apppath : "");
  static const char *docpath = realpath ((runpath (RPath::INSTALLDIR) + "/doc/").c_str(), NULL);
  static size_t docpath_length = strlen (docpath ? docpath : "");
  if (apppath && docpath)
    {
      char *uripath = realpath ((apppath + std::string ("/") + path).c_str(), NULL);
      std::string dest = uripath ? uripath : "";
      free (uripath);
      if ((dest.compare (0, apppath_length, apppath) == 0 && dest[apppath_length] == '/') ||
          (dest.compare (0, docpath_length, docpath) == 0 && dest[docpath_length] == '/'))
        return dest;
    }
  return ""; // 404
}

static void
http_request (websocketpp::connection_hdl hdl)
{
  using namespace Bse;
  ServerEndpoint::connection_ptr con = websocket_server.get_con_from_hdl (hdl);
  const ptrdiff_t conid = ptrdiff_t (con.get());
  const auto &parts = Bse::string_split (con->get_resource(), "?");
  std::string simplepath = simplify_path (parts[0]);
  // root
  if (simplepath == "/")
    {
      con->append_header ("Content-Type", "text/html; charset=utf-8");
      con->set_body ("<!DOCTYPE html>\n"
                     "<html><head><title>Beast-Sound-Engine</title></head><body>\n"
                     "<h1>Beast-Sound-Engine</h1>\n"
                     "<a href='/app.html'>app.html</a><br/>\n"
                     "Resource: " + con->get_resource() + "<br/>\n"
                     "URI: " + con->get_uri()->str() + "<br/>\n"
                     "</body></html>\n");
      con->set_status (websocketpp::http::status_code::ok);
      if (verbose)
        Bse::printerr ("%p: GET:     %s\n", conid, simplepath);
      return;
    }
  // file
  std::string filepath = app_path (simplepath);
  if (!filepath.empty() && Path::check (filepath, "drx"))
    filepath = Path::join (filepath, "index.html");
  if (Path::check (filepath, "fr"))
    {
      if (string_endswith (filepath, ".html") || string_endswith (filepath, ".htm"))
        con->append_header ("Content-Type", "text/html; charset=utf-8");
      else if (string_endswith (filepath, ".js") || string_endswith (filepath, ".mjs"))
        con->append_header ("Content-Type", "application/javascript");
      else if (string_endswith (filepath, ".css"))
        con->append_header ("Content-Type", "text/css");
      else if (string_endswith (filepath, ".ico"))
        con->append_header ("Content-Type", "image/x-icon");
      else if (string_endswith (filepath, ".gif"))
        con->append_header ("Content-Type", "image/gif");
      else if (string_endswith (filepath, ".svg"))
        con->append_header ("Content-Type", "image/svg+xml");
      else if (string_endswith (filepath, ".png"))
        con->append_header ("Content-Type", "image/png");
      else if (string_endswith (filepath, ".jpg"))
        con->append_header ("Content-Type", "image/jpeg");
      else
        con->append_header ("Content-Type", "application/octet-stream");
      Blob blob = Blob::from_file (filepath);
      con->set_body (blob.string());
      con->set_status (websocketpp::http::status_code::ok);
      if (verbose)
        Bse::printerr ("%p: FILE:    %s\n", conid, simplepath);
      return;
    }
  // 404
  con->append_header ("Content-Type", "text/html; charset=utf-8");
  con->set_status (websocketpp::http::status_code::not_found);
  con->set_body ("<!DOCTYPE html>\n"
                 "<html><head><title>404 Not Found</title></head><body>\n"
                 "<h1>Not Found</h1>\n"
                 "<p>The requested URL was not found: <tt>" + con->get_uri()->str() + "</tt></p>\n"
                 "<hr><address>" "beast-sound-engine/" + Bse::version() + "</address>\n"
                 "<hr></body></html>\n");
  Bse::printerr ("%p: 404:     %s\n", conid, simplepath);
}

class EventHub {
  static ssize_t new_id () { static ssize_t idgen = -1000000; return --idgen; }
  struct EventHandler {
    websocketpp::connection_hdl weak_hdl;       // connection weak_ptr
    Bse::ObjectIfaceW           weak_obj;       // ObjectIface weak_ptr
    Aida::IfaceEventConnection  event_con;      // internally weak_ptr
{'fraction_non_alphanumeric': 0.1590687766281193, 'fraction_numerical': 0.005873402312842361, 'mean_word_length': 3.53567977915804, 'pattern_counts': {'":': 4, '<': 203, '<?xml version=': 0, '>': 267, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 2}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3593497', 'n_tokens_mistral': 11656, 'n_tokens_neox': 11412, 'n_words': 2438}