/*
 *  R : A Computer Language for Statistical Data Analysis
 *  file pager.c
 *  Copyright (C) 1998--2002  Guido Masarotto and Brian Ripley
 *  Copyright (C) 2004--8     The R Foundation
 *  Copyright (C) 2004--2025  The R Core Team
 *
 *  This program 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.
 *
 *  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 for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, a copy is available at
 *  https://www.R-project.org/Licenses/
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include "win-nls.h"

#ifdef Win32
#define USE_MDI 1
#endif

#define WIN32_LEAN_AND_MEAN 1
#include <windows.h>
#include "graphapp/ga.h"
#ifdef USE_MDI
#include "graphapp/stdimg.h"
#endif
#include "console.h"
#include "consolestructs.h"
#include "rui.h"
#include <Startup.h> /* for CharacterMode */

#define CE_UTF8 1
extern size_t Rf_utf8towcs(wchar_t *wc, const char *s, size_t n);

#define PAGERMAXKEPT 12
#define PAGERMAXTITLE 128

static int pagerActualKept = 0, pagerActualShown;
static pager pagerInstance = NULL;
static menubar pagerBar = NULL;
static xbuf pagerXbuf[PAGERMAXKEPT];
static char pagerTitles[PAGERMAXKEPT][PAGERMAXTITLE+8];
static menuitem pagerMenus[PAGERMAXKEPT];
static int pagerRow[PAGERMAXKEPT];
static void pagerupdateview(void);

void menueditoropen(control m);
void menueditornew(control m);

/* from console.c */
extern int pagerMultiple, haveusedapager;


/*
   To be fixed: during creation, memory is allocated two times
   (faster for small files but a big waste otherwise)
*/
static xbuf file2xbuf(const char *name, int enc, int del)
{
    HANDLE f;
    DWORD rr, vv;
    char *p;
    xlong dim, cnt;
    xint  ms;
    xbuf  xb;
    wchar_t *wp, *q;

    if (enc == CE_UTF8) {
	size_t len = Rf_utf8towcs(NULL, name, 0);
	if (len == (size_t)-1) {
	    R_ShowMessage(G_("Error opening file"));
	    return NULL;
	}
	wchar_t *wfn = (wchar_t *)malloc((len + 1)*sizeof(wchar_t));
	if (!wfn) {
	    R_ShowMessage(G_("Insufficient memory to display file in internal pager"));
	    return NULL;
	}
	Rf_utf8towcs(wfn, name, len + 1);
	f = CreateFileW(wfn, GENERIC_READ, FILE_SHARE_READ,
			NULL, OPEN_EXISTING, 0, NULL);
	free(wfn);
    } else
	f = CreateFile(name, GENERIC_READ, FILE_SHARE_READ,
		       NULL, OPEN_EXISTING, 0, NULL);
    if (f == INVALID_HANDLE_VALUE) {
	R_ShowMessage(G_("Error opening file"));
	return NULL;
    }
    vv = GetFileSize(f, NULL);
    p = (char *) malloc((size_t) vv + 1);
    if (!p) {
	CloseHandle(f);
	R_ShowMessage(G_("Insufficient memory to display file in internal pager"));
	return NULL;
    }
    ReadFile(f, p, vv, &rr, NULL);
    CloseHandle(f);
    if (del) DeleteFile(name);
    p[rr] = '\0';
    cnt = mbstowcs(NULL, p, 0);
    wp = (wchar_t *) malloc((cnt+1) * sizeof(wchar_t));
    if (!wp) {
	R_ShowMessage(G_("Insufficient memory to display file in internal pager"));
	return NULL;
    }
    mbstowcs(wp, p, cnt+1);
    for (q = wp, ms = 1, dim = cnt; *q; q++) {
	if (*q == '\t')
	    dim += TABSIZE;
	else if (*q == '\n') {
	    dim++;
	    ms++;
	}
    }
    free(p);
    if ((xb = newxbuf(dim + 1, ms + 1, 1)))
	for (q = wp, ms = 0; *q; q++) {
	    if (*q == L'\r') continue;
	    if (*q == L'\n') {
		ms++;
		xbufaddxc(xb, *q);
		/* next line interprets underlining in help files */
		if (q[1] ==  L'_' && q[2] == L'\b') xb->user[ms] = -2;
	    } else xbufaddxc(xb, *q);
	}
    free(wp);
    return xb;
}

static void delpager(control m)
{
    int i;

    ConsoleData p = getdata(m);
    if (!pagerMultiple) {
	for (i = 0; i < pagerActualKept; i++) xbufdel(pagerXbuf[i]);
	pagerActualKept = 0;
    }
    else
	xbufdel(p->lbuf);
    freeConsoleData(getdata(m));
}

void pagerbclose(control m)
{
    show(RConsole);
    if (!pagerMultiple) {
	hide(pagerInstance);
	del(pagerInstance);
	pagerInstance = pagerBar = NULL;
    }
    else {
	hide(m);
	del(m);
    }
}

static void pagerclose(control m)
{
    pagerbclose(getdata(m));
}

static void pagerprint(control m)
{
    consoleprint(getdata(m));
}

static void pagersavefile(control m)
{
    consolesavefile(getdata(m), 1);
}

static void pagercopy(control m)
{
    control c = getdata(m);

    if (consolecancopy(c)) consolecopy(c);
    else R_ShowMessage(G_("No selection"));
}

static void pagerpaste(control m)
{
    control c = getdata(m);

    if (CharacterMode != RGui) {
	R_ShowMessage(G_("No RGui console to paste to"));
	return;
    }
    if (!consolecancopy(c)) {
	R_ShowMessage(G_("No selection"));
	return;
    } else {
	consolecopy(c);
    }
    if (consolecanpaste(RConsole)) {
	consolepaste(RConsole);
	show(RConsole);
    }
}

static void pagerpastecmds(control m)
{
    control c = getdata(m);

    if (CharacterMode != RGui) {
	R_ShowMessage(G_("No RGui console to paste to"));
	return;
    }
    if (!consolecancopy(c)) {
	R_ShowMessage(G_("No selection"));
	return;
    } else {
	consolecopy(c);
    }
    if (consolecanpaste(RConsole)) {
	consolepastecmds(RConsole);
	show(RConsole);
    }
}

static void pagerselectall(control m)
{
    control c = getdata(m);

    consoleselectall(c);
}

static void pagerstayontop(control m)
{
    control c = getdata(m);

    BringToTop(c, 2);
}

static void pagerconsole(control m)
{
    show(RConsole);
}

static void pagerchangeview(control m)
{
    ConsoleData p = getdata(pagerInstance);
    int i = getvalue(m);

    if (i >= pagerActualKept) return;
    uncheck(pagerMenus[pagerActualShown]);
    /* save position of middle line of pager display */
    pagerRow[pagerActualShown] = FV + ROWS/2;
    pagerActualShown = i;
    check(pagerMenus[i]);
    pagerupdateview();
}

static void pagerupdateview(void)
{
    control c = pagerInstance;
    ConsoleData p = getdata(c);

    settext(pagerInstance, &pagerTitles[pagerActualShown][4]);
    p->lbuf = pagerXbuf[pagerActualShown];
    setfirstvisible(c, pagerRow[pagerActualShown] - ROWS/2);
    setfirstcol(c, 0);
    show(c);
}

static int pageraddfile(const char *wtitle,
			const char *filename, int enc,
			int deleteonexit)
{
    ConsoleData p = getdata(pagerInstance);
    int i;
    xbuf nxbuf = file2xbuf(filename, enc, deleteonexit);

    if (!nxbuf) {
	/* R_ShowMessage("File not found or memory insufficient"); */
	return 0;
    }
    if (pagerActualKept == PAGERMAXKEPT) {
	pagerActualKept -= 1;
	xbufdel(pagerXbuf[pagerActualKept]);
    }
    if(pagerActualKept > 0)
	pagerRow[0] = FV;
    for (i = pagerActualKept; i > 0; i--) {
	pagerXbuf[i] = pagerXbuf[i - 1];
	pagerRow[i] = pagerRow[i - 1];
	strcpy(&pagerTitles[i][4], &pagerTitles[i - 1][4]);
    }
    pagerXbuf[0] = nxbuf;
    pagerRow[0] = 0;
    strcpy(&pagerTitles[0][4], wtitle);
    pagerActualKept += 1;
    for (i = 0; i < pagerActualKept; i++) {
	enable(pagerMenus[i]);
	settext(pagerMenus[i], pagerTitles[i]);
    }
    for (i = pagerActualKept; i < PAGERMAXKEPT; i++)
	disable(pagerMenus[i]);
    uncheck(pagerMenus[pagerActualShown]);
    pagerActualShown = 0;
    check(pagerMenus[pagerActualShown]);
    return 1;
}

static MenuItem PagerPopup[] = {		   /* Numbers used below */
    {GN_("Copy"), pagercopy, 'C', 0},			   /* 0 */
    {GN_("Paste to console"), pagerpaste, 'V', 0},	   /* 1 */
    {GN_("Paste commands to console"), pagerpastecmds, 0, 0},   /* 2 */
    {GN_("Select all"), pagerselectall, 'A', 0},		   /* 3 */
    {"-", 0, 0, 0},
    {GN_("Stay on top"), pagerstayontop, 0, 0},		   /* 5 */
    {"-", 0, 0, 0},
    {GN_("Close"), pagerclose, 0, 0},			   /* 7 */
    LASTMENUITEM
};

static void pagermenuact(control m)
{
    control c = getdata(m);
    ConsoleData p = getdata(c);
    if (consolecancopy(c)) {
	enable(p->mcopy);
	enable(p->mpopcopy);
	if (CharacterMode == RGui) {
	    enable(p->mpaste);
	    enable(p->mpastecmds);
	    enable(p->mpoppaste);
	    enable(p->mpoppastecmds);
	}
    } else {
	disable(p->mcopy);
	disable(p->mpopcopy);
	disable(p->mpaste);
	disable(p->mpastecmds);
	disable(p->mpoppaste);
	disable(p->mpoppastecmds);
    }
    if (ismdi())
	disable(PagerPopup[5].m);
    else {
	enable(PagerPopup[5].m);
	if (isTopmost(c))
	    check(PagerPopup[5].m);
	else
	    uncheck(PagerPopup[5].m);
    }
}


#define MCHECK(a) if (!(a)) {freeConsoleData(p);del(c);return NULL;}
RECT *RgetMDIsize(void); /* in rui.c */

static pager pagercreate(void)
{
    ConsoleData p;
    int w, h, i, x, y, w0, h0;
    pager c;
    menuitem m;

    p = newconsoledata((consolefn) ? consolefn : FixedFont,
		       pagerrow, pagercol, 0, 0,
		       guiColors,
		       PAGER, 0, 0);
    if (!p) return NULL;

/*    if (ismdi()) {
      x = y = w = h = 0;
      }
      else {
      w = WIDTH ;
      h = HEIGHT;
      x = (devicewidth(NULL) - w) / 2;
      y = (deviceheight(NULL) - h) / 2 ;
      } */
    w = WIDTH ;
    h = HEIGHT;
    /* centre a single pager, randomly place each of multiple pagers */
#ifdef USE_MDI
    if(ismdi()) {
	RECT *pR = RgetMDIsize();
	w0 = pR->right;
	h0 = pR->bottom;
    } else {
#endif
	w0 = devicewidth(NULL);
	h0 = deviceheight(NULL);
#ifdef USE_MDI
    }
#endif
    x = (w0 - w) / 2; x = x > 20 ? x:20;
    y = (h0 - h) / 2; y = y > 20 ? y:20;
    if(pagerMultiple) {
#ifdef Win32
	DWORD rand = GetTickCount();
#else
	int rand = 0;
#endif
	int w0 = 0.4*x, h0 = 0.4*y;
	w0 = w0 > 20 ? w0 : 20;
	h0 = h0 > 20 ? h0 : 20;
	x += (rand % w0) - w0/2;
	y += ((rand/w0) % h0) - h0/2;
    }
    c = (pager) newwindow("PAGER", rect(x, y, w, h),
			  Document | StandardWindow | Menubar |
			  VScrollbar | HScrollbar | TrackMouse);
    if (!c) {
	freeConsoleData(p);
	return NULL;
    }
    setdata(c, p);
    if(h == 0) HEIGHT = getheight(c);
    if(w == 0) WIDTH  = getwidth(c);
    COLS = WIDTH / FW - 1;
    ROWS = HEIGHT / FH - 1;
    BORDERX = (WIDTH - COLS*FW) / 2;
    BORDERY = (HEIGHT - ROWS*FH) / 2;
    gsetcursor(c, ArrowCursor);
    gchangescrollbar(c, VWINSB, 0, 0, ROWS, 0);
    gchangescrollbar(c, HWINSB, 0, COLS-1, COLS, 1);
    setbackground(c, guiColors[pagerbg]);
#ifdef USE_MDI
    if (ismdi()) {
	int btsize = 24;
	rect r = rect(2, 2, btsize, btsize);
	control tb, bt;
	addto(c);
	MCHECK(tb = newtoolbar(btsize + 4));
	gsetcursor(tb, ArrowCursor);
	addto(tb);
	MCHECK(bt = newtoolbutton(open_image, r, menueditoropen));
	MCHECK(addtooltip(bt, G_("Open script")));
	gsetcursor(bt, ArrowCursor);
	/* wants NULL as data, not the pager */
	r.x += (btsize + 6) ;
	MCHECK(bt = newtoolbutton(copy1_image, r, pagerpaste));
	MCHECK(addtooltip(bt, G_("Paste to console")));
	gsetcursor(bt, ArrowCursor);
	setdata(bt, (void *) c);
	r.x += (btsize + 6) ;
	MCHECK(bt = newtoolbutton(copy1_image, r, pagerpastecmds));
	MCHECK(addtooltip(bt, G_("Paste commands to console")));
	gsetcursor(bt, ArrowCursor);
	setdata(bt, (void *) c);
	r.x += (btsize + 6) ;
	MCHECK(bt = newtoolbutton(print_image, r, pagerprint));
	MCHECK(addtooltip(bt, G_("Print")));
	gsetcursor(bt, ArrowCursor);
	setdata(bt, (void *) c);
	r.x += (btsize + 6) ;
	MCHECK(bt = newtoolbutton(console_image, r, pagerconsole));
	MCHECK(addtooltip(bt, G_("Return focus to Console")));
	gsetcursor(bt, ArrowCursor);
	setdata(bt, (void *) c);
    }
#endif
    addto(c);
    MCHECK(m = gpopup(pagermenuact, PagerPopup));
    setdata(m, c);
    setdata(p->mpopcopy = PagerPopup[0].m, c);
    setdata(p->mpoppaste = PagerPopup[1].m, c);
    setdata(p->mpoppastecmds = PagerPopup[2].m, c);
    setdata(PagerPopup[3].m, c);
    setdata(PagerPopup[5].m, c);
    setdata(PagerPopup[7].m, c);
    MCHECK(m = newmenubar(pagermenuact));
    setdata(m, c);
    MCHECK(newmenu(G_("File")));
    MCHECK(m = newmenuitem(G_("New script"), 'N', menueditornew));
    MCHECK(m = newmenuitem(G_("Open script..."), 'O', menueditoropen));
    MCHECK(m = newmenuitem(G_("Print..."), 'P', pagerprint));
    setdata(m, c);
    MCHECK(m = newmenuitem(G_("Save to File..."), 'S', pagersavefile));
    setdata(m, c);
    MCHECK(m = newmenuitem("-", 0, NULL));
    MCHECK(m = newmenuitem(G_("Close"), 0, pagerclose));
    setdata(m, c);
    MCHECK(newmenu(G_("Edit")));
    MCHECK(p->mcopy = newmenuitem(G_("Copy"), 'C', pagercopy));
    setdata(p->mcopy, c);
    MCHECK(p->mpaste = newmenuitem(G_("Paste to console"), 'V', pagerpaste));
    setdata(p->mpaste, c);
    MCHECK(p->mpastecmds = newmenuitem(G_("Paste commands to console"), 0, pagerpastecmds));
    setdata(p->mpastecmds, c);
    MCHECK(m = newmenuitem(G_("Select all"), 'A', pagerselectall));
    setdata(m, c);
    if (!pagerMultiple) {
	MCHECK(newmenu(G_("View")));
	for (i = 0; i < PAGERMAXKEPT; i++) {
	    snprintf(pagerTitles[i], PAGERMAXTITLE+8, "&%c.  ", 'A' + i);
	    MCHECK(pagerMenus[i] = newmenuitem(&pagerTitles[i][1], 0,
					       pagerchangeview));
	    setvalue(pagerMenus[i], i);
	}
    }
#ifdef USE_MDI
    if (ismdi()) newmdimenu();
    if (ismdi() && !(RguiMDI & RW_TOOLBAR)) toolbar_hide();
#endif
    MCHECK(BM = newbitmap(WIDTH, HEIGHT, 2));
    setdata(c, p);
    sethit(c, console_sbf);
    setresize(c, consoleresize);
    setredraw(c, drawconsole);
    setdel(c, delpager);
    setclose(c, pagerbclose);
    setkeyaction(c, console_ctrlkeyin);
    setkeydown(c, console_normalkeyin);
    setmousedrag(c, console_mousedrag);
    setmouserepeat(c, console_mouserep);
    setmousedown(c, console_mousedown);
    return(c);
}

static pager newpager1win(const char *wtitle,
			  const char *filename, int enc,
			  int deleteonexit)
{
    if (!pagerInstance && !(pagerInstance = pagercreate())) {
	R_ShowMessage(G_("Unable to create pager window"));
	return NULL;
    }
    if (!pageraddfile(wtitle, filename, enc, deleteonexit)) return NULL;
    pagerupdateview();
    return pagerInstance;
}

static pager newpagerNwin(const char *wtitle,
			  const char *filename, int enc,
			  int deleteonexit)
{
    pager c = pagercreate();
    ConsoleData p;

    if (!c) return NULL;
    settext(c, wtitle);
    p = getdata(c);
    if (!(p->lbuf = file2xbuf(filename, enc, deleteonexit))) {
	del(c);
	return NULL;
    }
    if (c) {
	gchangescrollbar(c, VWINSB, 0, NUMLINES - 1 , ROWS, 0);
	show(c);
    }
    return c;
}

pager newpager(const char *title,
	       const char *filename, int enc,
	       const char *header, int deleteonexit)
{
    char wtitle[PAGERMAXTITLE+1];
    pager c;

    /*    if (ismdi()) pagerMultiple = 1;*/
    strncpy(wtitle, title, PAGERMAXTITLE);
    wtitle[PAGERMAXTITLE] = '\0';
    if(strlen(header) &&
       ((strlen(header) + strlen(wtitle) + 4) < PAGERMAXTITLE)) {
	if(strlen(wtitle)) strcat(wtitle, " - ");
	strcat(wtitle, header);
    }
    if (!pagerMultiple)
	c = newpager1win(wtitle, filename, enc, deleteonexit);
    else
	c = newpagerNwin(wtitle, filename, enc, deleteonexit);
    if (c) {
	haveusedapager++;
	BringToTop(c, 0);
    }
    return c;
}
