/*
Copyright (C) 2003 by Sean David Fleming

sean@ivec.org

This program 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
of the License, or (at your option) any later version.

This program 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 this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

The GNU GPL can also be found at http://www.gnu.org
*/

#include <math.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>

#include "gdis.h"
#include "coords.h"
#include "model.h"
#include "edit.h"
#include "file.h"
#include "render.h"
#include "matrix.h"
#include "quaternion.h"
#include "measure.h"
#include "numeric.h"
#include "opengl.h"
#include "shortcuts.h"
#include "interface.h"
#include "dialog.h"

#define DEBUG 0
#define DEBUG2 0

/* global pak structures */
extern struct sysenv_pak sysenv;
extern struct elem_pak elements[];

GtkWidget *cf_spin;

struct frame_pak
{
gdouble latmat[9];
GSList *core_list;
GSList *shell_list;
};

/******************************/
/* free all pre-loaded frames */
/******************************/
void anim_free_all_frames(struct model_pak *model)
{
GSList *list;
struct frame_pak *frame;

g_assert(model != NULL);

for (list = model->frame_data_list ; list ; list=g_slist_next(list))
  {
  frame = list->data;

  free_slist(frame->core_list);
  free_slist(frame->shell_list);
  g_free(frame);
  }
g_slist_free(model->frame_data_list);
model->frame_data_list = NULL;
}

/**********************************/
/* attempt to pre-load all frames */
/**********************************/
gint anim_read_all_frames(struct model_pak *model)
{
gint i;
struct frame_pak *frame;
FILE *fp;

/* checks */
g_assert(model != NULL);
g_assert(model->animation == TRUE);

/* TODO - test is we have enough memory by using g_try_malloc() */

/* read in all frames and store relevant data */
  fp = fopen(model->filename, "r");
  if (!fp)
    {
    printf("Could not open source file.");
    return(1);
    }
  for (i=0 ; i<model->num_frames ; i++)
    {
/* NB: frames start at 1 */
    read_raw_frame(fp, i+1, model);
    matrix_lattice_init(model);

    frame = g_malloc(sizeof(struct frame_pak *));
    memcpy(frame->latmat, model->latmat, 9*sizeof(gdouble));
    frame->core_list = dup_core_list(model->cores);
    frame->shell_list = dup_shell_list(model->shels);

    model->frame_data_list = g_slist_prepend(model->frame_data_list, frame);
    }
  model->frame_data_list = g_slist_reverse(model->frame_data_list);

return(1);
}

/*************************************************/
/* store the current position of the file stream */
/*************************************************/
#define DEBUG_ADD_FRAME_OFFSET 0
gint add_frame_offset(FILE *fp, struct model_pak *data)
{
fpos_t *offset;

g_assert(fp != NULL);
g_assert(data != NULL);

offset = g_malloc(sizeof(fpos_t));
/* prepend & reverse? */
if (!fgetpos(fp, offset))
  data->frame_list = g_list_append(data->frame_list, offset); 
else
  {
  g_free(offset);
  return(1);
  }

#if DEBUG_ADD_FRAME_OFFSET
printf("stored: %p\n", offset);
#endif

return(0);
}

/***********************************/
/* retrieve the nth transformation */
/***********************************/
gint read_transform(gint n, struct model_pak *model)
{
gpointer camera;

g_assert(model != NULL);
g_assert(model->camera != NULL);

camera = g_slist_nth_data(model->transform_list, n);

g_assert(camera != NULL);

model->camera = camera;

return(0);
}

/**************************/
/* retrieve the nth frame */
/**************************/
#define DEBUG_READ_FRAME 0
gint read_frame(FILE *fp, gint n, struct model_pak *model)
{
gint status;
gdouble rmax, v1[3], v2[3];
gpointer camera=NULL;
GString *err_text;

g_assert(model != NULL);

rmax = model->rmax;

/* conventional or transformation style animation */
if (model->transform_list)
  {
/* NEW - process transformation list as an animation */
  status = read_transform(n, model);
  }
else
  {
g_assert(fp != NULL);

ARR3SET(v1, model->centroid);
ARR3SET(v2, model->offset);

status = read_raw_frame(fp, n, model);
if (status)
  {
  err_text = g_string_new("");
  g_string_printf(err_text, "Error reading frame: %d\n", n);
  gui_text_show(ERROR, err_text->str);
  g_string_free(err_text, TRUE);
  return(1);
  }

/* setup (have to save camera) */
camera = camera_dup(model->camera);
model_prep(model);
model->camera = camera;
g_free(model->camera_default);
model->camera_default = camera;

ARR3SET(model->centroid, v1);
ARR3SET(model->offset, v2);
  }

/* apply desired constraint */
if (model->periodic && !model->anim_fix)
  {
  switch (model->anim_confine)
    {
    case PBC_CONFINE_ATOMS:
      coords_confine_cores(model->cores, model);

    case PBC_CONFINE_MOLS:
      coords_compute(model);
      connect_bonds(model);
      connect_molecules(model);
      break;
    }
  }

if (model->anim_noscale)
  model->rmax = rmax;

return(0);
}

/***************************************/
/* current frame modification callback */
/***************************************/
#define DEBUG_SELECT_FRAME 0
void select_frame(GtkWidget *w, struct model_pak *model)
{
FILE *fp=NULL;

g_assert(w != NULL);
g_assert(model != NULL);

#if DEBUG_SELECT_FRAME
printf("request frame: %d, model: %p\n", model->cur_frame, model);
#endif

/* FIXME - better way to cope with transform animations */
if (!model->transform_list)
  fp = fopen(model->filename, "r");

read_frame(fp, model->cur_frame, model);

meas_graft_model(model);

gui_active_refresh();

redraw_canvas(SINGLE);

if (!model->transform_list)
  fclose(fp);
}

/*********************************************************/
/* read and draw the next frame in the current animation */
/*********************************************************/
#define DEBUG_DISPLAY_NEXT_FRAME 0
gint anim_next_frame(struct model_pak *model)
{
gulong time;
gchar *text, *name;

g_assert(model != NULL);

/* test if should we return to the start */
/*
model->cur_frame++;
*/
model->cur_frame += model->anim_step;

if (model->cur_frame == model->num_frames)
  if (model->anim_loop)
    model->cur_frame = 1;

/* continue until we run out of frames (or a stop is flagged) */
if (model->cur_frame < model->num_frames && model->animating)
  {
#if DEBUG_DISPLAY_NEXT_FRAME
printf("displaying [%d]\n", model->cur_frame);
#endif

time = mytimer();

/* if a dialog exists - update via the current frame spinner */
  if (dialog_exists(ANIM, model))
    gui_relation_update(model);
  else
    {
/* otherwise, update manually */
    read_frame(model->afp, model->cur_frame, model);
    meas_graft_model(model);
    gui_active_refresh();
    redraw_canvas(SINGLE);
    }

/* animation adjusted redraw time */
time = mytimer() - time;
model->redraw_cumulative += time;

/* NEW - render to file */
  if (sysenv.render.animate)
    {
    text = g_strdup_printf("%s_%06d.pov", sysenv.render.animate_file, model->cur_frame);
    name = g_build_filename(sysenv.cwd, text, NULL);

    write_povray(name, model);

/* NB: added this as jago keeps locking up on multi-frame renders */
    if (!sysenv.render.no_povray_exec)
      povray_exec(name);

    g_free(text);
    g_free(name);
    }

  return(TRUE);
  }

/* FIXME - find a better way to do this... */
if (!model->transform_list)
  fclose(model->afp);

/* done animation */
model->animating = FALSE;
model->cur_frame--;

/* create movie? */
if (sysenv.render.animate && !sysenv.render.no_povray_exec)
  {
  text = NULL; 
  switch (sysenv.render.animate_type)
    {
    case ANIM_GIF:
      text = g_strdup_printf("%s -delay %d %s_*.tga %s.gif",
                             sysenv.convert_path,
                             (gint) sysenv.render.delay,
                             sysenv.render.animate_file, sysenv.render.animate_file);
      break;

    case ANIM_MPEG:
      text = g_strdup_printf("%s -quality %d -delay %d %s_*.tga %s.mpg",
                             sysenv.convert_path, (gint) sysenv.render.mpeg_quality,
                             (gint) sysenv.render.delay,
                             sysenv.render.animate_file, sysenv.render.animate_file);
      break;
    }

  if (text)
    {
    system(text);
    g_free(text);
    gui_text_show(DEFAULT, "Completed movie creation.\n");
    }
  }

/* cleanup */
if (sysenv.render.no_keep_tempfiles)
  {
  text = g_strdup_printf("rm -rf %s_*.pov", sysenv.render.animate_file);
  system(text);
  g_free(text);
  text = g_strdup_printf("rm -rf %s_*.tga", sysenv.render.animate_file);
  system(text);
  g_free(text);
  }

/* done - return FALSE to terminate the timer */
return(FALSE);
}

/**********************************/
/* set current frame to beginning */
/**********************************/
void anim_rewind(GtkWidget *w, gpointer data)
{
struct model_pak *model;

model = dialog_model(data);
g_assert(model != NULL);

model->cur_frame = 1;

gui_relation_update(model);
}

/****************************/
/* set current frame to end */
/****************************/
void anim_fastforward(GtkWidget *w, gpointer data)
{
struct model_pak *model;

model = dialog_model(data);
g_assert(model != NULL);

model->cur_frame = model->num_frames-1;

gui_relation_update(model);
}

/**********************/
/* one frame backward */
/**********************/
void anim_step_backward(GtkWidget *w, gpointer data)
{
struct model_pak *model;

model = dialog_model(data);
g_assert(model != NULL);

if (model->cur_frame > 1)
  {
  model->cur_frame--;
  gui_relation_update(model);
  }
}

/*********************/
/* one frame forward */
/*********************/
void anim_step_forward(GtkWidget *w, gpointer data)
{
struct model_pak *model;

model = dialog_model(data);
g_assert(model != NULL);

if (model->cur_frame < model->num_frames-1)
  {
  model->cur_frame++;
  gui_relation_update(model);
  }
}

/****************************/
/* start an animation timer */
/****************************/
void anim_start(GtkWidget *w, gpointer data)
{
gint freq;
struct model_pak *model;

/* don't allow more than one */
model = dialog_model(data);
g_assert(model != NULL);
if (model->animating)
  return;

/* timeout frequency */
/* NB: we can't refresh any faster than units of 25 (ie the canvas redraw frequency) */
freq = 25 * model->anim_speed;

if (!model->transform_list)
  {
  model->afp = fopen(model->filename, "r");
  if (!model->afp)
    {
    gui_text_show(ERROR, "Failed to open animation stream.\n");
    return;
    }
  }

g_timeout_add(freq, (GSourceFunc) &anim_next_frame, model);
model->animating = TRUE;
}

/****************************/
/* stop the animation timer */
/****************************/
void anim_stop(GtkWidget *w, gpointer data)
{
struct model_pak *model;

model = dialog_model(data);
g_assert(model != NULL);
model->animating = FALSE;
}

/************************************/
/* toggle the render to file option */
/************************************/
void anim_render(GtkWidget *w, GtkWidget *a_frame)
{
if (sysenv.render.animate)
  gtk_widget_set_sensitive(GTK_WIDGET(a_frame), TRUE);
else
  gtk_widget_set_sensitive(GTK_WIDGET(a_frame), FALSE);
}

/***********************************/
/* pbc confinement option handlers */
/***********************************/
void atom_pbc(struct model_pak *model)
{
model->anim_confine = PBC_CONFINE_ATOMS;
}
void mol_pbc(struct model_pak *model)
{
model->anim_confine = PBC_CONFINE_MOLS;
}
void no_pbc(struct model_pak *model)
{
model->anim_confine = PBC_CONFINE_NONE;
}

/********************/
/* cleanup callback */
/********************/
void animate_cleanup(struct model_pak *model)
{
g_assert(model != NULL);
model->animating = FALSE;
}

/***********************/
/* configure animation */
/***********************/
void gui_animate_dialog(void)
{
gchar *tmp, *title;
gpointer dialog;
GtkWidget *window, *frame, *vbox, *hbox, *hbox2, *anim_box;
GtkWidget *notebook, *page, *button, *label, *entry, *scale;
GSList *group=NULL;
struct model_pak *data;

/* checks */
data = sysenv.active_model;
if (!data)
  return;
if (!data->animation)
  return;

/* CURRENT */
/*
anim_read_all_frames(data);
*/

/* prevent recording whilst in animation */
gui_mode_switch(FREE);

/* dialog setup */
title = g_strdup_printf("Animation: %s", data->basename);
dialog = dialog_request(ANIM, title, NULL, animate_cleanup, data);
g_free(title);
if (!dialog)
  return;
window = dialog_window(dialog);

/* notebook frame */
frame = gtk_frame_new(NULL);
gtk_box_pack_start(GTK_BOX(GTK_DIALOG(window)->vbox), frame, FALSE, FALSE, 0);
gtk_container_set_border_width(GTK_CONTAINER(frame), PANEL_SPACING);

/* create notebook */
notebook = gtk_notebook_new();
gtk_notebook_set_tab_pos(GTK_NOTEBOOK(notebook), GTK_POS_TOP);
gtk_container_add(GTK_CONTAINER(frame), notebook);
gtk_notebook_set_show_border(GTK_NOTEBOOK(notebook), TRUE);

/* page 1 */
page = gtk_vbox_new(FALSE, PANEL_SPACING);
label = gtk_label_new("Control");
gtk_notebook_append_page(GTK_NOTEBOOK(notebook), page, label);

/* general animation stuff */
frame = gtk_frame_new(NULL);
gtk_box_pack_start(GTK_BOX(page),frame,FALSE,FALSE,0);
/* create a vbox in the frame */
vbox = gtk_vbox_new(TRUE, PANEL_SPACING);
gtk_container_add(GTK_CONTAINER(frame), vbox);

/* num frames */
hbox = gtk_hbox_new(FALSE, 0);
gtk_container_set_border_width(GTK_CONTAINER(hbox), PANEL_SPACING);
gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
label = gtk_label_new("Number of frames:");
gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
tmp = g_strdup_printf("%9d", data->num_frames);
label = gtk_label_new(tmp);
gtk_box_pack_end(GTK_BOX(hbox), label, FALSE, FALSE, 0);

/* desired delay */
gui_direct_spin("Animation delay", &data->anim_speed,
                  1.0, 40.0, 1.0, NULL, NULL, vbox);

gui_direct_spin("Animation step size ", &data->anim_step,
                  1.0, data->num_frames, 1.0, NULL, NULL, vbox);

gui_direct_check("Don't recalculate connectivity", &data->anim_fix, NULL, NULL, vbox);

gui_direct_check("Don't recalculate scale", &data->anim_noscale, NULL, NULL, vbox);

gui_direct_check("Loop", &data->anim_loop, NULL, NULL, vbox);


/* page 2 */
page = gtk_vbox_new(FALSE, PANEL_SPACING);
label = gtk_label_new("Processing");
gtk_notebook_append_page(GTK_NOTEBOOK(notebook), page, label);

/* cycle options */
frame = gtk_frame_new(NULL);
gtk_box_pack_start(GTK_BOX(page),frame,FALSE,FALSE,0);

vbox = gtk_vbox_new(FALSE, 0);
gtk_container_add(GTK_CONTAINER(frame), vbox);
gtk_container_set_border_width(GTK_CONTAINER(vbox), PANEL_SPACING);

/* actions at start of each cycle */
new_radio_group(0, vbox, FF);

button = add_radio_button("Confine atoms to PBC", (gpointer) atom_pbc, data);
if (data->anim_confine == PBC_CONFINE_ATOMS)
  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), TRUE);

button = add_radio_button("Confine mols to PBC", (gpointer) mol_pbc, data);
if (data->anim_confine == PBC_CONFINE_MOLS)
  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), TRUE);

button = add_radio_button("Cell confinement off", (gpointer) no_pbc, data);
if (data->anim_confine == PBC_CONFINE_NONE)
  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), TRUE);


/* page 3 */
page = gtk_vbox_new(FALSE, PANEL_SPACING);
label = gtk_label_new("Rendering");
gtk_notebook_append_page(GTK_NOTEBOOK(notebook), page, label);

/* cycle options */
frame = gtk_frame_new(NULL);
gtk_box_pack_start(GTK_BOX(page), frame, FALSE, FALSE, 0);

vbox = gtk_vbox_new(FALSE, 0);
gtk_container_add(GTK_CONTAINER(frame), vbox);
gtk_container_set_border_width(GTK_CONTAINER(vbox), PANEL_SPACING);


anim_box = gtk_vbox_new(TRUE, 0);
gui_direct_check("Create movie", &sysenv.render.animate,
                   anim_render, anim_box, vbox);
gtk_box_pack_end(GTK_BOX(vbox), anim_box, TRUE, TRUE, 0);


/* sensitive box */
vbox = gtk_vbox_new(FALSE,0);
gtk_box_pack_start(GTK_BOX(anim_box), vbox, TRUE, TRUE, 0);
gtk_container_set_border_width(GTK_CONTAINER(vbox), PANEL_SPACING);


/* start off with a button */
button = gtk_radio_button_new_with_label (NULL, "Animated GIF");
g_signal_connect(GTK_OBJECT(button), "clicked",
                   GTK_SIGNAL_FUNC(event_render_modify), (gpointer) button);
gtk_box_pack_start(GTK_BOX(vbox), button, FALSE, TRUE, 0);
g_object_set_data(G_OBJECT(button), "id", (gpointer) ANIM_GIF);

/* make a radio group */
group = gtk_radio_button_group(GTK_RADIO_BUTTON(button));
/* do the rest of the buttons */
button = gtk_radio_button_new_with_label(group, "MPEG");
g_signal_connect(GTK_OBJECT(button), "clicked",
                 GTK_SIGNAL_FUNC(event_render_modify), (gpointer) button);
gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, TRUE, 0);
g_object_set_data(G_OBJECT(button), "id", (gpointer) ANIM_MPEG);

/* movie creation parameters */
gui_direct_spin("MPEG quality",
                  &sysenv.render.mpeg_quality, 1, 100, 1,
                  NULL, NULL, vbox);
gui_direct_spin("Delay (ms)",
                  &sysenv.render.delay, 0, 100, 5,
                  NULL, NULL, vbox);

/* file entry */
hbox = gtk_hbox_new (FALSE, 0);
gtk_box_pack_start(GTK_BOX(vbox),hbox,FALSE,TRUE,0);
label = gtk_label_new("Filename ");
gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, TRUE, 0);
entry = gtk_entry_new();
gtk_box_pack_end(GTK_BOX (hbox), entry, FALSE, TRUE, 0);
gtk_entry_set_text(GTK_ENTRY(entry), sysenv.render.animate_file);

/* update hook */
g_signal_connect(GTK_OBJECT(entry), "changed",
                 GTK_SIGNAL_FUNC(event_render_modify), (gpointer) entry);
g_object_set_data(G_OBJECT(entry), "id", (gpointer) ANIM_NAME);

/* misc options */
/*
gui_direct_check("Create povray input files then stop",
                   &sysenv.render.no_povray_exec,
                   NULL, NULL, vbox);
gui_direct_check("Delete intermediate input/image files",
                   &sysenv.render.no_keep_tempfiles,
                   NULL, NULL, vbox);
*/


/* set initial state */
if (!sysenv.render.animate)
  gtk_widget_set_sensitive(anim_box, FALSE);


/* NEW - slider for current frame */
frame = gtk_frame_new(NULL);
gtk_box_pack_start(GTK_BOX(GTK_DIALOG(window)->vbox), frame, FALSE, FALSE, 0);

vbox = gtk_vbox_new(FALSE, PANEL_SPACING);
gtk_container_add(GTK_CONTAINER(frame), vbox);

scale = gui_direct_hscale(0, data->num_frames-1, 1, &data->cur_frame, select_frame, data, vbox);
gtk_range_set_update_policy(GTK_RANGE(scale), GTK_UPDATE_DISCONTINUOUS);

/* control buttons */
hbox2 = gtk_hbox_new(FALSE, 0);
gtk_box_pack_start(GTK_BOX(vbox), hbox2, FALSE, FALSE, PANEL_SPACING);
hbox = gtk_hbox_new(TRUE, PANEL_SPACING);
gtk_box_pack_start(GTK_BOX(hbox2), hbox, TRUE, FALSE, 0);

gui_icon_button("GDIS_REWIND", NULL,
                  anim_rewind, dialog,
                  hbox);

gui_icon_button("GDIS_STEP_BACKWARD", NULL,
                  anim_step_backward, dialog,
                  hbox);

gui_icon_button("GDIS_PLAY", NULL,
                  anim_start, dialog,
                  hbox);

gui_icon_button("GDIS_PAUSE", NULL,
                   anim_stop, dialog,
                  hbox);

gui_icon_button("GDIS_STEP_FORWARD", NULL,
                  anim_step_forward, dialog,
                  hbox);

gui_icon_button("GDIS_FASTFORWARD", NULL,
                  anim_fastforward, dialog,
                  hbox);

gui_stock_button(GTK_STOCK_CLOSE, dialog_destroy, dialog,
                   GTK_DIALOG(window)->action_area);

/* display the dialog */
gtk_widget_show_all(window);

gui_relation_update(data);
}



syntax highlighted by Code2HTML, v. 0.9.1