//<file>
//
// File:        oglcontext.C - glyph for 3D drawing
//              implementation of glcontext for the
//              MESA (OpenGL workalike) graphics library
//
// Created:     10 Aug 94   Michael Pichler
//
// Changed:     12 Dec 95   Michael Pichler
//
//
//</file>

//<copyright>
//
// Copyright (c) 1994,95
// Institute for Information Processing and Computer Supported New Media (IICM),
// Graz University of Technology, Austria.
// original version (GL-based GLContext):
// Copyright (c) 1991 Stanford University
// Copyright (c) 1991 Silicon Graphics, Inc.
//
//</copyright>

/*
 * Permission to use, copy, modify, distribute, and sell this software and 
 * its documentation for any purpose is hereby granted without fee, provided
 * that (i) the above copyright notices and this permission notice appear in
 * all copies of the software and related documentation, and (ii) the names of
 * Stanford and Silicon Graphics may not be used in any advertising or
 * publicity relating to the software without the specific, prior written
 * permission of Stanford and Silicon Graphics.
 * 
 * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, 
 * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY 
 * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.  
 *
 * IN NO EVENT SHALL STANFORD OR SILICON GRAPHICS BE LIABLE FOR
 * ANY SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND,
 * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
 * WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF 
 * LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE 
 * OF THIS SOFTWARE.
 */
/*
 * Copyright (c) 1993-1995 by the Institute for Information Processing
 * and Computer Supported New Media (IICM), Graz University of
 * Technology, Austria.
 * 
 * Permission to use, copy, and distribute this software and its
 * documentation for any purpose is hereby granted without fee, provided
 * that (i) the above copyright notice and this permission notice appear
 * in all copies of the software and related documentation and (ii) no
 * charge of any kind is made for the software.
 * 
 * This software is provided "as-is" and without warranty of any kind,
 * express, implied or otherwise, including without limitation, any
 * warranty of merchantability or fitness for a particular purpose.
 * 
 * In no event shall the Institute for Information Processing and
 * Computer Supported New Media (IICM), Graz University of Technology, be
 * liable for any special, indirect or consequential damages or any
 * damages whatsoever resulting from loss of use, data or profits,
 * whether in an action of contract, negligence or other tortious action,
 * arising out of or in connection with the use or performance of this
 * software.
 */

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

#ifdef GE3DTEXT
#include <ge3d/ge3d.h>
#include <InterViews/font.h>
#include <IV-X11/xfont.h>
#endif

#include <InterViews/canvas.h>
#include <InterViews/display.h>
#include <InterViews/session.h>
#include <InterViews/style.h>
#include <InterViews/window.h>

#include <hyperg/hyperg/verbose.h>

#include <IV-X11/Xlib.h>
#include <IV-X11/xcanvas.h>
#include <IV-X11/xdisplay.h>
#include <IV-X11/xwindow.h>

#include <IV-X11/Xdefs.h>      /* Display, Font of X11 */
#include <GL/xmesa.h>
#include <GL/glu.h>
#include <IV-X11/Xundefs.h>  /* get back to IV's names */

#include <OS/string.h>

#include <iostream.h>
#include <stdlib.h>
#include <string.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 ();

    void shading (int flag)
    { shading_ = flag; }

  private:
    Window* parent_;
    XWindow overlay_xwin_;
    int double_buffered_;
    int requestoverlay_;
    int shading_;
    XMesaContext pixcon_;               // mesa context with X-pixmap
    XMesaContext imgcon_;               // mesa context with X-image
};



GLWindow::GLWindow (Glyph* g, Window* parent, int overlay)
: Window(g)
{
  parent_ = parent;
  requestoverlay_ = overlay;
  shading_ = 1;
  overlay_xwin_ = 0;
  pixcon_ = 0;
  imgcon_ = 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!
  // Mesa: always indirect rendering
  style (s);
}


GLWindow::~GLWindow()
{
  if (pixcon_)
    XMesaDestroyContext (pixcon_);
  if (imgcon_)
    XMesaDestroyContext (imgcon_);
}


// no GLWindow::draw


void GLWindow::place (Coord x, Coord y)
{
  DEBUGNL ("GLWindow::place");

  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 ();
  if (wrep->xwindow_ != WindowRep::unbound)
    XMoveWindow (dis->rep()->display_, wrep->xwindow_, wrep->xpos_, wrep->ypos_);

  DEBUGNL ("finished GLWindow::place");
}



void GLWindow::bind ()
{
  DEBUGNL ("beginning GLWindow::bind (Mesa)");

  DisplayRep* drep = display ()->rep ();
  XDisplay* xdpy = drep->display_;

  // create a window

  WindowRep* w = rep ();
  Canvas* c = w->canvas_;
  WindowTable* t = drep->wtable_;
  XWindow xw = w->xwindow_;

  WindowVisual* wvis = drep->default_visual_;
  Visual* vi_sual = wvis->visual ();
  int dep_th = wvis->depth ();

  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;

  DEBUGNL ("creating X window ...");

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

  DEBUGNL ("... X window created");

  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_;

  static XMesaContext firstmesacontext = 0;  // to share display lists

  // optionally pixmap or ximage context only (for performance tests)
  int backbuffers = 3;  // both (auto)
  String backbuftype;
  if (style ()->find_attribute ("mesabackbuf", backbuftype))
  { if (strstr (backbuftype.string (), "pixmap"))
      backbuffers = 1;  // Xpixmap only
    else if (strstr (backbuftype.string (), "image"))
      backbuffers = 2;  // Ximage only
  }

  DEBUGNL ("Mesa backbuffer (1 = pixmap, 2 = ximage, 3 = both): " << backbuffers);

#if 0
  // old MesaCreateContext (up to 1.1.2(?)): XWindow argument
  xmscon_ = XMesaCreateContext (
    xdpy, xw, 1 /* rgb */,
    double_buffered_,
    backimage_ /* 1: XImage, 0: pixmap */
  );
  if (!xmscon_)
    cerr << "3D context. fatal: XMesaCreateContext failed." << endl;
#else
  // new MesaCreateContext (from 1.1.5(?)): XVisualInfo argument, new XMesaBindWindow
  XVisualInfo xvisinfo;
  // assert: XMesaCreateContext only access only visual and depth fields of visinfo
  xvisinfo.visual = vi_sual;
  xvisinfo.depth = dep_th;

  if (backbuffers & 1)  // pixmap
  {
    pixcon_ = XMesaCreateContext (
      xdpy, &xvisinfo, 1, // rgb mode
      double_buffered_, 0, // pixmap
      firstmesacontext  // for sharing display lists
    );
    if (!firstmesacontext)
      firstmesacontext = pixcon_;
  }

  if (backbuffers & 2)  // ximage
  {
    imgcon_ = XMesaCreateContext (
      xdpy, &xvisinfo, 1, // rgb mode
      double_buffered_, 1, // ximage
      firstmesacontext  // for sharing display lists
    );
    if (!firstmesacontext)
      firstmesacontext = imgcon_;
  }

  if (!pixcon_ && !imgcon_)
    cerr << "3D context. fatal: XMesaCreateContext failed." << endl;
  if ((pixcon_ && !XMesaBindWindow (pixcon_, xw)) || (imgcon_ && !XMesaBindWindow (imgcon_, xw)))
    cerr << "3D context. fatal: XMesaBindWindow failed." << endl;
#endif

  XMapRaised (drep->display_, xw);

  DEBUGNL ("finished GLWindow::bind");

} // GLWindow::bind (MESA)



void GLWindow::repair ()
{
//cerr << "GLWindow::repair () entered" << endl;

  WindowRep* wrep = rep ();
  Canvas* c = wrep->canvas_;
  CanvasRep* can = c->rep();

  if (can->damaged_)
  {
    // XDisplay* xdpy = wrep->display_->rep ()->display_;

    if (shading_)
    { XMesaMakeCurrent (imgcon_ ? imgcon_ : pixcon_);
      // cerr << "using imgcon (shading)" << endl;
    }
    else
    { XMesaMakeCurrent (pixcon_ ? pixcon_ : imgcon_);
      // cerr << "using pixcon (wire)" << endl;
    }

    // some OpenGL libraries automatically reshape the viewport, others don't
    // glViewport (0, 0, c->pwidth () - 1 , c->pheight () - 1);  // -1 for Mesa 1.2.1 (bugfix)!!
    glViewport (0, 0, c->pwidth (), c->pheight ());
    // cerr << "test: smaller viewport!!!" << endl;
    // glViewport (10, 10, 500, 300);

/*
    // GL: 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_)
      XMesaSwapBuffers ();
    else
      glFlush ();

    can->clear_damage();
  }

//cerr << "GLWindow::repair () done" << endl;

} // repair


void GLWindow::activateOverlay (int /*clearing*/)
{
}


void GLWindow::deactivateOverlay ()
{
}


#ifdef GE3DTEXT
// ge3d_text

void ge3d_text (float x, float y, float z, const char* str)
{
// MESA library (1.1.1) does not yet support glXUseXFont
// TODO: (current Mesa library 1.1.4(?) does)
#ifndef MESA
  static int fontlistbase = -1;

//cerr << "writing '" << str << "' at position (" << x << ", " << y << ", " << z << ")." << endl;

  if (fontlistbase < 0)
  {
    const Font* font = Font::lookup ("fixed");
    if (!font)
    { fontlistbase = 0;
      return;
    }
    Resource::ref (font);
    FontRep* rep = font->rep (Session::instance ()->default_display ());

    // supported characters: ' ' (0x20) to DEL (0x7f), i.e. 96 chars
    fontlistbase = glGenLists (96);
    if (fontlistbase)
      glXUseXFont (rep->font_->fid, 0x20, 96, fontlistbase);

    Resource::unref (font);
  }

  if (fontlistbase)  // assuming correct characters
  {
    const char* chr = str;
    int base2 = fontlistbase - 0x20;

    glRasterPos3f (x, y, z);
    while (*chr)
      glCallList (*chr++ + base2);  // *chr++ - 0x20 + fontlistbase
  }
#endif
}


// ge3dText

void ge3dText (const point3D* p, const char* str)
{
  ge3d_text (p->x, p->y, p->z, str);
}
#endif


// 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 0;  // not yet ported; will depend on available visuals
}


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
{
  DEBUGNL ("GEContext::allocate");

  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 ();
  }

  DEBUGNL ("end of GEContext::allocate");

} // 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 ()
{
  // not necessary with modified unbind of xwindow.c
  delete glwin_;  // is created again in next allocate
  glwin_ = 0;
  origcur_ = 0;
}



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


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


int GEContext::implementationHints ()
{
  return impl_slow;  // no HW acceleration
}


int GEContext::graphicsSupport (Display*)
{
  return 1;  // every Xserver allows MESA output!
}
