/*

Copyright (C) 1998, 1999, 2000, 2001, 2002, 2004, 2005 Matthew P. Hodges
This file is part of XMakemol.

XMakemol is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.

XMakemol is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with XMakemol; see the file COPYING.  If not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

*/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <math.h>
#include <stdio.h>
#include <stdlib.h>

#include <Xm/Xm.h>

#include "defs.h"
#include "globals.h"

/* Function prototypes */

void canvas_cb (Widget, XtPointer, XtPointer);
void echo_to_message_area(char *);
double mod_of_vec (double *);
void rotate_atoms (double *, double, Boolean, Boolean);
void echo_to_message_area (char *);
int select_atom_internal (Boolean);
void store_mouse_coords (void);
void translate_atoms (double *);

/* When mouse_motion = 1, that means (for the purpose of redrawing)
   that the system is either being rotated or translated; this is
   used when View->Outline is selected */

static Boolean mouse_motion = 0;
static Position mouse_coord[2];

#define XY_MOUSE_SENSITIVITY 100;
#define Z_MOUSE_SENSITIVITY 10;

XButtonEvent *bevent;

enum mouse_translations
{
  ONE_DOWN,
  ONE_MOTION,
  ONE_UP,
  TWO_DOWN,
  TWO_MOTION,
  TWO_UP,
  THREE_DOWN,
  THREE_MOTION,
  THREE_UP,
  CTRL_ONE_DOWN,
  CTRL_ONE_MOTION,
  CTRL_ONE_UP,
  CTRL_TWO_DOWN,
  CTRL_TWO_MOTION,
  CTRL_TWO_UP,
  CTRL_THREE_DOWN,
  CTRL_THREE_MOTION,
  CTRL_THREE_UP,
  SHIFT_ONE_DOWN,
  SHIFT_ONE_MOTION,
  SHIFT_ONE_UP,
  SHIFT_TWO_DOWN,
  SHIFT_TWO_MOTION,
  SHIFT_TWO_UP,
  SHIFT_THREE_DOWN,
  SHIFT_THREE_MOTION,
  SHIFT_THREE_UP
} mouse_translation;

void
track_cb(Widget widget, XtPointer client_data, XtPointer call_data)
{
  
  void change_frame(int,Boolean,Boolean);
  void invert_coords(void);
  void reset_orientation(void);
  void reset_position(void);
  void reflect_x(void);
  void reflect_y(void);
  void reflect_z(void);

  int item_no = (int) client_data;
  
  switch(item_no){
  case 0:
    echo_to_message_area("Track rotations about local COM");
    rotate_about = ROTATE_ABOUT_LOCAL_COM;
    break;
  case 1:
    echo_to_message_area("Track rotations about the origin");
    rotate_about = ROTATE_ABOUT_ORIGIN;
    break;
  case 2:
    redraw=1;
    change_frame(frame_no,False,True);
    echo_to_message_area("Centre atoms");
    break;
  case 3:
    redraw=1;
    reset_orientation();
    echo_to_message_area("Resetting orientation");
    break;
  case 4:
    redraw=1;
    reset_position();
    echo_to_message_area("Resetting position");
    break;
  case 5:
    reflect_x();
    atoms_sorted=0;             /* Depths may have changed */
    change_frame(frame_no,False,False);
    echo_to_message_area("x coordinates reflected");
    break;
  case 6:
    reflect_y();
    atoms_sorted=0;             /* Depths may have changed */
    change_frame(frame_no,False,False);
    echo_to_message_area("y coordinates reflected");
    break;
  case 7:
    reflect_z();
    atoms_sorted=0;             /* Depths may have changed */
    change_frame(frame_no,False,False);
    echo_to_message_area("z coordinates reflected");
    break;
  case 8:
    invert_coords();
    atoms_sorted=0;             /* Depths may have changed */
    change_frame(frame_no,False,False);
    echo_to_message_area("Coordinates inverted");
    break;
  }
  
}


void
track(Widget widget, XEvent *event, String *args, int *num_args)
{

  void motion_start (void);
  void motion_stop (void);

  void xy_rotation (void);      /* Rotate about vector in XY plane */
  void z_rotation (void);       /* Rotate about Z */
  void select_atom_for_measure (void);

  void xy_translation (void);   /* Translate in XY plane */
  void z_translation (void);    /* Translate along Z */
  void select_atom_for_edit (void);

  void region_set_start (XButtonEvent *);
  void region_set_end (XButtonEvent *);

  void show_invisible_atoms (void);
  void hide_invisible_atoms (void);
  void select_atom_for_invis (void);
  
  bevent = (XButtonEvent *) event;

  /* aro - don't do translations if file is not loaded yet */
  if(strcmp(current_file_name, "") != 0)
    {
        
      if(*num_args != 1)
        {
          XtError ("Wrong number of args!");
        }

      mouse_translation = (enum mouse_translations) atoi (args[0]);

      switch (mouse_translation)
        {
        case ONE_DOWN:
          motion_start ();
          break;
        case ONE_MOTION:
          xy_rotation ();
          break;
        case ONE_UP:
          motion_stop ();
          break;
        case TWO_DOWN:
          motion_start ();
          break;
        case TWO_MOTION:
          z_rotation ();
          break;
        case TWO_UP:
          motion_stop ();
          break;
        case THREE_DOWN:
          select_atom_for_measure ();
          break;
        case THREE_MOTION:
          break;
        case THREE_UP:
          break;
        case CTRL_ONE_DOWN:
          motion_start ();
          break;
        case CTRL_ONE_MOTION:
          xy_translation ();
          break;
        case CTRL_ONE_UP:
          motion_stop ();
          break;
        case CTRL_TWO_DOWN:
          motion_start ();
          break;
        case CTRL_TWO_MOTION:
          z_translation ();
          break;
        case CTRL_TWO_UP:
          motion_stop ();
          break;
        case CTRL_THREE_DOWN:
          select_atom_for_edit ();
          break;
        case CTRL_THREE_MOTION:
          break;
        case CTRL_THREE_UP:
          break;
        case SHIFT_ONE_DOWN:
          region_set_start (bevent);
          break;
        case SHIFT_ONE_MOTION:
          region_set_end (bevent);
          break;
        case SHIFT_ONE_UP:
          break;
        case SHIFT_TWO_DOWN:
          show_invisible_atoms ();
          break;
        case SHIFT_TWO_MOTION:
          break;
        case SHIFT_TWO_UP:
          hide_invisible_atoms ();
          break;
        case SHIFT_THREE_DOWN:
          select_atom_for_invis ();
          break;
        case SHIFT_THREE_MOTION:
          break;
        case SHIFT_THREE_UP:
          break;
        }
    }
}


void
store_mouse_coords (void)
{
  /* Store the mouse coordinates */

  mouse_coord[0] = bevent->x;
  mouse_coord[1] = bevent->y;
}


void
motion_start (void)
{
  store_mouse_coords ();

  mouse_motion = 1;
  redraw = 1;
  canvas_cb (canvas, NULL, NULL);
}


void
motion_stop (void)
{
  mouse_motion = 0;
  redraw = 1;
  canvas_cb (canvas, NULL, NULL);
}


void
xy_rotation (void)
{
  int i;
  double axis[3], mod, phi;

  /* Determine the axis to rotate about */

  axis[0] = (double) (mouse_coord[1] - bevent->y);
  axis[1] = (double) (mouse_coord[0] - bevent->x);
  axis[2] = 0;

  mod = mod_of_vec (axis);
  if (mod == 0) return;

  for (i = 0; i < 2; i++)       /* axis[2] = 0; ignore */
    {
      axis[i] /= mod;
    }

  /* Determine the amount of rotation; full canvas_width -> 180 degree
     rotation */

  phi = (double) (PI  * mod / canvas_width);

  atoms_sorted = 0;
  redraw = 1;

  rotate_atoms (axis, phi, 1, 1);

  store_mouse_coords ();
}


void
z_rotation (void)
{
  double axis[3], vector[3], mod, phi;

  /* Determine the axis to rotate about */

  vector[0] = (double) (mouse_coord[0] - bevent->x);
  vector[1] = (double) (mouse_coord[1] - bevent->y);
  vector[2] = 0;

  mod = mod_of_vec (vector);
  if (mod == 0) return;

  axis[0] = 0;
  axis[1] = 0;
  axis[2] = (vector[0] > 0) ? 1 : -1; /* Direction depends on X motion */

  /* Determine the amount of rotation; full canvas_width -> 180 degree
     rotation */

  phi = (double) (PI  * mod / canvas_width);

  atoms_sorted = 0;
  redraw = 1;

  rotate_atoms (axis, phi, 1, 1);

  store_mouse_coords ();
}


void
select_atom_for_measure (void)
{
  void update_selected(int);
  void clear_message_area (void);

  char message[64];
  int closest;

  if (no_atoms == 0) return;

  /* Here, 1 means ignore invisible atoms in the selection process */

  closest = select_atom_internal (1);
  update_selected (closest);

  if (atoms[closest].sel == 1)
    {
      sprintf (message, "Atom %d selected: x = %9.4f, y = %9.4f, z = %9.4f",
               closest + 1,
               atoms[closest].x,
               atoms[closest].y,
               atoms[closest].z);

      echo_to_message_area (message);
    }
  else
    {
      clear_message_area ();
    }
}


void xy_translation (void)
{

  int i;
  double vector[3];

  vector[0] = (double) (bevent->x - mouse_coord[0]);
  vector[1] = (double) -(bevent->y - mouse_coord[1]); /* Y is down (X11) */
  vector[2] = 0;

  for (i = 0; i < 2; i++)       /* vector[2] = 0; ignore */
    {
      /* sensitivity should be customizable */
      vector[i] /= XY_MOUSE_SENSITIVITY;
    }

  translate_atoms (vector);

  store_mouse_coords ();

}


void z_translation (void)
{

  double vector[3];

  vector[0] = 0;
  vector[1] = 0;
  vector[2] = (double) (bevent-> x - mouse_coord[0]);

  /* sensitivity should be customizable */
  vector[2] /= Z_MOUSE_SENSITIVITY;

  translate_atoms (vector);

  store_mouse_coords ();

}


void
select_atom_for_edit (void)
{
  int closest;

  if (no_atoms == 0) return;

  if (edit_posn_dialog == NULL)
    {
      echo_to_message_area
        ("Edit selections only allowed when Edit->Positions is open");
      return;
    }
  /* Here, 1 means do ignore invisible atoms in the selection
     process */

  closest = select_atom_internal (1);

  if (closest == -1) return;
  
  if (atoms[closest].edit == 1)
    {
      atoms[closest].edit = 0;
    }
  else
    {
      atoms[closest].edit = 1;
    }
  
  redraw = 1;
  canvas_cb(canvas, NULL, NULL);
      
}


void show_invisible_atoms (void)
{
  view_ghost_atoms = 1;
  redraw = 1;
  canvas_cb (canvas, NULL, NULL);
  view_ghost_atoms = 0;
}


void hide_invisible_atoms (void)
{
  view_ghost_atoms = 0;
  redraw = 1;
  canvas_cb (canvas, NULL, NULL);
}


void
select_atom_for_invis (void)
{
  
  int count_visible_atoms (void);
  void update_selected(int);
  struct frame * get_selected_frame (void);
  void update_bbox (void);

  int closest, no_visible_atoms;

  struct frame *this_frame;

  if (no_atoms == 0) return;

  /* Here, 0 means don't ignore invisible atoms in the selection
     process */

  closest = select_atom_internal (0);

  if (closest == -1) return;

  this_frame = get_selected_frame ();

  if (atoms[closest].visi == 1)
    {
      atoms[closest].visi = 0;
      this_frame->atom[closest].visi = 0;
    }
  else
    {
      atoms[closest].visi = 1;
      this_frame->atom[closest].visi = 1;
    }

  no_visible_atoms = count_visible_atoms ();
  update_bbox ();
  redraw = 1;
  canvas_cb(canvas, NULL, NULL);
      
}

/* Function to determine whether we are currently rotating or
   translating */

Boolean
mouse_motion_p (void)
{
  return (mouse_motion);
}


/* If ignore_invisible is true, we don't care if the atoms are invisible
 or not */

int
select_atom_internal (Boolean ignore_invisible)
{

  void convert_to_canvas_coords(double *, double *, Boolean);
  
  int i, closest, first_value_set = 0;

  double a[3], b[2], dist, dist_keep = 0;
  
  closest = -1;
  
  for(i = 0; i < no_atoms; i++)
    {
      if((atoms[i].visi == True) || (ignore_invisible == False))
        {
          double x, y;

          a[0] = atoms[i].x;
          a[1] = atoms[i].y;
          a[2] = atoms[i].z;

          convert_to_canvas_coords(a, b, 0);

          x = (double) (b[0] - bevent->x);
          y = (double) (b[1] - bevent->y);

          dist = (x * x) + (y * y);

          if(first_value_set == 1)
            {
              if(dist < dist_keep)
                {
                  dist_keep = dist;
                  closest = i;
                }
            }
          else
            {
              dist_keep = dist; /* Set for the first atom */
              closest = i;
              first_value_set = 1;
            }
          
        }
    }
  
  return (closest);
  
}


void
update_selected (int closest)
{

  void update_lengths_dialog(Boolean);

  static int i,j,no_selected;

  Boolean deselected;

  if(sel_init == 0){
    for(i=0;i<4;i++){
      selected[i]=-1; /* set initial values */
    }
    sel_init=1;
  }

  /* If selected atom is invisible, deselect */

  for(i = 0; i < 4; i++)
    {
      if(selected[i] != -1)
        {
          if(atoms[selected[i]].visi == 0)
            {
              selected[i]=-1;
            }
        }
    }
  

  /* find out how many atoms selected */

  no_selected=0;
  for(i=0;i<4;i++){
    if(selected[i]==-1){
      break;
    }
    no_selected++;
  }

  /* if closest is already selected, deselect and pack
     selected */

  deselected=0;

  for(i=0;i<4;i++){
    if(selected[i]==closest){
      /* deselect and pack */
      selected[i]=-1;
      for(j=i;j<3;j++){
        selected[j]=selected[j+1];
      }
      selected[3]=-1;
      deselected=1;
      break;
    }
  }
 
  /* if four atoms already selected, pop the first off the
     top of the stack (unless already deselected one of them) */

  if(!deselected){
    if(no_selected==4){
      for(i=0;i<3;i++){
        selected[i]=selected[i+1];
      }
      selected[3]=closest;
    }else{
      selected[no_selected]=closest;
    }
  }

  for(i=0;i<no_atoms;i++){
    atoms[i].sel=0;
  }
  
  for(i=0;i<4;i++){
    if(selected[i]==-1){
      break;
    }
    atoms[selected[i]].sel=1;
  }

  update_lengths_dialog(True);
  
  /* update visual */

  redraw=1;
  canvas_cb(canvas,NULL,NULL);

}

/* If called with sel_changes_flag == True, then the selections have
   changed and we need to calculate the lengths, angles and torsion
   angle again. Otherwise, we just update the atomic coordinates
*/

void
update_lengths_dialog(Boolean sel_changed_flag)
{

  double get_angle(int, int, int);
  double get_length(int, int);
  double get_torsion(int, int, int, int);

  int i,j,k,w,seli,selj,selk,sell;

  char string[100];
  char *labels = "ABCD";

  double distance,angle,torsion;

  XmString label;

  w=0;

  if(meas_dialog != NULL){
    for(i=0;i<4;i++){
      seli=selected[i];
      if(seli != -1){
        sprintf(string,"%2c  :%9.4f %9.4f %9.4f   %-2s    %d",
                labels[i],atoms[seli].x,atoms[seli].y,atoms[seli].z,
                atoms[seli].label, seli + 1);
      }else{
        sprintf(string,"%2c  :                                       ",
                labels[i]);
      }
      
      label=XmStringCreateLocalized(string);
      XtVaSetValues(meas_label_w[w], XmNlabelString, label, NULL);
      w++;
    }

    if(sel_changed_flag){

      for(i=0;i<3;i++){
        seli=selected[i];
        for(j=i+1;j<4;j++){
          selj=selected[j];
          if((seli != -1) && (selj != -1)){
            distance=get_length(seli,selj);
            
            sprintf(string,"%c-%c  :  %10.4f",labels[i],labels[j],distance);
            label=XmStringCreateLocalized(string);
            
          }else{
            sprintf(string,"%c-%c  :            ",labels[i],labels[j]);
            label=XmStringCreateLocalized(string);
          }
          
          XtVaSetValues(meas_label_w[w], XmNlabelString, label, NULL);
          w++;        
        }
      }
      
      for(i=0;i<2;i++){
        seli=selected[i];
        for(j=i+1;j<3;j++){
          selj=selected[j];
          for(k=j+1;k<4;k++){
            selk=selected[k];
            if((seli != -1) && (selj != -1) && (selk != -1)){
              angle=get_angle(seli,selj,selk);
              
              sprintf(string,"  %c-%c-%c  :  %10.4f",
                      labels[i],labels[j],labels[k],angle);
            }else{
              sprintf(string,"  %c-%c-%c  :            ",
                      labels[i],labels[j],labels[k]);
            }
            
            label=XmStringCreateLocalized(string);
            XtVaSetValues(meas_label_w[w], XmNlabelString, label, NULL);
            w++;
          }   
        }
      }
      
      seli=selected[0];
      selj=selected[1];
      selk=selected[2];
      sell=selected[3];
      
      if((sell != -1)){ /* selected -> torsion available */
        torsion=get_torsion(seli,selj,selk,sell);
        sprintf(string, "A-B-C-D  :  %10.4f",torsion);
      }else{
        sprintf(string, "A-B-C-D  :            ");
      }
      
      label=XmStringCreateLocalized(string);
      XtVaSetValues(meas_label_w[w], XmNlabelString, label, NULL);
    
      
    }
    
    XmStringFree(label);
    
  }
}


void
reset_orientation(void)
{

  void change_frame(int, Boolean, Boolean);

  int i,j;

  for(i=0;i<3;i++){
    for(j=0;j<3;j++){
      if(i==j){
        global_matrix[i][j]=1;
      }else{
        global_matrix[i][j]=0;
      }
    }
  }
  
  /* Atoms will no longer be sorted */

  atoms_sorted=0;

  change_frame(frame_no,False,False);

}


void
reset_position(void)
{

  void change_frame(int, Boolean, Boolean);

  int i;

  for(i=0;i<3;i++){
    global_vector[i]=0;
  }

  change_frame(frame_no,False, False);

}


void
reflect_x (void)
{

  struct frame * get_first_frame ();

  int i;

  struct frame *this_frame;

  this_frame = get_first_frame ();

  while (this_frame != NULL)
    {
      for (i = 0; i < this_frame->no_atoms; i++)
        {
          this_frame->atom[i].x *= -1.0;
        }

      if (this_frame->bbox_available)
        {
          this_frame->bbox[0][0] *= -1.0;
          this_frame->bbox[0][1] *= -1.0;
        }

      this_frame = this_frame->next;
    }
}


void
reflect_y (void)
{

  struct frame * get_first_frame ();

  int i;

  struct frame *this_frame;

  this_frame = get_first_frame ();

  while (this_frame != NULL)
    {
      for (i = 0; i < this_frame->no_atoms; i++)
        {
          this_frame->atom[i].y *= -1.0;
        }

      if (this_frame->bbox_available)
        {
          this_frame->bbox[1][0] *= -1.0;
          this_frame->bbox[1][1] *= -1.0;
        }

      this_frame = this_frame->next;
    }

}


void
reflect_z (void)
{

  struct frame * get_first_frame ();

  int i;

  struct frame *this_frame;

  this_frame = get_first_frame ();

  while (this_frame != NULL)
    {
      for (i = 0; i < this_frame->no_atoms; i++)
        {
          this_frame->atom[i].z *= -1.0;
        }

      if (this_frame->bbox_available)
        {
          this_frame->bbox[2][0] *= -1.0;
          this_frame->bbox[2][1] *= -1.0;
        }

      this_frame = this_frame->next;
    }

}


void
invert_coords (void)
{

  struct frame * get_first_frame ();

  int i;

  struct frame *this_frame;

  this_frame = get_first_frame ();

  while (this_frame != NULL)
    {
      for (i = 0; i < this_frame->no_atoms; i++)
        {
          this_frame->atom[i].x *= -1.0;
          this_frame->atom[i].y *= -1.0;
          this_frame->atom[i].z *= -1.0;
        }

      if (this_frame->bbox_available)
        {
          this_frame->bbox[0][0] *= -1.0;
          this_frame->bbox[0][1] *= -1.0;
          this_frame->bbox[1][0] *= -1.0;
          this_frame->bbox[1][1] *= -1.0;
          this_frame->bbox[2][0] *= -1.0;
          this_frame->bbox[2][1] *= -1.0;
        }

      this_frame = this_frame->next;
    }

}


syntax highlighted by Code2HTML, v. 0.9.1