/* Copyright (C) 2024 The PARI group.

This file is part of the PARI-XEUS package.

PARI/GP is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version. It is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY WHATSOEVER.

Check the License for details. You should have received a copy of it, along
with the package; see the file 'COPYING'. If not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */

#include <string>
#include <vector>
#include <iostream>

#include "nlohmann/json.hpp"

#include "xeus/xinput.hpp"
#include "xeus/xinterpreter.hpp"
#include "xeus/xhelper.hpp"

#include "xeus-gp/xinterpreter.hpp"

#include <pari/pari.h>
#include <pari/paripriv.h>

#include <readline/readline.h>
#include <readline/history.h>

#define gp_get_ploth_default_sizes(S)                                  \
{ PARI_plot *_T = (PARI_plot *)S;                                      \
  GEN D = GP_DATA->plothsizes;                                         \
  if (D) switch(lg(D)) {                                               \
    case 5: if (D[4]) _T->vunit  = D[4];     /*FALL THROUGH*/          \
    case 4: if (D[3]) _T->hunit  = D[3];     /*FALL THROUGH*/          \
    case 3: if (D[2]) _T->height = D[2];     /*FALL THROUGH*/          \
    case 2: if (D[1]) _T->width  = D[1];                               \
  }                                                                    \
}

extern "C"
{
    static void *Cthis;
    static jmp_buf pari_xeus_env;
    static int newres;

    void gp_err_recover(long numerr) { longjmp(pari_xeus_env, numerr); }

#if 0
    void
    pari_xeus_help(const char *s)
    {
      const char *url = "https://pari.math.u-bordeaux.fr/dochtml";
    #if ((PARI_VERSION_CODE>>PARI_VERSION_SHIFT)&1)
      pari_err(e_MISC,"Help: %s/help-stable/%s",url,s);
    #else
      pari_err(e_MISC,"Help: %s/help/%s",url,s);
    #endif
    }
#endif

    static void
    pari_xeus_draw(PARI_plot *T, GEN w, GEN x, GEN y)
    {
        pari_sp av = avma;
        nl::json pub_data, pub_metadata, pub_transcient;
        GEN svg  = pari_base64(rect2svg(w,x,y,NULL));
        (void) T;
        pub_data["text/html"] = stack_sprintf("<img src=\"data:image/svg+xml;charset=utf-8;base64,%Ps\">\n", svg);
        pub_metadata["image/text"] =  "PARI PLOT";
        pub_transcient["image/text"] =  "PARI/GP";
        ((xeus::xinterpreter*)Cthis)->display_data(pub_data,pub_metadata,pub_transcient);
        set_avma(av);
    }
    static long plot_width, plot_height;
    static pari_rl_interface pari_rl;

    static void pari_init_rl(void)
    {
      pari_use_readline(pari_rl);
    }

    static void
    pari_xeus_get_plot(PARI_plot *T)
    {
      T->width   = plot_width;
      T->height  = plot_height;
      T->hunit   = 3;
      T->vunit   = 3;
      T->fwidth  = 9;
      T->fheight = 12;
      gp_get_ploth_default_sizes(T);
      T->dwidth  = 0;
      T->dheight = 0;
      T->draw = &pari_xeus_draw;
    }

    void
    pari_xeus_plot_init(long width, long height)
    {
      plot_width  = width;
      plot_height = height;
      pari_set_plot_engine(pari_xeus_get_plot);
    }
    static void xeus_puts(const char*s)
    {
      if (newres)
      {
        nl::json pub_data;
        pub_data["text/plain"] = "";
        ((xeus::xinterpreter*)Cthis)->publish_execution_result(newres, std::move(pub_data), nl::json::object());
        newres = 0;
      }
      ((xeus::xinterpreter*)Cthis)->publish_stream("stdout", s);
    }
    static void xeus_out_putch(char c)
    {
      const char s[2] = {c, 0};
      xeus_puts(s);
    }
    static void xeus_out_puts(const char*s)
    {
      xeus_puts(s);
    }
    static void xeus_out_flush(void) {}
    static void xeus_err_putch(char c)
    {
      const char s[2] = {c, 0};
      xeus_puts(s);
    }
    static void xeus_err_puts(const char*s)
    {
      xeus_puts(s);
    }
    static void xeus_err_flush(void) {}
}

namespace nl = nlohmann;

namespace xeus_gp
{
    interpreter::interpreter()
    {
        xeus::register_interpreter(this);
    }

    void interpreter::execute_request_impl(send_reply_callback cb,
                                           int n,
                                           const std::string& code,
                                           xeus::execute_request_config /*config*/,
                                           nl::json /*user_expressions*/)
    {
      const char * str = code.c_str();
      struct gp_context rec;
      long er, res;
      gp_context_save(&rec);
      newres = n;
      if ((er = setjmp(pari_xeus_env)))
      { /* recover: jump from error [ > 0 ] or allocatemem [ -1 ] */
        if (er > 0)
          gp_context_restore(&rec);
        else
          gp_context_save(&rec);
        set_avma(pari_mainstack->top);
        parivstack_reset();
        res = 1;
      } else
      {
        os_signal(SIGINT, pari_sighandler);
        res = gp_embedded(str);
        os_signal(SIGINT, SIG_IGN);
      }
      if (!res)
        cb(xeus::create_successful_reply());
      else
        cb(xeus::create_error_reply());
    }

    void interpreter::configure_impl()
    {
      gp_embedded_init(8000000,1000000000);
      os_signal(SIGINT, SIG_IGN);
      cb_pari_err_recover = gp_err_recover;
      GP_DATA->fmt->prettyp = 2;
      GP_DATA->fmt->sp = 1;
      Cthis = (void*)this;
      pariOut->putch = xeus_out_putch;
      pariOut->puts  = xeus_out_puts;
      pariOut->flush = xeus_out_flush;
      pariErr->putch = xeus_err_putch;
      pariErr->puts  = xeus_err_puts;
      pariErr->flush = xeus_err_flush;
      pari_xeus_plot_init(640,400);
      sd_help("gphelp -detex -utf8 -ch 4 -cb 0 -cu 2",0);
      sd_colors("brightfg",0);
      pari_init_rl();
    }

    nl::json interpreter::is_complete_request_impl(const std::string& code)
    {
        // Insert code here to validate the ``code``
        // and use `create_is_complete_reply` with the corresponding status
        // "unknown", "incomplete", "invalid", "complete"
        (void) code;
        return xeus::create_is_complete_reply("unknown"/*status*/, "   "/*indent*/);
    }

    nl::json interpreter::complete_request_impl(const std::string& code,
                                                     int cursor_pos)
    {
        nl::json nj;
        long pos;
        char ** c = pari_completion_matches(&pari_rl, code.c_str(), cursor_pos,&pos);
        if (c) while (*c) nj.push_back(*c++);
        return xeus::create_complete_reply(nj, pos, cursor_pos);
    }

    nl::json interpreter::inspect_request_impl(const std::string& /*code*/,
                                                      int /*cursor_pos*/,
                                                      int /*detail_level*/)
    {
        return xeus::create_inspect_reply(false/*found*/);
    }

    void interpreter::shutdown_request_impl() {
        pari_close();
    }

    nl::json interpreter::kernel_info_request_impl()
    {

        const std::string  protocol_version = "5.3";
        const std::string  implementation = "xeus-gp";
        const std::string  implementation_version = XEUS_GP_VERSION;
        const std::string  language_name = "gp";
        const std::string  language_version = PARIVERSION;
        const std::string  language_mimetype = "text/x-gp";
        const std::string  language_file_extension = ".gp";
        const std::string  language_pygments_lexer = "gp";
        const std::string  language_codemirror_mode = "";
        const std::string  language_nbconvert_exporter = "";
        const std::string  banner = "PARI/GP";
        const bool         debugger = false;
        const nl::json     help_links = nl::json::array();

        return xeus::create_info_reply(
            protocol_version,
            implementation,
            implementation_version,
            language_name,
            language_version,
            language_mimetype,
            language_file_extension,
            language_pygments_lexer,
            language_codemirror_mode,
            language_nbconvert_exporter,
            banner,
            debugger,
            help_links
        );
    }

}
