#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>

#ifdef bool
#undef bool
#endif

#ifdef CURSES
#define CURSES_INC CURSES
#else
#define CURSES_INC "ncurses/curses.h"
#endif

#include CURSES_INC

#include <string.h>

#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

#include "pdl.h"

#define CHBUF  256
#ifndef MIN
#define MIN(a,b) ((a)<(b)?(a):(b))
#endif

#define HLAB   4

static int colwid, dcols, drows;

/* enum pdl_datatypes { PDL_B, PDL_S, PDL_US,   PDL_L,   PDL_LL,    PDL_F,   PDL_D  }; */
char *format[] =      { "%3d", "%6d", "%6hd", "%11ld", "%11lld", "%10.4g", "%11.4g" };
int    width[] =      {     4,     7,      7,      12,       12,       11,      12  };

char *str_value(int x, int y,
		int type, int nx, void *data, char *str)
{
  // int offsets into our array, not the PDL enum
  switch (type) {
  case 0: // PDL_B
    sprintf(str,format[type],*(((char *)data)+y*nx+x));
    break;
  case 1: // PDL_S
    sprintf(str,format[type],*(((short *)data)+y*nx+x));
    break;
  case 2: // PDL_US
    sprintf(str,format[type],*(((unsigned short *)data)+y*nx+x));
    break;
  case 3: // PDL_L
    sprintf(str,format[type],*(((int *)data)+y*nx+x));
    break;
  case 4: // PDL_LL
    sprintf(str,format[type],*(((long long *)data)+y*nx+x));
    break;
  case 5: // PDL_F
    sprintf(str,format[type],*(((float *)data)+y*nx+x));
    break;
  case 6: // PDL_D
    sprintf(str,format[type],*(((double *)data)+y*nx+x));
    break;
  default:
    Perl_croak(aTHX_ "type (val=%d) not implemented",type);
    break;
  }
  return str;
}

void set_value(int x, int y,
		int type, int nx, void *data, char *str)
{
  switch (type) {
  case PDL_B:
    *(((PDL_Byte *)data)+y*nx+x) = atol(str);
    break;
  case PDL_S:
    *(((PDL_Short *)data)+y*nx+x) = atol(str);
    break;
  case PDL_US:
    *(((PDL_Ushort *)data)+y*nx+x) = atol(str);
    break;
  case PDL_L:
    *(((PDL_Long *)data)+y*nx+x) = atol(str);
    break;
  case PDL_LL:
    *(((PDL_LongLong *)data)+y*nx+x) = atol(str);
    break;
  case PDL_F:
    *(((PDL_Float *)data)+y*nx+x) = atof(str);
    break;
  case PDL_D:
    *(((PDL_Double *)data)+y*nx+x) = atof(str);
    break;
  default:
    Perl_croak(aTHX_ "type (val=%d) not implemented",type);
    break;
  }
  return;
}

void update_vlab(WINDOW *win, int x, int ioff)
{
  char line[BUFSIZ];
  int len, k, d;
  chtype chline[BUFSIZ];
  extern int colwid;

  for (k=0;k<colwid;k++)
    chline[k] = ' ';

  sprintf(line,"%d",ioff+x);
  len = strlen(line);
  d = (colwid-len)/2;
  for (k=0;k<len;k++)
    chline[k+d] = line[k] | A_BOLD;
  chline[colwid-1] = '|' | A_BOLD;
  chline[colwid] = 0;

  mvwaddchnstr(win,0,x*colwid,chline,colwid);
}
void update_hlab(WINDOW *win, int y, int joff)
{
  char line[BUFSIZ];
  int len, k, d;
  chtype chline[BUFSIZ];

  for (k=0;k<HLAB;k++)
    chline[k] = ' ';

  sprintf(line,"%-4d",joff+y);
  len = strlen(line);
  d = (HLAB-len)/2;

  for (k=0;k<len;k++)
    chline[k+d] = line[k] | A_BOLD;
  chline[HLAB] = 0;
  mvwaddchnstr(win,y,0,chline,HLAB);
}
void clear_cell(WINDOW *win, int i, int j)
{
  char line[BUFSIZ];
  int len, k, d;
  chtype chline[BUFSIZ];
  extern int colwid;

  for (k=0;k<colwid-1;k++)
    chline[k] = ' ';
  chline[colwid-1] = '|' | A_BOLD;
  chline[colwid] = '\0';
  mvwaddchnstr(win,j,i*colwid,chline,colwid);
}
void set_cell(WINDOW *win, int i, int j, int ioff, int joff,
	      int type, int nx, void *data)
{
  char line[BUFSIZ];
  int len, k, d;
  chtype chline[BUFSIZ];
  extern int colwid;

  for (k=0;k<colwid-1;k++)
    chline[k] = ' ';
  str_value(i,j,type,nx,data,line);
  len = strlen(line);
  for (k=0;k<len;k++)
    chline[k] = line[k];
  chline[len] = ' ';
  chline[colwid-1] = '|' | A_BOLD;
  chline[colwid] = '\0';
  mvwaddchnstr(win,j-joff,(i-ioff)*colwid,chline,colwid);
}

void update_row(WINDOW *win, int y, int ioff, int joff,
		int type, int nx, void *data)
{
  char line[BUFSIZ];
  int len, k, d, i;
  chtype chline[BUFSIZ];
  extern int colwid, dcols;

  for (i=0;i<dcols;i++) {
    for (k=0;k<colwid-1;k++)
      chline[k] = ' ';
    str_value(i+ioff,y+joff,type,nx,data,line);
    len = strlen(line);
    for (k=0;k<len;k++)
      chline[k] = line[k];
    chline[len] = ' ';
    chline[colwid-1] = '|' | A_BOLD;
    chline[colwid] = '\0';
    mvwaddchnstr(win,y,i*colwid,chline,colwid);
  }
}
void update_col(WINDOW *win, int x, int ioff, int joff,
		int type, int nx, void *data)
{
  char line[BUFSIZ];
  int len, k, d, j;
  chtype chline[BUFSIZ];
  extern int colwid, drows;

  for (j=0;j<drows;j++) {
    for (k=0;k<colwid-1;k++)
      chline[k] = ' ';
    str_value(x+ioff,j+joff,type,nx,data,line);
    len = strlen(line);
    for (k=0;k<len;k++)
      chline[k] = line[k];
    chline[len] = ' ';
    chline[colwid-1] = '|' | A_BOLD;
    chline[colwid] = '\0';
    mvwaddchnstr(win,j,x*colwid,chline,colwid);
  }
}



void browse(int type, int nc, int nr, void *in)
{
  WINDOW *pdlscr, *wmenu, *wscroll, *warray, *whlab, *wvlab, *wtmp;
  char s[CHBUF],echobuf[CHBUF],line[CHBUF];
  chtype ch;
  int i,j,eps,ioff,joff,iecho;
  int ncols, nrows, mycols;
  extern int colwid, dcols, drows, width[];

  pdlscr = initscr();  /* sets LINES, COLS (which aren't macro constants...) */
  clear();  /* Clear the screen before we start drawing */

  colwid = width[type];
  ncols = (COLS-HLAB)/colwid;
  dcols = MIN(nc,ncols);
  mycols = dcols*colwid;

  nrows = LINES-3;
  drows = MIN(nr,nrows);

  cbreak();
  noecho();
  nonl();
  intrflush(pdlscr,FALSE);
  keypad(pdlscr,TRUE);
  /* Menu bar */
  wmenu  = subwin(pdlscr,1,COLS,0,0);
  wvlab  = subwin(pdlscr,1,mycols,1,HLAB);
  wscroll= subwin(pdlscr,drows,mycols+HLAB,2,0);
  warray = subwin(wscroll,drows,mycols,2,HLAB);
  whlab  = subwin(wscroll,drows,HLAB,2,0);

  keypad(warray,TRUE);
  scrollok(pdlscr,TRUE);
  scrollok(wscroll,TRUE);

  wmenu  = subwin(pdlscr,1,COLS,0,0);

  sprintf(s,"Perldl data browser: type %d, (%d,%d), type q to quit\n",
	  type,nc,nr);
  mvwaddstr(wmenu,0,10,s);
  wrefresh(wmenu);

  for (i=0;i<dcols;i++) {
    update_vlab(wvlab,i,0);
  }
  wrefresh(wvlab);

  for (j=0;j<drows;j++) {
    update_hlab(whlab,j,0);
  }
  wrefresh(whlab);

  for (j=0;j<drows;j++) {
    update_row(warray,j,0,0,type,nc,in);
  }

  i = j = eps = 0;
  ioff = joff = 0;
  while (tolower(ch=mvwgetch(warray,j-joff,(i-ioff)*colwid+
		      MIN(eps,colwid-2))) != 'q') {
    /* #define ECHOCH */
#ifdef ECHOCH
    sprintf(echobuf,"%8o",ch);
    mvwaddstr(wmenu,0,iecho,echobuf);
    iecho = (iecho < 72) ? iecho+8 :0;
    wrefresh(wmenu);
#endif
    switch (ch) {
    case KEY_LEFT:
    case KEY_RIGHT:
    case KEY_UP:
    case KEY_DOWN:
    case '\t':
    case '\015':
      if (eps) {
	line[eps] = '\0';
	set_value(i,j,type,nc,in,line);
      }
      set_cell(warray,i,j,ioff,joff,type,nc,in);
      eps = 0;
      wrefresh(warray);
      break;
    case '\b':
    case KEY_DL:
    case 0177:
      if (eps) {
	eps--;
	mvwaddch(warray,j-joff,(i-ioff)*colwid+MIN(eps,colwid-2),' ');
	wrefresh(warray);
      }
      continue;
    default:
      if (!eps && ch >= 32 && ch <= 127) {
	clear_cell(warray,i-ioff,j-joff);
	wrefresh(warray);
      }
      mvwaddch(warray,j-joff,(i-ioff)*colwid+MIN(eps,colwid-2),ch|A_UNDERLINE);
      line[eps++]=ch;
      continue;
    }

    switch (ch) {
    case KEY_LEFT:
      i = (i<2)?0:i-1;
      if (i-ioff == -1) {
	ioff--;
	wtmp = newwin(1,mycols-colwid,1,HLAB);
	overwrite(wvlab,wtmp);
	mvwin(wtmp,1,HLAB+colwid);
	overwrite(wtmp,wvlab);
	delwin(wtmp);
	update_vlab(wvlab,0,ioff);
	wtmp = newwin(drows,mycols-colwid,2,HLAB);
	overwrite(warray,wtmp);
	mvwin(wtmp,2,HLAB+colwid);
	overwrite(wtmp,warray);
	delwin(wtmp);
	update_col(warray,0,ioff,joff,type,nc,in);
	wrefresh(warray);
	wrefresh(wvlab);
      }
      break;
    case KEY_RIGHT:
    case '\t':
      i = (i>nc-2)?nc-1:i+1;
      if (i-ioff == dcols) {
	ioff++;
	wtmp = newwin(1,mycols-colwid,1,HLAB+colwid);
	overwrite(wvlab,wtmp);
	mvwin(wtmp,1,HLAB);
	overwrite(wtmp,wvlab);
	delwin(wtmp);
	update_vlab(wvlab,dcols-1,ioff);
	wtmp = newwin(drows,mycols-colwid,2,HLAB+colwid);
	overwrite(warray,wtmp);
	mvwin(wtmp,2,HLAB);
	overwrite(wtmp,warray);
	delwin(wtmp);
	update_col(warray,dcols-1,ioff,joff,type,nc,in);
	wrefresh(warray);
	wrefresh(wvlab);
      }
      break;
    case KEY_UP:
      j = (j<2)?0:j-1;
      if (j-joff == -1) {
	joff--;
	wscrl(wscroll,-1);
	wrefresh(wscroll);
	update_hlab(whlab,0,joff);
	wrefresh(whlab);
	update_row(warray,0,ioff,joff,type,nc,in);
	wrefresh(warray);
      }
      break;
    case KEY_DOWN:
    case '\015':
      j = (j>nr-2)?nr-1:j+1;
      if (j-joff == drows) {
	joff++;
	wscrl(wscroll,1);
	wrefresh(wscroll);
	update_hlab(whlab,drows-1,joff);
	wrefresh(whlab);
	update_row(warray,drows-1,ioff,joff,type,nc,in);
	wrefresh(warray);
      }
      break;
    }
  }
  nl();
  echo();
  nocbreak();
  endwin();
}

#ifdef WITH_IO_BROWSER_MAIN
main ()
{
    double b[27*15];

    int i, j;

    j = 0;
    for (i=0; i<27*15; i++) {
        b[i] = j++;
    }

    browse(PDL_D, 27, 15, &b);
}
#endif
