// toolbox.C : interface toolbox for grafix and numerical recipes
// (c) wolf 11/96

#include "lattice.h"
#include "toolbox.h"

struct play_par { // attach a scrollbar to a parameter
  double *pptr;
  double min,max;
  char *descr;
  scrollbar *scr;
};

// class tool_lat : a lattice manager for display tool sequences of arrays
// with selector menus, clock and spawning feature
// the "action" method must be defined in derived classes (see replay_lat) !
class tool_lat : public lattice_manager {
  info_window *headline;
  disp_array *dsel; // actually selected display descriptor 
  char *zmode;
public:
  char *header; // header of headline
  tool_lat *spawned_from;
  struct splist { tool_lat *spawned; splist *next;} *spl; 

  tool_lat(window &parent, int w, int h, int x, int y, int nx, int ny, 
	   disp_array *dstart);
  ~tool_lat();
  void install_spawned(tool_lat *gsp); // hook a new spawned gl into chain
  virtual void draw_interior(); // recompute array do draw from member "array"
  void redraw_spawned(); // redraws all spawned windows; parsing the spl list
  void setsel(disp_array *ddp); // set parameters of selected disp_array
};

//************************************************************************//
// class tool_lat : a lattice manager for display time sequences of arrays
// with selector menues, clock and spawning feature

tool_lat::tool_lat(window &parent, int w, int h, int x, int y, 
		   int nx, int ny, disp_array *dstart) : 
  lattice_manager(parent, w, h-20,x, y+20, nx, ny, NULL) {
    headline = new info_window(parent,w,20,x,y);
    header = "";
    qptr = new float[nx*ny]; 
    setsel(dstart);
    body = False; b_light = 0; // horizontal light source
    a_light = 140*M_PI/180;
    beta = 70*M_PI/180; // 70 grd tilt
    spl = NULL; spawned_from = NULL;
  }

tool_lat::~tool_lat() { 
  delete qptr; 
  if (spawned_from == NULL) return;
  // inhibit redrawing initiated from the spawner of myself 
  splist *spx = spawned_from->spl; 
  // printf("delete %x\n",this);
  // instead of complicated re-arranging the list, simply set spawned = 0
  while (spx) { 
    if (spx->spawned == this) { spx->spawned = NULL; }
    spx = spx->next;
  }
}

// hook a new spawned gl into chain
void tool_lat::install_spawned(tool_lat *gsp) { 
  splist *sn = new splist; 
  sn->next = spl; sn->spawned = gsp;
  spl = sn; 
  gsp->spawned_from = this;  // remember my spawner for destructor !
  // printf("installing %x from %x\n",gsp,this);
  gsp->dsel = dsel; // use selected display also
  gsp->gamma = gamma; gsp->z0 = z0; // but the actual values here

}
  
void tool_lat::setsel(disp_array *ddp) {  // set selected disp_array
  dsel = ddp; 
  gamma = dsel->gamma; 
  z0 = dsel->z0; zmode = dsel->zmode;  
}

char *zauto = "auto", *zinit = "init", *zfixed = "fixed"; 

void tool_lat::draw_interior() { 
  // make a copy from member "array" to array "qptr"
  float (*lptr)[ny] = (float (*) [ny]) qptr; // ptr to the array to draw "qptr"

  float zmin = dsel->array[1][1], zmax = zmin;
  for (int x=0; x<nx; x++) for (int y=0; y < ny; y++) {
    float z = lptr[x][y] = dsel->array[x+1][y+1];
    zmin = zmin <? z; zmax = zmax >? z;
  }
  float zf = (zmin < 0 && zmin < -zmax) ? zmin : zmax;

  // limit z-factor to avoid too long computation time  
  if (fabs(gamma*zf) > 3.0) gamma = 3.0/zf;
  if (dsel->zmode == zinit || dsel->zmode == zauto) gamma = 0.8/zf; 
  if (dsel->zmode == zinit) { 
    dsel->zmode = zfixed; // now set constant
  } 
  dsel->gamma = gamma; // store value for further calls
  sprintf(headline->info,"%s %s",header,dsel->descr);
  headline->redraw();
  lattice_manager::draw_interior();
}

// redraws all spawned windows; parsing the spl list
void tool_lat::redraw_spawned() {
  redraw(); // first redraw myself
  splist *spx = spl;
  while (spx) { 
    tool_lat *gsp = spx->spawned; if (gsp) gsp->redraw(); 
    spx = spx->next; 
  }
}

// **********************************************************

poll_main::poll_main(char *name, int ww, int wh) : main_window(name,ww,wh) { 
  top = (top_main == 0); if (top) top_main = this;
  mb = new menu_bar(*this,ww,20,0,0,70,100,0);
  if (top) new quit_button(*mb); else new delete_button(*mb,this);
  bh = 20;
  scr = NULL; // *active* scrolbar

}
void poll_main::polling_handler() { // virtual function of main_window
  if (scr) {
    if (scr->value >= scr->max) polling_mode = False; // turn off
    else scr->step_fwd(); 
  }
}  

// install act_scr as active and toggle polling_mode
void poll_main::switch_mode(nr_scrollbar *act_scr) { 
  scr = (polling_mode) ? 0 : act_scr; // if pm was True : switch of
  polling_mode = ! polling_mode;
  act_scr->play_mode = polling_mode;
  // printf("switch %p %d %d\n",scr,polling_mode,scr->value); 
}


// find is usable for all derived classes of string
template <class T> T* find(T **str, char *value) {
  for (int i = 0; str[i]; i++)
    if (str[i]->descr == value) return str[i];
  return 0;
}

// make a selector list from list of strings -> for pulldowns
template <class T>
int make_list(T **str, menu_bar *mb, char *menu, window *wptr) {
  int nitems = 0;
  while (str[nitems]) nitems++;
  if (nitems > 30) error("missing '0' at the end of disp_array");
  if (nitems > 1) { // else useless
    // char **sel_list = new char* [nitems+1]; 
    char *sel_list[nitems+1];
    for (int i=0; i<nitems; i++) {
      sel_list[i] = str[i]->descr;
      if (strlen(sel_list[i]) > 80) 
	error("missing '0' at the end of disp_array");
    }
    sel_list[nitems] = 0;
    make_radio_menu(*mb, menu, sel_list, wptr); 
  }
  return nitems;
}
 
static char *select = "select"; // menubar selector strings
static char *solve = "solver";
 
one_main::one_main(char *name, int nx, disp_vector **dv, one_solver **sv, 
		   int ww, int wh, int hc) 
: poll_main(name,ww,wh), nx(nx), dv(dv), sv(sv) {
  cw = new nr_coord(*this,nx,dv,ww,wh-hc-bh,0,hc); // leave room at top & bot
  cw->define_coord(0,-1.0,nx,1.0); // default system
  infw = new info_window(*this,ww,bh,0,wh-bh);    
  sprintf(info_text,"info_text"); // start text  
  // pulldown_window for selecting two_solvers
  if (sv == 0 || sv[0] == 0) error("one_main : no solver defined!");
  make_list(sv, mb, solve, this);
  svact = sv[0]; 
  solve_it();
}
  
void one_main::solve_it() {   
  if (svact) {     
    svact->func(nx); sprintf(infw->info,"%s",info_text); 
    redraw(); 
  }
}
void one_main::action(char* menu, char* value) {
  // printf("%s\n",value);  
  if (menu == solve) { 
    svact = find(sv, value);      
    solve_it(); // call selected solver 
  }
}

char *help_two[] = {
  "'rotate' lets the image rotate horizontally",
  "         press again to stop it",
  "'select' (pulldown-menu) switches the displayed arrays",
  "'solver' selects the solver function from the list of two_solvers",
  "'spawn' creates a new window with same appearance","",
  "* Please note the 'help'-button in the bottom menu line",
  "  It explaines the use of display buttonsand options", 0 };
    
class two_main : public poll_main {
public:
  tool_lat *lm;
  int nx,ny;
  disp_array **da;
  two_solver **sv;
  two_solver *svact; // actual used solver

  two_main(char *name, int nx, int ny, disp_array **da, two_solver **sv,
	    Bool top = True) : 
  poll_main(name,450,500), nx(nx), ny(ny), da(da), sv(sv) {
    int w = width, h = height;

    if (top) // for other windows rotating not yet installed
      new toggle_button(*mb,"rotate",&polling_mode); 
 
    // pulldown_window for selecting arrays
    make_list(da, mb, select, this);
    
    // pulldown_window for selecting two_solvers
    make_list(sv, mb, solve, this);

    new instance_button <two_main> (*mb,"spawn",this->spawn,this);
    lm = new tool_lat(*this,w,h-bh,0,bh,nx,ny,da[0]);
    svact = sv[0];

    new help_button(*mb,"help",help_two);

    if (top) solve_it(); // for the first time use 1. solver
  }

  virtual void solve_it() { 
    if (svact) { 
      svact->func(nx,ny); lm->header = svact->descr; 
      lm->redraw_spawned(); 

    }
  }
  // handle callbacks from pulldowns
  virtual void action(char* menu, char* value) {
    // printf("%s\n",value);  
    if (menu == select) {
      disp_array *ddp = find(da, value); 
      // find_sel(value);
      if (ddp) { lm->setsel(ddp); lm->redraw(); }
    } else if (menu == solve) { 
      svact = find(sv, value);      
      solve_it(); // call selected solver 
    }
  }

  virtual void polling_handler() {
    if (scr) poll_main::polling_handler(); // use scrollbar handler
    else lm->rotate_alpha(0.05); // rotate the lattice for some 3 grad
    // lm->a_light += 0.1; lm->redraw(); // rotate light source
  }
 
  void spawn() {
    two_main *rsm = new two_main("spawned",nx,ny,da,sv,false);
    lm->install_spawned(rsm->lm); // hook into redraw chain
    rsm->RealizeChildren();   
  }

};

poll_main* poll_main::top_main = 0; // top main window : handles polling loop

static void switch_mode(nr_scrollbar *act_scr) {
  poll_main::top_main->switch_mode(act_scr);
}

nr_scrollbar::nr_scrollbar(window &parent, int w, int x, int y, int n_ticks, 
			   float *fptr, poll_main *pm,
			   float minp, float maxp, float fdel) 
: fptr(fptr), fdel(fdel), pm(pm),
  tick_scrollbar(parent, (VVP) &updated, this, w-15-15-20, 
		 x+50, y, n_ticks, minp, maxp, *fptr)  
{ // printf("%f %f %f\n",minp,maxp,*fptr);
  new instance_button <nr_scrollbar> // left
    (parent,"<", &step_bck, this, 15,20, x, y); 
    
  new instance_button <nr_scrollbar> // left +15
    (parent,">", &step_fwd, this, 15, 20, x+15, y); 
    
  new switch_button(parent,">>","||",&play_mode, // left + 30
		    (VVP) &switch_mode, this, 20, 20, x+30,y);
  // new instance_button <nr_scrollbar>  // right - 20
  //  (parent,"<<", &reset, this, 20, 20, x+w-20, y);
  // *fptr = initial value
}

char info_text[400]; // to pass a string into info_window 

static char *help_scrollbar[] = {
  "This window enables you to play with some parameters via scrollbars.","",
  "By pressing the left mouse button at the desired value, the scrollbar",
  "will move to it.","",
  "You may press the buttons '>' and '<' for a stepwise changing,",
  "Or you may press '>>' to start an animated motion.",
  "(It will stop when pressed again)",
  0 };

void make_ctrl_window(poll_main *pm, parameter **scr) {
  int nsc = 0; if (scr) while (scr[nsc]) nsc++;
  if (nsc > 0) { // create control window
    main_window *ctrl = new main_window("control window",500,20+60*nsc);
    new help_button(*ctrl,0,0,help_scrollbar);
    for (int i = 0, y = 20; i < nsc; i++, y += 60) 
      scr[i]->make_scrollbar(ctrl,pm,y); 
    pm->RealizeChildren(); // only for nicer stacking
    ctrl->RealizeChildren(); 
  }
}

// only as security fallback, if *fptr is not initialized
void check_parameter(parameter **scr) {
  int nsc = 0; 
  if (scr) while (scr[nsc]) scr[nsc++]->check();
}   

// interface function for simple creating a onedim interactive visualization
void one_show(char *name, int n, disp_vector **dv, one_solver **sv, 
	      parameter **scr) {  
  check_parameter(scr); // to call before creating om !!
  one_main *om = new one_main(name,n,dv,sv,600,300);
  make_ctrl_window(om,scr);
  om->main_loop();
}

// aux interface function 
void two_show(char *name, int nx, int ny, disp_array **da, two_solver **sv,
	      parameter **scr) {
  check_parameter(scr);
  two_main *tm = new two_main(name,nx,ny,da,sv);
  make_ctrl_window(tm,scr);
  tm->main_loop();
}

/* testing 
class Dmatrix {
  double **ptr;
  int nr,nc;
public:
  Dmatrix(int nr, int nc) : nr(nr), nc(nc) { ptr = dmatrix(1, nr, 1, nc); }
  ~Dmatrix() { free_dmatrix(ptr,1,nr,1,nc); }
};
*/
