/*******************************************************************************
*                                                                              *
*                                   Viewmol                                    *
*                                                                              *
*                               D R A W E L . C                                *
*                                                                              *
*                 Copyright (c) Joerg-R. Hill, October 2003                    *
*                                                                              *
********************************************************************************
*
* $Id: drawel.c,v 1.6 2003/11/07 10:59:40 jrh Exp $
* $Log: drawel.c,v $
* Revision 1.6  2003/11/07 10:59:40  jrh
* Release 2.4
*
* Revision 1.5  2000/12/10 15:05:02  jrh
* Release 2.3
*
* Revision 1.4  1999/05/24 01:25:12  jrh
* Release 2.2.1
*
* Revision 1.3  1999/02/07 21:47:09  jrh
* Release 2.2
*
* Revision 1.2  1998/01/26 00:47:29  jrh
* Release 2.1
*
* Revision 1.1  1996/12/10  18:40:27  jrh
* Initial revision
*
*/
#include<math.h>
#include<stdio.h>
#include<stdlib.h>
#include<X11/Intrinsic.h>
#include<X11/StringDefs.h>
#include<X11/keysym.h>
#include<Xm/Xm.h>
#include<GL/gl.h>
#include "viewmol.h"

void updateMOs(int);

extern double makeTics(double, double, double *, char *);
extern void setWindowColor(int, Pixel, const float*);
extern void (*drawBegin)(GLenum), (*drawEnd)(void), (*drawVertex2d)(double, double);
extern void (*drawString)(char *, double, double, double, double, GLuint, char);
extern void (*drawLineStipple)(GLint, GLushort);
extern void (*drawLineWidth)(GLfloat);
extern void (*drawDisable)(GLenum);
extern void printDialog(Widget, caddr_t, XmAnyCallbackStruct *);
extern void pixelToWorld(int, double *, double *);
extern void drawBackground(int, Pixel, double);
extern int StringWidth(XFontStruct *, char *);
extern int StringHeight(XFontStruct *);
extern void setMenuItem(int, int);
extern void fremem(void **);
extern void deleteGridObjects(void);
extern void redraw(int);
extern void annotateWavefunction(void);
extern char *getStringResource(Widget, char *);
extern void selectMolecule(Widget, caddr_t, XmToggleButtonCallbackStruct *);
extern int  makeAnnotation(int, int, float, float, float, int, const GLfloat *,
                           int, int, char *);
extern int existsGridObject(void);
extern void calcmo(void);

extern struct MOLECULE *molecules;
extern struct WINDOW windows[];
extern Widget topShell;
extern Pixel stdcol[9];
extern double denres, *grid;
extern int nmolecule, iwavef;
extern int unit, selectAtom, selectAtomAnnotation, automaticRecalculate;
extern int picking, swapBuffers;

void drawMODiagram(Widget w, caddr_t client_data, XmDrawingAreaCallbackStruct *data)
{
  char line[15], trans[15];
  char form[8], units[7];
  double scan[500];
  double eunit=1.0, e1h, e2h, e;
  double xpix, ypix, strl=0.0e0;
  double ticMark, ticSave, ticInc;
  double smax, shape, center, ehelp, fac;
  double off1, off2, off;
  const float red[4]   = {1.0, 0.0, 0.0, 0.0};
  const float green[4] = {0.0, 1.0, 0.0, 0.0};
  const float blue[4]  = {0.0, 0.0, 1.0, 0.0};
  const float white[4] = {1.0, 1.0, 1.0, 0.0};
  int renderMode, imol, draw, spin;
  register double l;
  register int i, j;

/* This subroutine draws a MO energy diagram */

/* Convert units from Hartree/particle */
  switch (unit)
  {
    case HARTREE: eunit=1.0;             /* to Hartree/particle */
                  strcpy(units, "Eh");
                  break;
    case KJ_MOL:  eunit=2625.5001;       /* to kJ/mol */
                  strcpy(units, "kJ/mol");
                  break;
    case EV:      eunit=27.211611;       /* to eV */
                  strcpy(units, "eV");
                  break;
    case CM:      eunit=4.3598149e6/(6.626176*2.99792458);   /* to 1/cm (E/hc) */
                  strcpy(units, "1/cm");
                  break;
  }
  imol=windows[MO].set;
  glGetIntegerv(GL_RENDER_MODE, &renderMode);
  if (renderMode == GL_RENDER)
    glXMakeCurrent(XtDisplay(windows[MO].widget), XtWindow(windows[MO].widget), windows[MO].context);

  if (!picking)
  {
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
  }
  pixelToWorld(MO, &xpix, &ypix);
  drawBackground(MO, windows[MO].background, (double)0.0);
  setWindowColor(FOREGROUND, windows[MO].foreground, windows[MO].foreground_rgb);
  if (molecules[imol].orbitals[0].spin != ALPHAANDBETA)
    off=ypix*(double)StringHeight(windows[MO].greekFont);
  else
    off=0.0;
  glOrtho(windows[MO].left, windows[MO].right, windows[MO].bottom-off,
          windows[MO].top, windows[MO].near, windows[MO].far);
  e1h=(windows[MO].bottom-off)*eunit;
  e2h=windows[MO].top*eunit;
  glShadeModel(GL_FLAT);

/* Calculate and draw tic marks */

  glPushName(imol);
  ticInc=makeTics(e1h, e2h, &ticMark, form);
  ticSave=ticMark;
  do
  {
    if (fabs(ticMark) < 1.0e-5) ticMark=0.0;
    sprintf(line, form, ticMark);
    l=(double)(StringWidth(windows[MO].font, line)+9.0);
    strl= strl > l ? strl : l;
    (*drawString)(line, 2.0*xpix, ticMark/eunit-5.0*ypix, 0.0, 0.0, windows[MO].GLfontId, 'l');
    ticMark+=ticInc;
  } while (ticMark < e2h);
  strl*=xpix;
  ticMark=ticSave;

  (*drawLineWidth)((GLfloat)1.);
  glEnable(GL_LINE_STIPPLE);                             /* Dotted lines */
  (*drawLineStipple)(1, 0x3333);
  (*drawBegin)(GL_LINES);
  (*drawVertex2d)(strl, 0.0);
  (*drawVertex2d)(1.0e0, 0.0);
  (*drawEnd)();
  (*drawDisable)(GL_LINE_STIPPLE);
  (*drawBegin)(GL_LINES);
  (*drawVertex2d)(strl, windows[MO].bottom-off);
  (*drawVertex2d)(strl, windows[MO].top);
  (*drawEnd)();

  do
  {
    (*drawBegin)(GL_LINES);
    (*drawVertex2d)(strl-5.0*xpix, ticMark/eunit);
    (*drawVertex2d)(strl+5.0*xpix, ticMark/eunit);
    (*drawEnd)();
    ticMark+=ticInc;
  } while (ticMark < e2h);
  (*drawString)(units, 2.0*xpix, (ticMark-5.0*ticInc/2.0)/eunit, 0.0, 0.0, windows[MO].GLfontId, 'l');
  /* Check for UHF calculation and label alpha and beta eigenvalues */
  if (molecules[imol].orbitals[0].spin != ALPHAANDBETA)
  {
    (*drawString)("a", 0.25+0.75*strl+5.0*xpix, windows[MO].bottom+5.0*ypix, 0.0, 0.0, windows[MO].GLgreekFontId, 'c');
    (*drawString)("b", 0.75+0.25*strl-5.0*xpix, windows[MO].bottom+5.0*ypix, 0.0, 0.0, windows[MO].GLgreekFontId, 'c');
  }

/* Draw energy levels */

  if (windows[MO].mode == DENSITY_OF_STATES)
  {
    if (molecules[imol].orbitals[0].spin == ALPHAANDBETA)
    {
      e=windows[MO].bottom;
      smax=0.0;
      for (i=0; i<500; i++)
      {
        scan[i]=0.0;
        for (j=0; j<molecules[imol].nbasfu; j++)
        {
          shape=e-molecules[imol].orbitals[j].energy;
          scan[i]+=exp(-shape*shape/denres);
        }
        smax= smax > scan[i] ? smax : scan[i];
        e+=(windows[MO].top-windows[MO].bottom)/500.0;
      }
      (*drawBegin)(GL_LINE_STRIP);
      (*drawVertex2d)(scan[0]*(1.0-strl)/smax+strl, windows[MO].bottom);
      for (i=1; i<500; i++)
        (*drawVertex2d)(scan[i]*(1.0-strl)/smax+strl, windows[MO].bottom+
                   (double)i*(windows[MO].top-windows[MO].bottom)/500.0);
      (*drawEnd)();
    }
    else
    {
      draw=TRUE;
      spin=ALPHA;
      setWindowColor(FOREGROUND, stdcol[BLUE], blue);
      while (draw)
      {
        e=windows[MO].bottom;
        smax=0.0;
        for (i=0; i<500; i++)
        {
          scan[i]=0.0;
          for (j=0; j<molecules[imol].nbasfu; j++)
          {
            if (molecules[imol].orbitals[j].spin == spin)
            {
              shape=e-molecules[imol].orbitals[j].energy;
              scan[i]+=exp(-shape*shape/denres);
            }
          }
          smax= smax > scan[i] ? smax : scan[i];
          e+=(windows[MO].top-windows[MO].bottom)/500.0;
        }
        (*drawBegin)(GL_LINE_STRIP);
        (*drawVertex2d)(scan[0]*(1.0-strl)/smax+strl, windows[MO].bottom);
        for (i=1; i<500; i++)
          (*drawVertex2d)(scan[i]*(1.0-strl)/smax+strl, windows[MO].bottom+
                     (double)i*(windows[MO].top-windows[MO].bottom)/500.0);
        (*drawEnd)();
        switch (spin)
        {
          case ALPHA: spin=BETA;
                      (*drawBegin)(GL_LINE_STRIP);
                      (*drawVertex2d)(0.25+0.75*strl, windows[MO].bottom+0.5*StringHeight(windows[MO].greekFont)*ypix);
                      (*drawVertex2d)(0.25, windows[MO].bottom+0.5*StringHeight(windows[MO].greekFont)*ypix);
                      (*drawEnd)();
                      setWindowColor(FOREGROUND, stdcol[GREEN], green);
                      break;
          case BETA:  draw=FALSE;
                      (*drawBegin)(GL_LINE_STRIP);
                      (*drawVertex2d)(0.75+0.25*strl-10.0*xpix, windows[MO].bottom+0.5*StringHeight(windows[MO].greekFont)*ypix);
                      (*drawVertex2d)(0.75-0.5*strl-10.0*xpix, windows[MO].bottom+0.5*StringHeight(windows[MO].greekFont)*ypix);
                      (*drawEnd)();
                      break;
        }
      }
    }
  }
  else
  {
    for (i=0; i<molecules[imol].nbasfu; i++)
    {
      if (i != molecules[imol].imo)
      {
        glPushName(i+1);
        (*drawBegin)(GL_LINES);
        if (molecules[imol].orbitals[i].spin == BETA)
          (*drawVertex2d)(0.5+0.5*strl+10.0*xpix, molecules[imol].orbitals[i].energy);
        else
          (*drawVertex2d)(strl+20.0*xpix, molecules[imol].orbitals[i].energy);
        if (molecules[imol].orbitals[i].spin == ALPHA)
          (*drawVertex2d)(0.5+0.5*strl-10.0*xpix, molecules[imol].orbitals[i].energy);
        else
          (*drawVertex2d)(1.0-20.0*xpix, molecules[imol].orbitals[i].energy);
        (*drawEnd)();
        glPopName();
      }
    }

/* Draw the selected energy level with symmetry and energy
   in a centered box */

    if (molecules[imol].imo != (-1))
    {
      setWindowColor(FOREGROUND, stdcol[RED], red);
      glPushName(molecules[imol].imo+1);
      (*drawBegin)(GL_LINES);
      if (molecules[imol].orbitals[molecules[imol].imo].spin == BETA)
        (*drawVertex2d)(0.5+0.5*strl+10.0*xpix, molecules[imol].orbitals[molecules[imol].imo].energy);
      else
        (*drawVertex2d)(strl+20.0*xpix, molecules[imol].orbitals[molecules[imol].imo].energy);
      if (molecules[imol].orbitals[molecules[imol].imo].spin == ALPHA)
        (*drawVertex2d)(0.5+0.5*strl-10.0*xpix, molecules[imol].orbitals[molecules[imol].imo].energy);
      else
        (*drawVertex2d)(1.0-20.0*xpix, molecules[imol].orbitals[molecules[imol].imo].energy);
      (*drawEnd)();
      glPopName();
      if (molecules[imol].orbitals[molecules[imol].imo].spin == ALPHA)
        center=0.25+0.75*strl+5.0*xpix;
      else if (molecules[imol].orbitals[molecules[imol].imo].spin == BETA)
        center=0.75+0.25*strl-5.0*xpix;
      else
        center=0.5+strl*0.5;
      if (molecules[imol].imosave != (-1) && molecules[imol].imosave != molecules[imol].imo)
      {
        ehelp=molecules[imol].orbitals[molecules[imol].imo].energy-molecules[imol].orbitals[molecules[imol].imosave].energy;
        (*drawBegin)(GL_LINES);
        (*drawVertex2d)(strl+20.0*xpix, molecules[imol].orbitals[molecules[imol].imosave].energy);
        (*drawVertex2d)(1.0-20.0*xpix, molecules[imol].orbitals[molecules[imol].imosave].energy);
        (*drawEnd)();
        setWindowColor(FOREGROUND, stdcol[BLUE], blue);
        (*drawBegin)(GL_LINES);
        (*drawVertex2d)(center, molecules[imol].orbitals[molecules[imol].imosave].energy);
        (*drawVertex2d)(center, molecules[imol].orbitals[molecules[imol].imo].energy);
        (*drawEnd)();
        if (molecules[imol].imosave < molecules[imol].imo)
          fac=-10.0;
        else
          fac=10.0;
        (*drawBegin)(GL_LINE_STRIP);
        (*drawVertex2d)(center-3.0*xpix, molecules[imol].orbitals[molecules[imol].imo].energy+fac*ypix);
        (*drawVertex2d)(center, molecules[imol].orbitals[molecules[imol].imo].energy);
        (*drawVertex2d)(center+3.0*xpix, molecules[imol].orbitals[molecules[imol].imo].energy+fac*ypix);
        (*drawEnd)();
        sprintf(line, "%.6f", eunit*ehelp);
        sprintf(trans, "%s -> %s", molecules[imol].orbitals[molecules[imol].imosave].symmetry,
                molecules[imol].orbitals[molecules[imol].imo].symmetry);
        ehelp=fabs(ehelp)/2.0+molecules[imol].orbitals[molecules[imol].imo < molecules[imol].imosave ? molecules[imol].imo : molecules[imol].imosave].energy+13.*ypix;
      }
      else
      {
        ehelp=molecules[imol].orbitals[molecules[imol].imo].energy;
        sprintf(line, "%.6f", eunit*molecules[imol].orbitals[molecules[imol].imo].energy);
        sprintf(trans, "%s", molecules[imol].orbitals[molecules[imol].imo].symmetry);
      }
      off1=(double)(StringWidth(windows[MO].font, trans)+4)/2.0;
      off2=(double)(StringWidth(windows[MO].font, line)+4)/2.0;
      off=xpix*(off1 > off2 ? off1 : off2);
      setWindowColor(FOREGROUND, stdcol[WHITE], white);
      if (ehelp-windows[MO].bottom < 27.0*ypix)
      {
        glRectd(center-off, ehelp+27.0*ypix, center+off, ehelp);
        setWindowColor(FOREGROUND, windows[MO].foreground, windows[MO].foreground_rgb);
        (*drawBegin)(GL_LINE_LOOP);
        (*drawVertex2d)(center-off, ehelp+27.0*ypix);
        (*drawVertex2d)(center-off, ehelp);
        (*drawVertex2d)(center+off, ehelp);
        (*drawVertex2d)(center+off, ehelp+27.0*ypix);
        (*drawEnd)();
        (*drawString)(trans, center-(off1-2.0)*xpix, ehelp+16.0*ypix, 0.0, 0.0, windows[MO].GLfontId, 'l');
        (*drawString)(line, center-(off2-2.0)*xpix, ehelp+3.0*ypix, 0.0, 0.0, windows[MO].GLfontId, 'l');
      }
      else
      {
        glRectd(center-off, ehelp-27.0*ypix, center+off, ehelp);
        setWindowColor(FOREGROUND, windows[MO].foreground, windows[MO].foreground_rgb);
        (*drawBegin)(GL_LINE_LOOP);
        (*drawVertex2d)(center-off, ehelp-27.0*ypix);
        (*drawVertex2d)(center-off, ehelp);
        (*drawVertex2d)(center+off, ehelp);
        (*drawVertex2d)(center+off, ehelp-27.0*ypix);
        (*drawEnd)();
        (*drawString)(trans, center-(off1-2.0)*xpix, ehelp-11.0*ypix, 0.0, 0.0, windows[MO].GLfontId, 'l');
        (*drawString)(line, center-(off2-2.0)*xpix, ehelp-24.0*ypix, 0.0, 0.0, windows[MO].GLfontId, 'l');
      }
    }
  }
  glPopName();
  if (swapBuffers) glXSwapBuffers(XtDisplay(windows[MO].widget), XtWindow(windows[MO].widget));
}

void quitMODiagram(Widget w, caddr_t client_data, XmAnyCallbackStruct *data)
{
  register int imol;

  if (windows[MO].widget == NULL) return;
  imol=windows[MO].set;
  deleteGridObjects();
  glXMakeCurrent(XtDisplay(windows[VIEWER].widget), XtWindow(windows[VIEWER].widget), windows[VIEWER].context);
  fremem((void **)&windows[MO].font);
  XtUnmanageChild(XtParent(windows[MO].widget));
  XtDestroyWidget(XtParent(windows[MO].widget));
  windows[MO].widget=0;
  setMenuItem(VIEWER_ENERGYLEVEL, True);
  if (nmolecule > 0)
  {
    molecules[imol].imo=(-1);
    molecules[imol].imosave=(-1);
    redraw(VIEWER);
  }
}

void setTransition(Widget w, caddr_t dummy, XmToggleButtonCallbackStruct *data)
{
  register int imol;

  imol=windows[MO].set;
  if (data->set)
    molecules[imol].imosave=molecules[imol].imo;
  else
    molecules[imol].imosave=(-1);
}

void MODiagramKeyAction(KeySym keysym)
{
  XmToggleButtonCallbackStruct data;
  struct MOLECULE *mol;
  int imoSave, spin;

  mol=&molecules[windows[MO].set];
  imoSave=mol->imo;
  switch (keysym)
  {
    case XK_Print: printDialog((Widget)0, (caddr_t)MO, (XmAnyCallbackStruct *)0);
                   break;
    case XK_Down:  if (mol->imo != -1)
	           {
                     spin=mol->orbitals[mol->imo].spin;
                     while (mol->orbitals[--mol->imo].spin != spin && mol->imo >= 0);
                     if (mol->imo < 0)
		     {
		       mol->imo=mol->nbasfu;
                       while (mol->orbitals[--mol->imo].spin != spin && mol->imo >= 0);
		     }
		   }
                   break;
    case XK_Up:    if (mol->imo != -1)
		   {
                     spin=mol->orbitals[mol->imo].spin;
                     while (mol->orbitals[++mol->imo].spin != spin && mol->imo < mol->nbasfu);
                     if (mol->imo >= mol->nbasfu)
		     {
		       mol->imo=-1;
                       while (mol->orbitals[++mol->imo].spin != spin && mol->imo < mol->nbasfu);
		     }
		   }
                   break;
    case XK_Left:  if (mol->imo != -1)
		   {
                     if (mol->orbitals[mol->imo].spin == BETA)
                       while (mol->orbitals[--mol->imo].spin == BETA && mol->imo >= 0);
		   }
                   break;
    case XK_Right: if (mol->imo != -1)
                   {
                     if (mol->orbitals[mol->imo].spin == ALPHA)
                       while (mol->orbitals[++mol->imo].spin == ALPHA && mol->imo < mol->nbasfu);
		   }
                   break;
    case XK_Tab:   if (nmolecule > 1)                                          
                   {                                                           
                     data.set=TRUE;                                            
                     do
                     {
                       windows[MO].set++;                                  
                       if (windows[MO].set >= nmolecule) windows[MO].set=0;
                     } while (molecules[windows[MO].set].orbitals == NULL);
                     (void)selectMolecule((Widget)0, (XtPointer)&windows[MO].selectMenu[windows[MO].set],
                                          &data);
                   }                   
                   break;
  }
  updateMOs(imoSave);
  annotateWavefunction();
}

void updateMOs(int imoSave)
{
  struct MOLECULE *mol;
  const GLfloat black[4] = {0.0, 0.0, 0.0, 0.0};
  char *word;

  mol=&molecules[windows[MO].set];
  if (automaticRecalculate)
  {
    switch (iwavef)
    {
      case BASIS_IN_MO:       word=getStringResource(topShell, "selectAtomTitle");
                              selectAtomAnnotation=makeAnnotation(selectAtomAnnotation,
                                                   CENTERED, 0.0, 1.0, 0.0, stdcol[BLACK],
                                                   black, 0, 0, word);
                              selectAtom=TRUE;
                              break;
      case MOLECULAR_ORBITAL: selectAtom=FALSE;
                              if (!existsGridObject()) calcmo();
                              break;
    }
  }
  if (mol->imo != imoSave && grid != NULL)
    fremem((void **)&grid);
  if (mol->orbitals[mol->imo].occupation == 0.0 && mol->needMoloch)
    setMenuItem(VIEWER_WAVEFUNCTION, False);
  else if (mol->basisset != NULL)
    setMenuItem(VIEWER_WAVEFUNCTION, True);
}
