/*
 * REmbeddedPosix.cpp
 *
 * Copyright (C) 2022 by Posit Software, PBC
 *
 * Unless you have received this program directly from Posit Software pursuant
 * to the terms of a commercial license agreement with Posit Software, then
 * this program is licensed to you under the terms of version 3 of the
 * GNU Affero General Public License. This program is distributed WITHOUT
 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
 *
 */

#include <cstring>

#include <core/Log.hpp>

#include <r/RRuntime.hpp>
#include <r/session/REventLoop.hpp>

#include <Rembedded.h>
#include <Rversion.h>
#include <R_ext/eventloop.h>

#include <boost/date_time/posix_time/posix_time_duration.hpp>

#include <shared_core/FilePath.hpp>

#include <r/RExec.hpp>
#include <r/RInterface.hpp>
#include <r/RErrorCategory.hpp>
#include <r/RUtil.hpp>

#include "REmbedded.hpp"

#ifdef __APPLE__
#include <dlfcn.h>
extern "C" void R_ProcessEvents(void);
extern "C" void (*ptr_R_ProcessEvents)(void);
#define QCF_SET_PEPTR  1  /* set ProcessEvents function pointer */
#define QCF_SET_FRONT  2  /* set application mode to front */
extern "C"  typedef void (*ptr_QuartzCocoa_SetupEventLoop)(int, unsigned long);
#endif

extern int R_running_as_main_program;  // from unix/system.c

extern "C" {
void run_Rmainloop(void);
} // extern "C"

using namespace rstudio::core;

namespace rstudio {
namespace r {
namespace session {

// Local copy of R's structRstart, always including the 'nconnections'
// field added in R 4.4.0. This allows runtime detection of nconnections
// support regardless of which R headers were used at compile time.
// The struct is zero-initialized before R_DefParams; R >= 4.4.0
// populates nconnections = 128, older R leaves it as 0 (from memset).
typedef struct
{
   int R_Quiet;
   int R_NoEcho;
   int R_Interactive;
   int R_Verbose;
   int LoadSiteFile;
   int LoadInitFile;
   int DebugInitFile;
   SA_TYPE RestoreAction;
   SA_TYPE SaveAction;
   R_SIZE_T vsize;
   R_SIZE_T nsize;
   R_SIZE_T max_vsize;
   R_SIZE_T max_nsize;
   R_SIZE_T ppsize;
   int NoRenviron : 16;
   int RstartVersion : 16;
   int nconnections;

   // Padding, to allow for extensions in newer versions of R,
   // in case newer versions of R expect a struct with extra
   // memory available.
   char padding[128];
} RStartup;

void runEmbeddedR(const core::FilePath& /*rHome*/,    // ignored on posix
                  const core::FilePath& /*userHome*/, // ignored on posix
                  bool quiet,
                  bool loadInitFile,
                  SA_TYPE defaultSaveAction,
                  int nconnections,
                  const Callbacks& callbacks,
                  InternalCallbacks* pInternal)
{
   // disable R signal handlers. see src/main/main.c for the default
   // implementations. in our case ignore them for the following reasons:
   //
   // INT - no concept of Ctrl-C based interruption (use flag directly)
   //
   // SEGV, ILL, & BUS: unsupported due to prompt invoking networking
   // code (unsupported from within a signal handler)
   //
   // USR1 & USR2: same as above SEGV, etc. + we use them for other purposes
   //
   // PIPE: we ignore this globally in SessionMain. before doing this we
   // confirmed that asio wasn't in some way manipulating it -- on linux
   // boost passes MSG_NOSIGNAL to sendmsg and on OSX sets the SO_NOSIGPIPE
   // option on all sockets created. note that on other platforms including
   // solaris, hpux, etc. boost uses detail/signal_init to ignore SIGPIPE
   // globally (this is done in io_context.hpp).
   R_SignalHandlers = 0;

   // set message callback early so we can see initialization error messages
   ptr_R_ShowMessage = callbacks.showMessage;

   // running as main program (affects location of R_CStackStart on platforms
   // without HAVE_LIBC_STACK_END or HAVE_KERN_USRSTACK). see also discussion
   // on R_CStackStart in 8.1.5 Threading issues
   R_running_as_main_program = 1;

   // initialize R
   const char *args[]= {"RStudio", "--interactive"};
   Rf_initialize_R(sizeof(args)/sizeof(args[0]), (char**)args);

   // For newSession = false we need to do a few things:
   //
   //   1) set R_Quiet so we startup without a banner
   //
   //   2) set LoadInitFile to suppress execution of .Rprofile
   //
   //   3) we also need to make sure that .First is not executed. this is
   //      taken care of via the fact that we set RestoreAction to SA_NORESTORE
   //      which means that when setup_Rmainloop there is no .First function
   //      available to it because we haven't restored the environment yet.
   //      Note that .First is executed in the case of new sessions because
   //      it is read from .Rprofile as part of setup_Rmainloop. This implies
   //      that in our version of R the .First function must be defined in
   //      .Rprofile rather than simply saved into the global environment
   //      of the default workspace
   //
   RStartup rp;
   memset(&rp, 0, sizeof(rp));
   RStartup* Rp = &rp;
   R_DefParams((Rstart) Rp);
   Rp->R_NoEcho = FALSE;
   Rp->R_Quiet = quiet ? TRUE : FALSE;
   Rp->R_Interactive = TRUE;
   Rp->SaveAction = defaultSaveAction;
   Rp->RestoreAction = SA_NORESTORE; // handled within initialize()
   Rp->LoadInitFile = loadInitFile ? TRUE : FALSE;

   // set max connections if requested (requires R >= 4.4.0)
   // R >= 4.4.0 sets Rp->nconnections = 128 in R_DefParams;
   // the field remains 0 (from memset) on older R versions.
   nconnections = validateMaxConnections(nconnections);
   if (nconnections > 0)
   {
      if (Rp->nconnections > 0)
      {
         Rp->nconnections = nconnections;
      }
      else
      {
         LOG_WARNING_MESSAGE("r-max-connections requires R >= 4.4.0");
      }
   }

   R_SetParams((Rstart) Rp);

   // redirect console
   R_Interactive = TRUE; // should have also been set by call to Rf_initialize_R
   R_Consolefile = nullptr;
   R_Outputfile = nullptr;
   ptr_R_ReadConsole = callbacks.readConsole;
   ptr_R_WriteConsole = nullptr; // must set this to NULL for Ex to be called
   ptr_R_WriteConsoleEx = callbacks.writeConsoleEx;
   ptr_R_ResetConsole = callbacks.resetConsole;
   ptr_R_EditFile = callbacks.editFile;
   ptr_R_Busy = callbacks.busy;

   // hook messages (in case Rf_initialize_R overwrites previously set hook)
   ptr_R_ShowMessage = callbacks.showMessage;

   // hook file handling
   ptr_R_ChooseFile = callbacks.chooseFile;
   ptr_R_ShowFiles = callbacks.showFiles;

   // hook history
   ptr_R_loadhistory = callbacks.loadhistory;
   ptr_R_savehistory = callbacks.savehistory;
   ptr_R_addhistory = callbacks.addhistory;

   // hook suicide, but save reference to internal suicide so we can forward
   pInternal->suicide = ptr_R_Suicide;
   ptr_R_Suicide = callbacks.suicide;

   // hook clean up, but save reference to internal clean up so can forward
   pInternal->cleanUp = ptr_R_CleanUp;
   ptr_R_CleanUp = callbacks.cleanUp;

   // NOTE: we do not hook the following callbacks because they are targeted
   // at clients that have a stdio-based console
   //    ptr_R_FlushConsole
   //    ptr_R_ClearerrConsole

   // Initialize runtime dispatch before setup_Rmainloop so any code reached
   // from .Rprofile / Rprofile.site can safely call through r::runtime /
   // r::sexp. libR is already loaded into the process; we only resolve
   // symbol addresses here -- value-dependent reads use lazy pointer
   // dereferencing (see s_pUnboundValue / s_pSaveAction in RRuntime.cpp).
   //
   // Use R_Suicide rather than Rf_error on the failure branch: Rf_error
   // longjmps to R_ToplevelContext, which is not set up until
   // setup_Rmainloop runs.
   Error error = r::runtime::initialize();
   if (error)
   {
      LOG_ERROR(error);
      R_Suicide("RStudio failed to initialize R runtime dispatch");
   }

   // initialize the main loop
   setup_Rmainloop();

   // run the main loop
   run_Rmainloop();
}

Error completeEmbeddedRInitialization()
{
   return Success();
}

namespace event_loop {

namespace {

// is the polled event handler currently disabled?
int s_disablePolledEventHandler = 0;

// currently installed polled event handler
void (*s_polledEventHandler)(void) = nullptr;

// previously existing polled event handler
void (*s_oldPolledEventHandler)(void) = nullptr;

// function we register with R to implement polled event handler
void polledEventHandler()
{
   if (s_disablePolledEventHandler != 0)
      return;

   if (s_polledEventHandler != nullptr)
      s_polledEventHandler();

   if (s_oldPolledEventHandler != nullptr)
      s_oldPolledEventHandler();
}

} // anonymous namespace


void initializePolledEventHandler(void (*newPolledEventHandler)(void))
{
   // can only call this once
   BOOST_ASSERT(!s_polledEventHandler);

   // implementation based on addTcl() in tcltk_unix.c

   // copy handler function
   s_polledEventHandler = newPolledEventHandler;

   // preserve old handler and set new one
   s_oldPolledEventHandler = R_PolledEvents;
   R_PolledEvents = polledEventHandler;
   
   // set R_wait_usec
   if (R_wait_usec > 10000 || R_wait_usec == 0)
      R_wait_usec = 10000;
}

// NOTE: this call is used in child process after multicore forks
// to make sure all subsequent R code is executed without any
// event handlers (appropriate since the forked child is headless).
// the prefix "permanently" is used because we explicitly don't
// handle the ability to restore event handling by calling
// initializePolledEventHandler -- this is because we overwrite
// s_oldPolledEventHandler with NULL, thus losing any reference
// we have to a R_PolledEvents value that existed before our
// initialization (it would be possible to implement a temporary
// disable with a bit more complex control flow)
void permanentlyDisablePolledEventHandler()
{
   s_polledEventHandler = nullptr;
   s_oldPolledEventHandler = nullptr;
}

bool polledEventHandlerInitialized()
{
   return s_polledEventHandler != nullptr;
}

void processEvents()
{
#ifdef __APPLE__
   R_ProcessEvents();

   // pickup X11 graphics device events (if any) via X11 input handler
   fd_set* what = R_checkActivity(0,1);
   if (what != nullptr)
      R_runHandlers(R_InputHandlers, what);
#else
   // check for activity on standard input handlers (but ignore stdin).
   // return immediately if there is no input currently available
   fd_set* what = R_checkActivity(0,1);

   // run handlers on the input (or run the polled event handler if there
   // is no input currently available)
   R_runHandlers(R_InputHandlers, what);
#endif
}

DisablePolledEventHandlerScope::DisablePolledEventHandlerScope()
{
   s_disablePolledEventHandler += 1;
}

DisablePolledEventHandlerScope::~DisablePolledEventHandlerScope()
{
   s_disablePolledEventHandler -= 1;
}

} // namespace event_loop
} // namespace session
} // namespace r
} // namespace rstudio



