/*******************************************************************************
+
+  LEDA 3.5
+
+  _winmgr.c
+
+  This file is part of the LEDA research version (LEDA-R) that can be 
+  used free of charge in academic research and teaching. Any commercial
+  use of this software requires a license which is distributed by the
+  LEDA Software GmbH, Postfach 151101, 66041 Saarbruecken, FRG
+  (fax +49 681 31104).
+
+  Copyright (c) 1991-1997  by  Max-Planck-Institut fuer Informatik
+  Im Stadtwald, 66123 Saarbruecken, Germany     
+  All rights reserved.
+ 
*******************************************************************************/
#include "windef.h"
#include "vga.h"
#include <LEDA/bitmaps/leda_icon.xbm>
#include <time.h>

#if !defined(CLOCKS_PER_SEC)
#define CLOCKS_PER_SEC 1000000
#endif

//------------------------------------------------------------------------------
// window manager, etc ...                                    
//------------------------------------------------------------------------------

const int HEADER_W = 20;
const int BORDER_W =  5;
const int RESIZE_W =  6;

static int root_color = blue2;   
static int label_col0 = grey2;

const int  win_max = 16;

VgaWindow  win_list[win_max];
int        win_count = 0;

VgaWindow  win_stack[win_max];
int        win_top = 0;


static VgaWindow root_win = 0;

static int mouse_x = 0;
static int mouse_y = 0;


VgaWindow set_draw_window(Window w) 
{ VgaWindow win = win_list[w];

  // set color and drawing mode

  vga_set_color(win->COLOR);

  switch (win->MODE) {
  case src_mode: vga_set_mode(0);
                 break;
  case and_mode: vga_set_mode(1);
                 break;
  case or_mode:  vga_set_mode(2);
                 break;
  case xor_mode: vga_set_mode(3);
                 break;
  }

  return win; 
}


static int handle_next_event(int* win, int* val, int* x, int* y, int block)
{ 
  int new_mouse_x;
  int new_mouse_y;

  int ev = vga_next_event(val,&new_mouse_x,&new_mouse_y,block);

  if (mouse_x != new_mouse_x || mouse_y != new_mouse_y)
  { vga_draw_pointer(new_mouse_x,new_mouse_y,mouse_x,mouse_y);
    mouse_x = new_mouse_x;
    mouse_y = new_mouse_y;
   }

  VgaWindow w;
  for(int i = win_top; i >= 0; i--)
  { w = win_stack[i];
    if (mouse_x >= w->x0  && mouse_x <= w->x1 &&
        mouse_y >= w->y0  && mouse_y <= w->y1 )  break;
   }

  *win = w->id;
  *x = mouse_x - w->xpos;
  *y = mouse_y - w->ypos;
  return ev;
}

static unsigned char rev_byte(unsigned char c)
{  unsigned char c1 = 0x00;
   for(int i=0; i<8; i++)
   { c1 <<= 1;
     if (c&1) c1 |= 1;
     c >>= 1;
    }
  return c1;
 }
 


static void draw_window(VgaWindow win, int clear_win = 1)
{
  int bw  = win->xpos - win->x0 - 2;
  int lw  = win->ypos - win->y0 - 2;
  int bw1 = 2*(bw+1);

  int x0 = win->x0;
  int y0 = win->y0;

  int x1 = win->x1;
  int y1 = win->y1;

  drawing_mode save_mode = x_set_mode(0,src_mode);
  int save_line_width = x_set_line_width(0,1);

  if (clear_win)
  { x_set_color(0,win->bg_col);
    x_box(0,x0,y0,x1,y1);
   }

  if (bw > 1)
  { x_set_color(0,win->label_col);
    x_box(root_win->id,x0+bw,y0+bw,x1-bw,y0+lw);
    x_box(root_win->id,x0,y0+bw1,x0+bw,y1-bw1);
    x_box(root_win->id,x1-bw,y0+bw1,x1,y1-bw1);
    x_box(root_win->id,x0+bw1,y1-bw,x1-bw1,y1);
    x_box(root_win->id,x0+bw1,y0,x1-bw1,y0+bw);

    x_set_color(0,black);
    x_rect(root_win->id,x0,y0,x1,y1);

    x_line(root_win->id,x0+bw,y0+bw,x0+bw1,y0+bw);
    x_line(root_win->id,x0+bw1,y0+bw,x0+bw1,y0+1);
    x_line(root_win->id,x0+bw1,y0+1,x1-bw1,y0+1);
    x_line(root_win->id,x1-bw1,y0+1,x1-bw1,y0+bw);
    x_line(root_win->id,x1-bw1,y0+bw,x1-bw,y0+bw);
    x_line(root_win->id,x1-bw,y0+bw,x1-bw,y0+bw1);
    x_line(root_win->id,x1-bw,y0+bw1,x1-1,y0+bw1);
    x_line(root_win->id,x1-1,y0+bw1,x1-1,y1-bw1);
    x_line(root_win->id,x1-1,y1-bw1,x1-bw,y1-bw1);
    x_line(root_win->id,x1-bw,y1-bw1,x1-bw,y1-bw);
    x_line(root_win->id,x1-bw,y1-bw,x1-bw1,y1-bw);
    x_line(root_win->id,x1-bw1,y1-bw,x1-bw1,y1-1);
    x_line(root_win->id,x1-bw1,y1-1,x0+bw1,y1-1);
    x_line(root_win->id,x0+bw1,y1-1,x0+bw1,y1-bw);
    x_line(root_win->id,x0+bw1,y1-bw,x0+bw,y1-bw);
    x_line(root_win->id,x0+bw,y1-bw,x0+bw,y1-bw1);
    x_line(root_win->id,x0+bw,y1-bw1,x0+1,y1-bw1);
    x_line(root_win->id,x0+1,y1-bw1,x0+1,y0+bw1);
    x_line(root_win->id,x0+1,y0+bw1,x0+bw,y0+bw1);
    x_line(root_win->id,x0+bw,y0+bw1,x0+bw,y0+bw);
  }

  x_set_color(0,black);
  x_rect(root_win->id, win->xpos-1, win->ypos-1,
                      win->xpos+win->width, win->ypos+win->height);

  if (lw > 15)
  { // iconize-button
    x_set_color(0,white);
    x_box(0,x0+13,y0+3,x0+27,y0+15);
    x_set_color(0,black);
    x_rect(root_win->id,x0+13,y0+3,x0+27,y0+15);
    x_line(root_win->id,x0+14,y0+16,x0+28,y0+16);
    x_line(root_win->id,x0+28,y0+4,x0+28,y0+15);
    x_line(root_win->id,x0+17,y0+6,x0+23,y0+6);
    x_line(root_win->id,x0+16,y0+6,x0+20,y0+13);
    x_line(root_win->id,x0+17,y0+6,x0+20,y0+13);
    x_line(root_win->id,x0+24,y0+6,x0+20,y0+13);
    x_line(root_win->id,x0+23,y0+6,x0+20,y0+13);
  
    x_set_header(win->id,win->header);
   }
  
  x_set_mode(0,save_mode);
  x_set_line_width(0,save_line_width);

  if (win->iconized)
  { int sz = leda_icon_width * leda_icon_width/8;
    unsigned char* bm = new unsigned char[sz];
    for (int i=0; i<sz; i++) bm[i] = rev_byte(leda_icon_bits[i]);
    vga_bitmap(win->xpos, win->ypos, bm,leda_icon_width/8, leda_icon_height);
    vga_flush(win->xpos,win->ypos,win->xpos+leda_icon_width,
                                  win->ypos+leda_icon_height);
    delete[] bm;
   }
}



static void change_geometry(VgaWindow win, int x0, int y0, int x1, int y1)
{
  //if (x0==win->x0 && y0==win->y0 && x1==win->x1 && y1==win->y1) return;

  int w = x1-x0+1;
  int h = y1-y0+1;

  int bw = win->xpos - win->x0;
  int hw = win->ypos - win->y0;

  x_close_window(win->id);

  win->x0 = x0;
  win->y0 = y0;
  win->x1 = x1;
  win->y1 = y1;
  win->xpos = x0 + bw;
  win->ypos = y0 + hw;
  win->width = w - 2*bw; 
  win->height = h - hw - bw;

  x_open_window(win->id,win->x0,win->y0,win->width,win->height,0);
}


static void iconize(VgaWindow win)
{ Window w;
  int val,x,y;

  // highlight iconize button
  drawing_mode save_mode = x_set_mode(0,xor_mode);
  x_box(root_win->id,win->x0+14,win->y0+4,win->x0+26,win->y0+14);
  while (handle_next_event(&w,&val,&x,&y,1) != button_release_event);
  x_box(root_win->id,win->x0+14,win->y0+4,win->x0+26,win->y0+14);
  x_set_mode(0,save_mode);

  int x0 = win->save_x0;
  int y0 = win->save_y0;
  int x1 = win->save_x1;
  int y1 = win->save_y1;
  int bg = win->save_bg_col;
  win->save_x0 = win->x0;
  win->save_y0 = win->y0;
  win->save_x1 = win->x1;
  win->save_y1 = win->y1;
  win->save_bg_col = win->bg_col;
  win->bg_col =  bg;

  win->iconized = 1-win->iconized;
  change_geometry(win,x0,y0,x1,y1);
}


static void move_win(VgaWindow win, int *x, int *y)
{ int xp0 = win->x0;
  int yp0 = win->y0;
  int xp1 = win->x1;
  int yp1 = win->y1;
  int wi  = xp1-xp0+1;
  int he  = yp1-yp0+1;
  int xb  = win->xpos - xp0;
  int yb  = win->ypos - yp0;
  int xc = mouse_x; /* absolute cursor coordinates */
  int yc = mouse_y; 
  int dx = xc-xp0;  /* relative to upper left corner */
  int dy = yc-yp0;

  Window w;
  int e,val;

  drawing_mode save_mode = x_set_mode(0,xor_mode);

  x_rect(root_win->id,xp0-1,yp0-1,xp1+1,yp1+1);

  do { e = handle_next_event(&w,&val,x,y,1);
      if (mouse_x != xc || mouse_y != yc)
       { int rx0 = mouse_x-dx-1;
         int ry0 = mouse_y-dy-1;
         int rx1 = mouse_x-dx+wi;
         int ry1 = mouse_y-dy+he;
         x_rect(root_win->id,rx0,ry0,rx1,ry1);
         x_rect(root_win->id,xc-dx-1,yc-dy-1,xc-dx+wi,yc-dy+he);
         xc = mouse_x;
         yc = mouse_y;
       }
     } while (e != button_release_event);

  xc -= dx;
  yc -= dy;

  x_rect(root_win->id,xc-1,yc-1,xc+wi,yc+he);
  x_set_mode(save_mode,xor_mode);

  change_geometry(win,xc,yc,xc+wi-1,yc+he-1);
  if (!win->iconized) (*(win->redraw))(win->inf);

}


static void resize_win(VgaWindow win, int* x, int* y, int pos)
{ int xp0 = win->x0;
  int yp0 = win->y0;
  int xp1 = win->x1;
  int yp1 = win->y1;
  int xb  = win->xpos - xp0;
  int yb  = win->ypos - yp0;
  
  int xc  = mouse_x; /* absolute cursor coordinates */
  int yc  = mouse_y;

  Window w;
  int e,val,dx,dy;

  drawing_mode save_mode = x_set_mode(0,xor_mode);

  x_rect(root_win->id,xp0-1,yp0-1,xp1+1,yp1+1);

  switch(pos) {
   case 0: dx = xp0-xc; dy = yp0-yc;
           break;
   case 1: dx = xp1-xc; dy = yp0-yc;
           break;
   case 2: dx = xp1-xc; dy = yp1-yc;
           break;
   case 3: dx = xp0-xc; dy = yp1-yc;
           break;
   }

  do { e = handle_next_event(&w,&val,x,y,1);
       if (mouse_x != xc || mouse_y != yc)
        { 
          switch(pos) {
          case 0: x_rect(root_win->id,mouse_x+dx-1,mouse_y+dy-1,xp1+1,yp1+1);
                  x_rect(root_win->id,xc+dx-1,yc+dy-1,xp1+1,yp1+1);
                  break;
          case 1: x_rect(root_win->id,xp0-1,mouse_y+dy-1,mouse_x+dx+1,yp1+1);
                  x_rect(root_win->id,xp0-1,yc+dy-1,xc+dx+1,yp1+1);
                  break;
          case 2: x_rect(root_win->id,xp0-1,yp0-1,mouse_x+dx+1,mouse_y+dy+1);
                  x_rect(root_win->id,xp0-1,yp0-1,xc+dx+1,yc+dy+1);
                  break;
          case 3: x_rect(root_win->id,mouse_x+dx-1,yp0-1,xp1+1,mouse_y+dy+1);
                  x_rect(root_win->id,xc+dx-1,yp0-1,xp1+1,yc+dy+1);
                  break;
           }

          xc = mouse_x;
          yc = mouse_y;
        }
     } while (e != button_release_event);


     switch(pos) {
     case 0: x_rect(root_win->id,xc+dx-1,yc+dy-1,xp1+1,yp1+1);
             break;
     case 1: x_rect(root_win->id,xp0-1,yc+dy-1,xc+dx+1,yp1+1);
             break;
     case 2: x_rect(root_win->id,xp0-1,yp0-1,xc+dx+1,yc+dy+1);
             break;
     case 3: x_rect(root_win->id,xc+dx-1,yp0-1,xp1+1,yc+dy+1);
             break;
     }

   x_set_mode(0,save_mode);

   xc += dx;
   yc += dy;

   x_close_window(win->id);

   switch(pos) {
   case 0: win->x0 = xc; win->y0 = yc; 
           break;
   case 1: win->x1 = xc; win->y0 = yc; 
           break;
   case 2: win->x1 = xc; win->y1 = yc; 
           break;
   case 3: win->x0 = xc; win->y1 = yc; 
           break;
   }

  win->xpos = win->x0 + xb;
  win->ypos = win->y0 + yb;
  win->width  = win->x1 - win->x0 - 2*xb + 1; 
  win->height = win->y1 - win->y0 - xb - yb + 1; 

  x_open_window(win->id,win->x0,win->y0,win->width,win->height,0);

  *x = win->width;
  *y = win->height;
}



//------------------------------------------------------------------------------


static int manage_next_event(Window* w, int* val, int* x, int *y, 
                                                  unsigned long* t, int block)
{
  // non-blocking
  
  // a primitive window manager active while searching for next event

  int e = handle_next_event(w,val,x,y,block);

  VgaWindow win = win_list[*w];

  int bw = win->xpos - win->x0;  // border width
  int lw = win->ypos - win->y0;  // label (header) width

  int x0 = win->x0;
  int y0 = win->y0;
  int x1 = win->x1;
  int y1 = win->y1;

  if ((x0<=mouse_x && mouse_x<=x0+bw) || (x1>=mouse_x && mouse_x>=x1-bw) ||  
      (y0<=mouse_y && mouse_y<=y0+lw) || (y1>=mouse_y && mouse_y>=y1-bw) )
  {
    // pointer on window frame: move or resize window

    if (e != button_press_event) return no_event;

    // check "resize corners"  if not iconized

    if (! win->iconized)
    { int cx[4];
      int cy[4];
      cx[0] = x0;          cy[0] = y0;
      cx[1] = x1-RESIZE_W; cy[1] = y0;
      cx[2] = x1-RESIZE_W; cy[2] = y1-RESIZE_W;
      cx[3] = x0;          cy[3] = y1-RESIZE_W;
    
      for(int i=0; i<4; i++)
        if (cy[i] <= mouse_y && mouse_y <= cy[i]+RESIZE_W &&
            cx[i] <= mouse_x && mouse_x <= cx[i]+RESIZE_W )
         { resize_win(win,x,y,i);
           win->clip_x  = 0;
           win->clip_y  = 0;
           win->clip_x1 = *x-1;
           win->clip_y1 = *y-1;
           x_set_color(win->id,win->bg_col);
           x_box(win->id,0,0,*x,*y);
           *val = *x;
           *t = *y;
           *x = 0;
           *y = 0;
           return exposure_event;
          }
     }

   
    // check "iconize spot"

    if (x0+13 <= mouse_x && mouse_x <= x0+27 &&
        y0+3  <= mouse_y && mouse_y <= y0+15 )   
       { iconize(win);
         *x = 0;
         *y = 0;
         *val = win->width;
         *t   = win->height;
         return (win->iconized) ? no_event : exposure_event;
        }
    else
       { move_win(win,x,y); 
         return configure_event; 
        }

  }  // pointer on frame


  *t = int((1000.0/CLOCKS_PER_SEC)*clock());

  if (win->iconized) e = no_event;
 
  return e;
}


int x_check_next_event(Window* win, int* val, int* x, int *y, unsigned long* t)
{ // non-blocking
  return manage_next_event(win,val,x,y,t,0);
 }

int x_get_next_event(Window* win, int* val, int* x, int *y, unsigned long* t)
{ // blocking
  return manage_next_event(win,val,x,y,t,1);
 }


int x_get_next_event(Window* win, int* val, int* x, int* y, unsigned long *t,
                                                            int msec)
{ // get next event with timeout
  // not implemented
  return x_get_next_event(win,val,x,y,t);
}


void x_put_back_event() { vga_back_event(); }


void x_open_display()
{ 
  if (root_win) return; // called before

  vga_init(1);

  if (vga_depth() == 1)
  { root_color = white;
    label_col0 = white;
   }

  vga_clear(root_color);

  mouse_x = 0;
  mouse_y = 0;

  root_win = new vga_window;
  root_win->id = 0;
  win_list[0] = root_win;
  win_stack[0] = root_win;
  win_count = 0;
  win_top = 0;

  root_win->inf = 0;
  root_win->xpos = 0;
  root_win->ypos = 0;
  root_win->width  = vga_width();
  root_win->height = vga_height();

  root_win->x0 = 0;
  root_win->y0 = 0;
  root_win->x1 = root_win->width-1;
  root_win->y1 = root_win->height-1;

  root_win->clip_x = 0;
  root_win->clip_y = 0;
  root_win->clip_x1 = root_win->x1;
  root_win->clip_y1 = root_win->y1;

  root_win->bg_col = root_color;

  root_win->COLOR = black;
  root_win->LINEWIDTH = 1;
  root_win->LINESTYLE = solid;
  root_win->MODE = src_mode;
  root_win->TEXTMODE = transparent;

  if (win_top==0) // no windows (other than root)
  { x_set_color(0,root_color);
    x_box(root_win->id,0,0,root_win->clip_x1,root_win->clip_y1);
    x_set_color(0,black);
  }
}



void x_close_display()
{ vga_init(0);
  delete root_win;
 }


int   x_window_width(Window win)  { return win_list[win]->width; }
int   x_window_height(Window win) { return win_list[win]->height; }
void* x_window_inf(Window win)    { return win_list[win]->inf; }

void x_window_position(Window win,int* x,int* y) 
{ *x = win_list[win]->x0; 
  *y = win_list[win]->y0; 
 }


Window x_create_window(void* inf, int width,int height,int bg,
                     const char* header, const char* label, int wp,
                     void (*func)(void*))
{ 
  VgaWindow win = new vga_window;
  win_count++;
  win_list[win_count] = win;
  win->id = win_count;

  strcpy(win->header,header);
  strcpy(win->label,label);

  win->bg_col = bg;
  win->label_col = label_col0;

  win->width = width;
  win->height = height;

  width  += 2*BORDER_W;
  height += HEADER_W+BORDER_W;
  win->x0 = 0;
  win->y0 = 0;
  win->x1 = width-1;
  win->y1 = height-1;
  win->xpos = BORDER_W;
  win->ypos = HEADER_W;

  win->iconized = 0;
  win->save_x0 = BORDER_W;
  win->save_y0 = BORDER_W + 50*(win_count-1);
  win->save_x1 = win->save_x0 + leda_icon_width-1  + BORDER_W + BORDER_W;
  win->save_y1 = win->save_y0 + leda_icon_height-1 + HEADER_W + BORDER_W;
  win->save_bg_col = white;
  win->redraw = func;
  win->inf = inf;

  win->COLOR = black;
  win->LINEWIDTH = 1;
  win->LINESTYLE = solid;
  win->MODE = src_mode;
  win->TEXTMODE = transparent;


  win->mapped = 0;

  return win->id;
}


void x_open_window(int w, int x, int y, int width, int height, int pw)
{
  VgaWindow win = win_list[w];

  if (win->mapped) return;

  int bw = BORDER_W;
  int hw = (pw==0) ? HEADER_W : BORDER_W;

  if (pw)
  { bw = 2;
    hw = 2;
   }


  win_stack[++win_top] = win;
  win->mapped = win_top;


  width  += 2*bw;
  height += hw+bw;

  if (pw > 0)
  { VgaWindow pwin = win_list[pw];
    x += pwin->xpos;
    y += pwin->ypos;
   }

/*
if (width  > vga_width())  width = vga_width();
if (height > vga_height()) height = vga_height();
if (x+width  > vga_width())  x = 0;
if (y+height > vga_height()) y = 0;
*/

  win->x0 = x;
  win->y0 = y;
  win->x1 = x+width-1;
  win->y1 = y+height-1;
  win->xpos = x+bw;
  win->ypos = y+hw;
  win->width = width - 2*bw; 
  win->height = height - hw - bw;
  
  if (win->mapped > 1)
    { win->buf_w =win->x1 - win->x0 + 2;
      win->buf_h =win->y1 - win->y0 + 1;
      win->buf=vga_getimage(win->x0,win->y0,win->buf_w,win->buf_h);
     }
  else  
    win->buf = 0;

  draw_window(win);

}


int x_window_opened(Window w) 
{  return win_list[w] && win_list[w]->mapped; }


void x_clear_window(Window w)
{ 
  VgaWindow win = set_draw_window(w);

  // make window top of stack

  if (win->mapped && win->mapped != win_top)
  { x_close_window(win->id);
    x_open_window(win->id,win->x0,win->y0,win->width,win->height,0);
   }

  vga_set_color(win->bg_col);
  vga_set_mode(src_mode);
  int x = win->xpos;
  int y = win->ypos;
  vga_box(x,y,x+win->width-1,y+win->height-1);
 }


void x_close_window(Window w)
{ VgaWindow win = win_list[w];

  if (win->mapped == 0) return;

  int i = win_top+1;

  while(i > win->mapped) // remove w and all windows above w
  { VgaWindow wx = win_stack[--i];
    if (wx->mapped > 1)
    { vga_putimage(wx->x0,wx->y0,wx->buf_w,wx->buf_h,wx->buf);
      vga_delimage(wx->buf);
     }
    else
    { int c = x_set_color(0,root_color);
      drawing_mode m  = x_set_mode(0,src_mode);
      x_box(0,wx->x0,wx->y0,wx->x1,wx->y1);
      x_set_color(0,c);
      x_set_mode(0,m);
     }
   }

  while (i < win_top)
  { VgaWindow wx = win_stack[i+1];
    win_stack[i] = wx;
    wx->mapped = i;

    if (wx->mapped > 1)
    { wx->buf_w =wx->x1 - wx->x0 + 2;
      wx->buf_h =wx->y1 - wx->y0 + 1;
      wx->buf=vga_getimage(wx->x0,wx->y0,wx->buf_w,wx->buf_h);
     }
    else  
    wx->buf = 0;

    draw_window(wx);
    if (!wx->iconized) (*(wx->redraw))(wx->inf);

    i++;
   }

  win_top--;
  win->mapped = 0;
 }


void x_destroy_window(Window w) 
{ VgaWindow win = win_list[w];
  if (win->mapped) x_close_window(w);
  delete win; 
  win_list[w] = 0;
}


void x_set_header(Window w, const char* s)
{ 

  char str[80];
  VgaWindow win = win_list[w];
  int n = (win->width - 40)/x_text_width(0,"H");

  int bw  = win->xpos - win->x0 - 2;
  int lw  = win->ypos - win->y0 - 2;

  int          save_co = x_set_color(0,win->label_col);
  text_mode    save_tm = x_set_text_mode(0,transparent);
  drawing_mode save_mo = x_set_mode(0,src_mode);

  x_box(root_win->id,win->x0+30,win->y0+bw,win->x1-bw,win->y0+lw);

  x_set_color(0,black);
  if (s != win->header) strcpy(win->header,s);

  strcpy(str,win->header);
  str[n] = 0;
  x_ctext(root_win->id,win->x0 + win->width/2 + 20, win->y0 + 10 ,str);

  x_set_text_mode(0,save_tm);
  x_set_color(0,save_co);
  x_set_mode(0,save_mo);
 }

