/*
 * file: gecontext.C - glyph for 3D drawing
 *
 * original version: Copyright (c) 1991 Stanford University
 *                   Copyright (c) 1991 Silicon Graphics, Inc.
 *
 * changes by: Michael Pichler
 *
 * created: 10 Nov 1992
 *
 * changed: 12 Dec 1995
 */



#include "gecontext.h"
#include "wtable.h"

#include <hyperg/hyperg/verbose.h>

#include <InterViews/canvas.h>
#include <InterViews/display.h>
#include <InterViews/session.h>
#include <InterViews/style.h>
#include <InterViews/window.h>
#include <IV-look/kit.h>
#include <IV-X11/Xlib.h>
#include <IV-X11/xcanvas.h>
#include <IV-X11/xdisplay.h>
#include <IV-X11/xwindow.h>

#include <OS/leave-scope.h>

/* to get rid of the inexistent header warning by makedepend */
/* gecontext.C is compiled for SGIs only */
#ifdef IRIX
#include <gl/glws.h>
#endif

#include <iostream.h>
#include <string.h>

#include "memleak.h"



// class GLWindow for Implementation of class GEContext


class GLWindow: public Window
{
  public:
    GLWindow (Glyph*, Window* parent, int overlay);
    ~GLWindow ();

    virtual void place (Coord x, Coord y);
    virtual void bind ();
    virtual void repair ();

    virtual void setattributes ()
    { set_attributes (); }

    void activateOverlay (int clear);
    void deactivateOverlay ();

  private:
    Window* parent_;
    XWindow overlay_xwin_;
    int double_buffered_;
    int requestoverlay_;
};



GLWindow::GLWindow (Glyph* g, Window* parent, int overlay)
: Window(g)
{
  parent_ = parent;
  requestoverlay_ = overlay;
  overlay_xwin_ = 0;

  Display* disp = parent->display ();
  display (disp);

  Style* s = new Style (disp->style ());
  s->alias ("GLWindow");
  // use double buffering for GL window (unless single buffered required explicitly)
  double_buffered_ = !s->value_is_on ("single_buffered");  // default on
  s->attribute ("double_buffered", "off");  // otherwise InterViews causes errors!
  style (s);
}


GLWindow::~GLWindow()
{
  WindowRep* w = rep();
  GLXunlink (w->display_->rep()->display_, w->xwindow_);
}


// no GLWindow::draw


void GLWindow::place (Coord x, Coord y)
{
  Window::place (x, y);
  WindowRep* wrep = rep ();
  Display* dis = wrep->display_;
  wrep->xpos_ = dis->to_pixels (wrep->left_);
  wrep->ypos_ = parent_->canvas ()->pheight () - dis->to_pixels (wrep->bottom_) 
                - canvas ()->pheight ();
}



static void configentry (GLXconfig& cf, int buffer, int mode, int arg)
{
  // little helper function used in GLWindow::bind
  cf.buffer = buffer;
  cf.mode = mode;
  cf.arg = arg;
}


static void get_visual_info (GLXconfig* cf, DisplayRep* d, Visual*& vis, unsigned int& depth)
{
  // little helper function used in GLWindow::bind
  XVisualInfo vinfo;
  vinfo.screen = d->screen_;
  vinfo.visualid = (long) cf->arg;
  int nv = 0;
  XVisualInfo* visuals = XGetVisualInfo (
    d->display_, VisualScreenMask | VisualIDMask, &vinfo, &nv
  );
  if (visuals != nil)
  {
    if (nv > 0)
    { vis = visuals->visual;
      depth = visuals->depth;
      DEBUGNL ("get_visual_info: visual with ID 0x" << hex << vis->visualid << dec << ", depth " << depth);
    }
    XFree ((char*) visuals);
  }
}


// #define setXColorEntry( xcol, pix, r, g, b )  \
// (xcol).pixel = (pix),  (xcol).red = (r),  (xcol).green = (g),  (xcol).blue = (b)
// // little helper to set an XColor entry


void GLWindow::bind ()
{
  GLXconfig init_config [16];
  DisplayRep* d = display ()->rep ();
  int overlay_type = GLX_OVERLAY;

  DEBUGNL ("GLWindow::bind");

  if (requestoverlay_)  // request overlay bitplanes
  {
    WidgetKit& kit = *WidgetKit::instance ();
    kit.begin_style ("GLWindow");

    // use popup bitplanes unless the user prefers overlay planes and they are also available
    if (getgdesc (GD_BITS_OVER_SNG_CMODE) < 2 || !kit.style ()->value_is_on ("overlayPlanes"))
    { DEBUGNL ("GLWindow: using popup bitplanes");
      overlay_type = GLX_POPUP;
    }
    else
    { DEBUGNL ("GLWindow: using overlay bitplanes");
      // overlay_type = GLX_OVERLAY;
    }

    configentry (init_config [0], GLX_NORMAL, GLX_RGB, TRUE);
    configentry (init_config [1], GLX_NORMAL, GLX_DOUBLE, double_buffered_);
    configentry (init_config [2], GLX_NORMAL, GLX_ZSIZE, GLX_NOCONFIG);  // Z-Buffer
    configentry (init_config [3], overlay_type, GLX_BUFSIZE, 2);  // GLX_NOCONFIG);
    configentry (init_config [4], overlay_type, GLX_DOUBLE, FALSE);  // single buf. overlay (default)
    configentry (init_config [5], 0, 0, 0);

    kit.end_style ();  // "GLWindow"
  }
  else  // usually: RGB, double buffered window, no overlay
  {
    // set up desired glx configuration
    configentry (init_config [0], GLX_NORMAL, GLX_RGB, TRUE);
    configentry (init_config [1], GLX_NORMAL, GLX_DOUBLE, double_buffered_);
    configentry (init_config [2], GLX_NORMAL, GLX_ZSIZE, GLX_NOCONFIG);  // 19950130
    configentry (init_config [3], 0, 0, 0);
  }

  // get available glx configuration
  GLXconfig* retconfig = GLXgetconfig (d->display_, d->screen_, init_config);
  if (!retconfig)
    cerr << "error on GLXgetconfig; requested window type not available!" << endl;

  WindowVisual* wv = WindowVisual::find_visual (display (), style ());
  Visual* vi_sual = wv->visual ();
  Visual* overlay_visual = vi_sual;
  unsigned int dep_th = wv->depth ();
  unsigned int overlay_depth = dep_th;
  XColormap color_map = 0L;
  XColormap overlay_color_map = 0L;

  for (GLXconfig* cf = retconfig; cf->buffer != 0; cf++)
  {
    // extract COLORMAP and VISUAL fields for NORMAL and OVERLAY windows
    if (cf->buffer == GLX_NORMAL && cf->mode == GLX_COLORMAP)
      color_map = (long) cf->arg;
    else if (cf->buffer == overlay_type && cf->mode == GLX_COLORMAP)
      overlay_color_map = (long) cf->arg;
    else if (cf->buffer == GLX_NORMAL && cf->mode == GLX_VISUAL)
      get_visual_info (cf, d, vi_sual, dep_th);
    else if (cf->buffer == overlay_type && cf->mode == GLX_VISUAL)
      get_visual_info (cf, d, overlay_visual, overlay_depth);
  } // for cf

  DEBUGNL ("visual used for GL has ID 0x" << hex << vi_sual->visualid << dec);
  if (requestoverlay_)
  { DEBUGNL ("overlay visual used for GL has ID 0x" << hex << overlay_visual->visualid << dec);
  }

  // create a window

  WindowRep* w = rep ();
  Canvas* c = w->canvas_;
  d = w->display_->rep ();
  XDisplay* dpy = d->display_;
  WindowTable* t = d->wtable_;
  XWindow xw = w->xwindow_;

  if (xw != WindowRep::unbound)
    t->remove (xw);

  Window::set_attributes ();
  w->xattrmask_ &= ~CWDontPropagate;     // IV 3.1
  w->xattrs_.do_not_propagate_mask = 0;  // IV 3.1
  w->xattrmask_ |= CWColormap;
  w->xattrs_.colormap = color_map;

  // interested in structure and pointer events
  w->xattrs_.event_mask = ExposureMask | StructureNotifyMask |
    ButtonPressMask | ButtonReleaseMask |
    PointerMotionMask | PointerMotionHintMask;

  xw = XCreateWindow (
    dpy, parent_->rep ()->xwindow_,
    w->xpos_, w->ypos_, c->pwidth (), c->pheight (), 0, // border width
    dep_th, w->xclass_, vi_sual, w->xattrmask_, &w->xattrs_
  );

  c->rep ()->xdrawable_ = xw;
  t->insert (xw, this);
  w->xwindow_ = xw;  // this X window will be used for GL
  w->xtoplevel_ = w->toplevel_->rep ()->xwindow_;

  if (requestoverlay_)  // create an overlay child window
  {
    // taken from GL-Xlib example (crash when using w->attrs_)
    XSetWindowAttributes init_attr;
    init_attr.colormap = overlay_color_map;
    init_attr.border_pixel = 0;  // Must exist, otherwise BadMatch error

    overlay_xwin_ = XCreateWindow (
      dpy, xw, 0, 0, 2000, 2000, 0,  // do not care for real size
      overlay_depth, InputOutput, overlay_visual, CWColormap|CWBorderPixel, &init_attr
    );

    XMapWindow (dpy, overlay_xwin_);

  } // overlay window

  // fill in X window ids in config array for GLXlink
  for (cf = retconfig; cf->buffer != 0; cf++)
  {
    if (cf->buffer == GLX_NORMAL && cf->mode == GLX_WINDOW)
    {
      // cerr << "  set GLX_WINDOW for normal window to " << (int) xw << endl;
      cf->arg = (int) xw;
    }
    else if (requestoverlay_ && cf->buffer == overlay_type && cf->mode == GLX_WINDOW)
    {
      // cerr << "  set GLX_WINDOW for overlay window to " << (int) overlay_xwin_ << endl;
      cf->arg = (int) overlay_xwin_;
    }
  }

  if (GLXlink (dpy, retconfig) < 0)
    cerr << "error on calling GLXlink!" << endl;

  // in gl-Xlib examples (not necessary)
  // GLXwinset (dpy, xw);

  XMapRaised (d->display_, xw);

  // like in gl-Xlib example program it may be better to set colours
  // of overlay planes here with XStoreColor (also possible with ge3d_MapColori)

  if (requestoverlay_)
  { // give overlay window its own colour map
    XSetWMColormapWindows (dpy, parent_->rep ()->xwindow_, &overlay_xwin_, 1);
  }

} // GLWindow::bind



void GLWindow::repair ()
{
  WindowRep* wrep = rep ();
  Canvas* c = wrep->canvas_;
  CanvasRep* can = c->rep();

  if (can->damaged_)
  {
    int retval = (int) GLXwinset (wrep->display_->rep ()->display_, wrep->xwindow_);
    if (retval)
      cerr << "GLXwinset returned error code " << retval << endl;

    // reshapeviewport ();  // tell GL that window size may have changed
    viewport (0, c->pwidth () - 1, 0, c->pheight () - 1);
    // reshapeviewport should be avoided (see glxwinset man page)

    // in no double buffering is available, redraw only the damaged region
    if (!double_buffered_)
    { CanvasDamage& dam = can->damage_;
      Display* d = can->display_;
      scrmask (d->to_pixels (dam.left), d->to_pixels (dam.right),
               d->to_pixels (dam.bottom), d->to_pixels (dam.top));
    }

    wrep->glyph_->draw (wrep->canvas_, wrep->allocation_);

    if (double_buffered_)
      swapbuffers ();
    else
      gflush ();

    can->clear_damage();
  }

} // repair


void GLWindow::activateOverlay (int clearing)
{
  WindowRep* wrep = rep ();
  GLXwinset (wrep->display_->rep ()->display_, overlay_xwin_);

  if (clearing)
  { color (0);
    clear ();
  }
}


void GLWindow::deactivateOverlay ()
{
  WindowRep* wrep = rep ();
  GLXwinset (wrep->display_->rep ()->display_, wrep->xwindow_);
}



// Implementation of class GEContext


GEContext::GEContext (Glyph* g, int requests)
: MonoGlyph (g)
{
  glwin_ = 0;  // created in allocate
  origcur_ = 0;
  requestoverlay_ = requests & RequestOverlay;
}


GEContext::~GEContext()
{
  Delete glwin_;
}


Window* GEContext::window ()
{ 
  return glwin_;
}


void GEContext::setCursor (Cursor* csr)
{
  if (glwin_)
  { if (!origcur_)
      origcur_ = glwin_->cursor ();
    glwin_->cursor (csr);
  }
}


void GEContext::resetCursor ()
{
  if (glwin_ && origcur_)
    glwin_->cursor (origcur_);
}


void GEContext::pushCursor (Cursor* csr)
{
  if (glwin_)
  { if (!origcur_)
      origcur_ = glwin_->cursor ();
    glwin_->push_cursor ();
    glwin_->cursor (csr);
  }
}


void GEContext::popCursor ()
{
  if (glwin_)
    glwin_->pop_cursor ();
}


int GEContext::overlaySupport ()
{
  return 1;  // presence of (at least 2bit) overlay plane guaranteed under IrisGL
}


void GEContext::activateOverlay (int clear)
{
  if (glwin_ && requestoverlay_)
    glwin_->activateOverlay (clear);
}


void GEContext::deactivateOverlay ()
{
  if (glwin_)
    glwin_->deactivateOverlay ();
}


void GEContext::allocate (Canvas* c, const Allocation& a, Extension&)
// told my actual allocation
{
  //cerr << "GEContext::allocate" << endl;

  const Allotment& ax = a.x_allotment ();
  const Allotment& ay = a.y_allotment ();


  if (!glwin_)  // if first allocation, create a New child window for GL
  {
    glwin_ = New GLWindow (body (), c->window (), requestoverlay_);

    glwin_->canvas()->size (ax.span (), ay.span ());
    glwin_->place (ax.origin (), ay.origin ());

    glwin_->bind ();
  }
  else  // otherwise resize existing child window
  {
    glwin_->canvas ()->size (ax.span (), ay.span ());
    glwin_->place (ax.origin (), ay.origin ());

    glwin_->resize ();
  }

  //cerr << "end of GEContext::allocate" << endl;

} // GEContext::allocate



// unbind
// delete glx window managed by gecontext
// when unmapping a window containing a gecontext do (in order):
// win->unmap ();  gecontext->unbind ();  win->unbind ();

void GEContext::unbind ()
{
  delete glwin_;  // is created again in next allocate
  glwin_ = 0;
  origcur_ = 0;
}



void GEContext::redraw (int)  /* hints ignored */
{
  // user request to redraw whole window
  if (glwin_)
    glwin_->rep ()->canvas_->damage_all ();
  // damage whole canvas of the glwindow
}


const char* GEContext::implementation ()
{
  return ("IrisGL");
}


int GEContext::implementationHints ()
{
  return impl_notextures;  // currently no texture support with IrisGL
}


int GEContext::graphicsSupport (Display* d)
{
  // the following code is just a guess, whether the display is set to
  // an SGI machine; it checks, whether there is any X-Extension
  // beginning with "SGI", which should work right in 99%
  // other approaches like GLXgetconfig, getgdesc crash in dglopen
  // anyway, we favour OpenGL over IrisGL

  if (!d)
    d = Session::instance ()->default_display ();
  XDisplay* dpy = d->rep ()->display_;

  int n = 0;
  char** extlist = XListExtensions (dpy, &n);
  int flag = 0;

  if (!extlist)  // pathologic
    return 0;

  while (n--)
  {
    if (!strncmp (extlist [n], "SGI", 3))
    { flag = 1;
      break;
    }
  }
  XFreeExtensionList (extlist);

  return flag;
}
