/*******************************************************************************
+
+  LEDA 3.5
+
+  draw.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 <math.h>
#include <LEDA/graphwin.h>

#include <LEDA/bitmaps/leda_icon.xpm>


static int t_color[16];

static bool is_north[9];
static bool is_south[9];
static bool is_west[9];
static bool is_east[9];

void GraphWin::init_static() {
  is_north[central_pos  ]=false; is_south[central_pos  ]=false;
  is_north[northwest_pos]=true;  is_south[northwest_pos]=false;
  is_north[north_pos    ]=true;  is_south[north_pos    ]=false;
  is_north[northeast_pos]=true;  is_south[northeast_pos]=false;
  is_north[east_pos     ]=false; is_south[east_pos     ]=false;
  is_north[southeast_pos]=false; is_south[southeast_pos]=true;
  is_north[south_pos    ]=false; is_south[south_pos    ]=true;
  is_north[southwest_pos]=false; is_south[southwest_pos]=true;
  is_north[west_pos     ]=false; is_south[west_pos     ]=false;

  is_west[central_pos  ]=false; is_east[central_pos  ]=false;
  is_west[northwest_pos]=true;  is_east[northwest_pos]=false;
  is_west[north_pos    ]=false; is_east[north_pos    ]=false;
  is_west[northeast_pos]=false; is_east[northeast_pos]=true;
  is_west[east_pos     ]=false; is_east[east_pos     ]=true;
  is_west[southeast_pos]=false; is_east[southeast_pos]=true;
  is_west[south_pos    ]=false; is_east[south_pos    ]=false;
  is_west[southwest_pos]=true;  is_east[southwest_pos]=false;
  is_west[west_pos     ]=true;  is_east[west_pos     ]=false;

  t_color[white ]=black;
  t_color[black ]=white;
  t_color[red   ]=white;
  t_color[green ]=black;
  t_color[blue  ]=white;
  t_color[yellow]=black;
  t_color[violet]=white;
  t_color[orange]=black;
  t_color[cyan  ]=black;
  t_color[brown ]=white;
  t_color[pink  ]=white;
  t_color[green2]=black;
  t_color[blue2 ]=white;
  t_color[grey1 ]=black;
  t_color[grey2 ]=black;
  t_color[grey3 ]=white;
}

inline int gw_color(int col) { return (col > 15 ? black : t_color[col]); }

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

void GraphWin::draw_node(node v) {

  draw_node(v,0,0);

}

void GraphWin::draw_node(node v, double dx, double dy) {

  if (!win_init_done) return;

  node_info &v_inf=n_info[v];

  double r1 = v_inf.r1;
  double r2 = v_inf.r2;
  double x  = v_inf.pos.xcoord()+dx;
  double y  = v_inf.pos.ycoord()+dy;

  int save_lw  = win_p->set_line_width(1);
  int clr      = v_inf.clr;
  int line_clr = v_inf.border_clr;

/*
  if (v == current_node && !v_inf.selected) 
  { clr = (clr == blue2) ? yellow : blue2;
    //line_clr = (line_clr == red) ? yellow : red;
   }
*/


 double sel_w = win_p->pix_to_real(2);

 if (v_inf.shape == square_node || v_inf.shape == circle_node) r2 = r1;
 if (v_inf.shape == circle_node || v_inf.shape == ellipse_node)
       sel_w = win_p->pix_to_real(1);

  r1  = win_p->pix_to_real(win_p->real_to_pix(r1));
  r2  = win_p->pix_to_real(win_p->real_to_pix(r2));

  x  = win_p->xreal(win_p->xpix(x));
  y  = win_p->yreal(win_p->ypix(y));

  double x0 = x - r1;
  double y0 = y - r2;
  double x1 = x + r1;
  double y1 = y + r2;

  if (v_inf.selected) 
  { double sx0 = x0 - sel_w; 
    double sy0 = y0 - sel_w;
    double sx1 = x1 + sel_w;
    double sy1 = y1 + sel_w;
    win_p->draw_box(sx0,sy0,sx1,sy1,(clr != red) ? red : yellow);
    win_p->draw_rectangle(sx0,sy0,sx1,sy1,black);
   }

   int label_clr = v_inf.label_clr;

   if (label_clr == -1) label_clr = gw_color(clr);


  switch (v_inf.shape) {

    case circle_node  : 
    case ellipse_node : 
            win_p->draw_filled_ellipse(x,y,r1,r2,clr);
	    win_p->draw_ellipse(x,y,r1,r2,line_clr);
            if (v == current_node)
            { double d = win_p->pix_to_real(1);
	      win_p->draw_ellipse(x,y,r1-d,r2-d,line_clr);
             }
	    break;


    case square_node : 
    case rectangle_node : {

            win_p->draw_box(x0,y0,x1,y1,clr);

            if (clr == grey3) // pixmap (experimental)
            { int d1 = win_p->real_to_pix(r1);
              int d2 = win_p->real_to_pix(r2);
              int dx = 55 - d1;
              int dy = 55 - d2;
              win_p->put_pixrect(x0,y0,leda_xpm,dx,dy,2*d1,2*d2);
             }


            if (clr == white || clr != bg_color)
               win_p->draw_rectangle(x0,y0,x1,y1,line_clr);
            else
             { color c1 = white;
               color c2 = grey3;
               win_p->draw_segment(x0,y0,x0,y1,c1);
               win_p->draw_segment(x0,y1,x1,y1,c1);
               win_p->draw_segment(x0,y0,x1,y0,c2);
               win_p->draw_segment(x1,y0,x1,y1,c2);
              }
   

            if (v == current_node)
            { double d = win_p->pix_to_real(1);
              win_p->draw_rectangle(x0+d,y0+d,x1-d,y1-d,line_clr);
             }

	    break;
     }
  }    

  win_p->set_line_width(save_lw);


  string label = get_label(v);
    
  if (label.length() == 0) return; 

  gw_position label_pos = v_inf.label_pos;

  if (label_pos == central_pos)
    { y += units_per_pixel;
      win_p->draw_ctext(x,y,label,label_clr);
     }
  else 
    { double tw = win_p->text_width(label);
      double th = win_p->text_height(label);

      double d  = win_p->pix_to_real(3);

      if (is_east[label_pos]) 
         x += r1+d;
      else
        if (is_west[label_pos]) 
           x -= r1+tw+d;
        else 
           x -= tw/2;

      if (is_north[label_pos]) 
         y += r2+th+d;
      else
        if (is_south[label_pos]) 
           y -= r2+d;
        else 
           y += th/2;
    
       win_p->draw_text(x,y,label,v_inf.label_clr);
    }

}

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

void GraphWin::draw_edge(edge e) {
  draw_edge(e,0,0);
}

void GraphWin::draw_edge(edge e, double dx, double dy) 
{
  if (!win_init_done) return;

  edge_info& e_inf = e_info[e];
  list<point>&   P = e_inf.p;	

  if (P.size() < 2) return;

  node v = source(e);
  node w = target(e);
  point src_p = get_position(v);
  point tar_p = get_position(w);

  if (P.size() == 2 && src_p == tar_p) return;

  color c = e_inf.clr;

/*
  if (e == found_edge && !e_inf.selected) 
    c = (c == red) ? green : red;
*/

  line_style old_style = win_p->set_line_style((line_style)e_inf.style);

  int e_width = e_inf.width;

  if (e_inf.selected) e_width += 1;

  int old_width = win_p->set_line_width(e_width);

  int old_join_style = win_p->set_join_style(3);

  list_item it=P.first();
  
  const point& p=P[it];

  double x0 = p.xcoord()+dx;
  double y0 = p.ycoord()+dy;
  double x1;
  double y1;

  it=P.succ(it);

  for(;;) {
    const point& p=P[it];
    x1=p.xcoord() + dx;
    y1=p.ycoord() + dy;
    if ((it=P.succ(it)) == nil) break;
    win_p->draw_segment(x0,y0,x1,y1,c);
    x0=x1;
    y0=y1;
  }

//win_p->set_join_style(0);

  if (directed) 
     win_p->draw_arrow(x0,y0,x1,y1,c);
  else 
     win_p->draw_segment(x0,y0,x1,y1,c);

 win_p->set_join_style(old_join_style);

  win_p->set_line_style(old_style);
  win_p->set_line_width(old_width);

  if (get_label_type(e) != no_label) {

    string label = get_label(e);
    
    if (label.length()) {


      gw_position label_pos=e_inf.label_pos;

      list_item it0=P.first(),it1=P.succ(it0);
      double x0=P[it0].xcoord(),y0=P[it0].ycoord();
      double x1=P[it1].xcoord(),y1=P[it1].ycoord();

      double x=(x0+x1)/2.0,y=(y0+y1)/2.0;

      if (label_pos == central_pos) 
	win_p->draw_ctext(x,y,label,e_inf.label_clr);
      
      else {

        double tw=win_p->text_width(label), th=win_p->text_height(label);

        double d = units_per_pixel;

	bool rturn = is_east[label_pos]; 

	if (x0 > x1) rturn = !rturn;

	if (x0 == x1) if (y0 > y1) rturn=!rturn;

	line l(P[it0],P[it1]);

	if (l.is_horizontal()) {
	  x-=tw/2;
	  if (rturn) y-=d; else y+=d+th;
	}
	else {
	  y+=th/2;
	  if (l.is_vertical()) {
	    if (rturn) x+=d; else x-=d+tw;
	  }
	  else {
	    double m = l.slope();

	    double rx=tw*0.707,ry=th*0.707;  // 0.707 = sqrt(2.0)
	    double rx_rx=rx*rx,ry_ry=ry*ry;
	    double m_rx_rx=m*rx_rx;
	    double t=sqrt(m*m_rx_rx+ry_ry);
	    if (rturn) { x+=m_rx_rx/t; y-=ry_ry/t; }
	    else { x-=m_rx_rx/t; y+=ry_ry/t; }

	    //	    win_p->draw_ellipse(x,y-th/2,rx,ry,blue);

	    x-=tw/2.0;
	  }
	}

	win_p->draw_text(x,y,label,e_inf.label_clr);
      }
    }
  }

}

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

void GraphWin::draw_node_with_edges(node v) { 
  edge e;
  forall_adj_edges(e,v) draw_edge(e);
  forall_in_edges(e,v) if (source(e) != target(e)) draw_edge(e);
  draw_node(v);
}

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


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

void GraphWin::draw_edges(node v, node w) {
  edge e;
  forall_incident_edges(e,v,w) draw_edge(e);
  if (v != w) 
    forall_incident_edges(e,w,v) draw_edge(e);
}

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


void GraphWin::draw_graph() { 
  if (!edges_embedded) embed_edges();
  edge e;
  forall_edges(e,*gr_p) draw_edge(e);
  node v;
  forall_nodes(v,*gr_p) draw_node(v);
}          
           
//----------------------------------------------------------------------------

void GraphWin::draw_graph(double dx, double dy) { 
  if (!edges_embedded) embed_edges();
  edge e;
  forall_edges(e,*gr_p) draw_edge(e,dx,dy);
  node v;
  forall_nodes(v,*gr_p) draw_node(v,dx,dy);
}          
           
//----------------------------------------------------------------------------

void GraphWin::draw_graph(node v) {
  const graph& G=get_graph();
  edge e;
  forall_edges(e,G) if ((target(e) != v) && (source(e) != v)) draw_edge(e);
  node w;
  forall_nodes(w,G) if (w != v) draw_node(w);
}


void GraphWin::draw_graph(const list<node>& L) {
  const graph& G=get_graph();
  node_array<bool> draw(G,true);
  node v;
  forall(v,L) draw[v] = false;
  edge e;
  forall_edges(e,G) if (draw[target(e)] && draw[source(e)]) draw_edge(e);
  forall_nodes(v,G) if (draw[v]) draw_node(v);
}

void GraphWin::draw_graph(edge e) {
  const graph& G=get_graph();
  edge x;
  forall_edges(x,G) if (x != e) draw_edge(x);
  node v;
  forall_nodes(v,G) draw_node(v);
}


void GraphWin::draw_graph(const list<edge>& L) {
  const graph& G=get_graph();
  edge_array<bool> draw(G,true);
  edge e;
  forall(e,L) draw[e] = false;
  forall_edges(e,G) if (draw[e]) draw_edge(e);
  node v;
  forall_nodes(v,G) draw_node(v);
}


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

void GraphWin::s_redraw(window *w_p, double x0, double y0, double x1, double y1)
{ 
  window&    W = *w_p;
  GraphWin& gw = *(GraphWin*)W.get_inf();


  if (gw.x_max != W.xmax() || gw.y_max != W.ymax()) // window resized
  { gw.set_show_status(false);
    if (gw.get_constant_width())
    { bool old_flush=gw.set_flush(false);
      graph& G=gw.get_graph();
      double scale=1.0/(gw.units_per_pixel*W.scale());
      node v;
      gw.set_node_radius1(gw.get_node_radius1()*scale);
      gw.set_node_radius2(gw.get_node_radius2()*scale);
      gw.set_edge_distance(gw.get_edge_distance()*scale);
      forall_nodes(v,G) 
      { gw.set_radius1(v,gw.get_radius1(v)*scale);  
        gw.set_radius2(v,gw.get_radius2(v)*scale);
       }
      gw.embed_edges();
      gw.set_flush(old_flush);
     }
    gw.update_win_bounds();
    gw.units_per_pixel=1.0/W.scale();
   }


  W.start_buffering();
  W.clear();
  gw.draw_graph();
  W.flush_buffer(x0,y0,x1,y1);
  W.stop_buffering();
}

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

void GraphWin::redraw() 
{ if (win_p->is_open())
  { win_p->start_buffering();
    win_p->clear();
    draw_graph();
    win_p->flush_buffer();
    win_p->stop_buffering();
   }
  status_redraw(status_win);
}


//----------------------------------------------------------------------------
// animation
//----------------------------------------------------------------------------

static void compute_bounding_box(GraphWin& gw, node v, 
                                 double& x0, double& y0, double& x1, double& y1)
{
  // computes bounding box for v and its adjacent nodes

  window& w = gw.get_window();

  point q = gw.get_position(v);

  double r1 = gw.get_radius1(v);
  double r2 = gw.get_radius2(v);
  double tw = w.text_width(gw.get_label(v));
  double th = w.text_height(gw.get_label(v));

  r1 += Max(tw,r1);
  r2 += Max(th,r2);


  x0 = q.xcoord() - r1;
  x1 = q.xcoord() + r1;
  y0 = q.ycoord() - r2;
  y1 = q.ycoord() + r2;

  edge e;
  forall_adj_edges(e,v)
  { list<point> pol = gw.get_bends(e);
    point p; 
    if (pol.empty())
        p = gw.get_position(target(e));
    else
        p = pol.head();
    double r1 = 2*gw.get_radius1(target(e));
    double r2 = 2*gw.get_radius2(target(e));
    if (p.xcoord()-r1 < x0) x0 = p.xcoord()-r1;
    if (p.xcoord()+r1 > x1) x1 = p.xcoord()+r1;
    if (p.ycoord()-r2 < y0) y0 = p.ycoord()-r2;
    if (p.ycoord()+r2 > y1) y1 = p.ycoord()+r2;
   }
  
  forall_in_edges(e,v)
  { list<point> pol = gw.get_bends(e);
    point p; 
    if (pol.empty())
        p = gw.get_position(source(e));
    else
        p = pol.tail();
    double r1 = 2*gw.get_radius1(source(e));
    double r2 = 2*gw.get_radius2(source(e));
    if (p.xcoord()-r1 < x0) x0 = p.xcoord()-r1;
    if (p.xcoord()+r1 > x1) x1 = p.xcoord()+r1;
    if (p.ycoord()-r2 < y0) y0 = p.ycoord()-r2;
    if (p.ycoord()+r2 > y1) y1 = p.ycoord()+r2;
   }

}



static void adjust_bounding_box(GraphWin& gw, node v,
                                double& x0, double& y0, double& x1, double& y1)
{
  string  s = gw.get_label(v);
  point   p = gw.get_position(v);
  double r1 = gw.get_radius1(v);
  double r2 = gw.get_radius2(v);
  double tw = gw.get_window().text_width(s);
  double th = gw.get_window().text_height(s);

  r1 += Max(tw,r1);
  r2 += Max(th,r2);

  double x = p.xcoord();
  double y = p.ycoord();

  if (x-r1 < x0) x0 = x-r1;
  if (x+r1 > x1) x1 = x+r1;
  if (y-r2 < y0) y0 = y-r2;
  if (y+r2 > y1) y1 = y+r2;
}



static void adjust_bounding_box(GraphWin& gw, edge e, 
                                double& x0, double& y0, double& x1, double& y1)
{ list<point> L = gw.get_edge_points(e);
  double  d = gw.get_window().pix_to_real(2);
  point p;
  forall(p,L)
  { if (p.xcoord() < x0) x0 = p.xcoord() - d;
    if (p.xcoord() > x1) x1 = p.xcoord() + d;
    if (p.ycoord() < y0) y0 = p.ycoord() - d;
    if (p.ycoord() > y1) y1 = p.ycoord() + d;
   }
}


static void compute_bounding_box(GraphWin& gw, edge e, 
                                 double& x0, double& y0, double& x1, double& y1)
{
  node  v = source(e);
  point p = gw.get_position(v);

  double r1 = gw.get_radius1(v);
  double r2 = gw.get_radius2(v);

  x0 = p.xcoord()-r1;
  x1 = p.xcoord()+r1;
  y0 = p.ycoord()-r2;
  y1 = p.ycoord()+r2;

  adjust_bounding_box(gw,e,x0,y0,x1,y1);
}




void GraphWin::n_animation_start(const list<node>& L)
{ n_anim_list = L;
  n_anim_flush = set_flush(false);
  window& W = get_window();
  W.start_buffering();
  W.clear();
  draw_graph(L);
  n_anim_buf = W.get_window_pixrect();
  node v;
  forall(v,L) draw_node_with_edges(v);
  W.flush_buffer();
  W.stop_buffering();

  if (L.length() == 1) 
  compute_bounding_box(*this,L.head(),anim_x0,anim_y0,anim_x1,anim_y1);
}



void GraphWin::n_animation_end()
{ window& W = get_window();
  node v;
  forall(v,n_anim_list) embed_node_with_edges(v);
  redraw();
  W.del_pixrect(n_anim_buf);
  n_anim_list.clear();
  set_flush(n_anim_flush);
}


void GraphWin::n_animation_step(bool nodes_only)
{ window& W = get_window();
  W.start_buffering();
  W.put_pixrect(n_anim_buf);
  node v;
  if (nodes_only)
    forall(v,n_anim_list) draw_node(v);
  else
    forall(v,n_anim_list)
    { embed_node_with_edges(v);
      draw_node_with_edges(v);
     }
  if (n_anim_list.length() == 1)
   { adjust_bounding_box(*this,n_anim_list.head(),anim_x0,anim_y0,
                                                  anim_x1,anim_y1);
     W.flush_buffer(anim_x0,anim_y0,anim_x1,anim_y1);
    }
  else
     W.flush_buffer();
  W.stop_buffering();
}


void GraphWin::e_animation_start(const list<edge>& L)
{ e_anim_list = L;
  e_anim_flush = get_flush();
  window& W = get_window();
  W.start_buffering();
  W.clear();
  draw_graph(L);
  e_anim_buf = W.get_window_pixrect();
  edge e;
  forall(e,L) draw_edge(e);
  W.flush_buffer();
  W.stop_buffering();
  if (L.length() == 1) 
    compute_bounding_box(*this,L.head(),anim_x0,anim_y0,anim_x1,anim_y1);
}


void GraphWin::e_animation_end()
{ window& W = get_window();
  redraw();
  W.del_pixrect(e_anim_buf);
  e_anim_list.clear();
  set_flush(e_anim_flush);
}


void GraphWin::e_animation_step()
{ window& W = get_window();
  W.start_buffering();
  W.put_pixrect(e_anim_buf);
  edge e;
  forall(e,e_anim_list) 
  { node v = source(e);
    node w = target(e);
    embed_edges(v,w);
    draw_edges(v,w);
   }
  if (e_anim_list.length() == 1)
   { adjust_bounding_box(*this,e_anim_list.head(),anim_x0,anim_y0,
                                                  anim_x1,anim_y1);
     W.flush_buffer(anim_x0,anim_y0,anim_x1,anim_y1);
    }
  else
    W.flush_buffer();
  W.stop_buffering();
}


