// Copyright 2003 Regents of the University of California

// Astropulse 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.

// Astropulse 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 Astropulse; see the file COPYING.  If not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

/* ap_graphics.C */
/* Graphics functions for AstroPulse */

#ifndef _WIN32
#include "ap_config.h"
#else
#include "boinc_win.h"
#endif

#ifdef BOINC_APP_GRAPHICS

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

#include "ap_graphics.h"
#include "astropulse.h"
#include "graphics2.h"
#include "util.h"
#include "str_util.h"

#ifdef _WIN32
#include "boinc_win.h"
#include <gl/gl.h>           // Header File For The OpenGL32 Library
#include <gl/glu.h>          // Header File For The GLu32 Library
#ifndef __MINGW32__
#include <gl/glaux.h>        // Header File For The Glaux Library
#endif
#include <glut.h>
#endif

#ifndef _WIN32
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>


#ifdef __APPLE_CC__
#include <Carbon/Carbon.h>
#include <DrawSprocket/DrawSprocket.h>
#include <AGL/agl.h>
#include <AGL/aglRenderers.h>
#include <OpenGL/gl.h>
#include <OpenGL/glu.h>
#include <GLUT/glut.h>
#endif

#ifdef HAVE_GL_H
#include "gl.h"
#elif defined(HAVE_GL_GL_H)
#include <GL/gl.h>
#elif defined(HAVE_OPENGL_GL_H)
#include <OpenGL/gl.h>
#endif

#ifdef HAVE_GLU_H
#include "glu.h"
#elif defined(HAVE_GL_GLU_H)
#include <GL/glu.h>
#elif defined(HAVE_OPENGL_GLU_H)
#include <OpenGL/glu.h>
#endif

#ifdef HAVE_GLUT_H
#include "glut.h"
#elif defined(HAVE_GL_GLUT_H)
#include <GL/glut.h>
#elif defined(HAVE_OPENGL_GLUT_H)
#include <OpenGL/glut.h>
#elif defined(HAVE_GLUT_GLUT_H)
#include <GLUT/glut.h>
#endif
#endif







#include "timecvt.h"

AP_GRAPHICS ap_graphics;

extern GLfloat mat_diffuse[];
extern GLfloat mat_specular[];
extern GLfloat mat_shininess[];

extern void MyCreateFont(unsigned int &base, char *fontName, int Size,int weight);

#define SX 50
#define SY 30
#define RX 50
#define RY 40
#define PI 3.14159265358979323846264

//double theta;
bool mouse_down = false;
int mouse_x, mouse_y;

using namespace Astropulse;

#define AP_JD0 2440587.5  /* Time at 0:00 GMT 1970 Jan 1 */
#define AP_SECONDS_PER_DAY 86400


time_t jd_to_time_t(double jd) {
  return ((time_t)((jd-AP_JD0)*AP_SECONDS_PER_DAY));
}

static void ra_string(float x, char* p) {
  int hr = (int) x;
  float fmin = x-hr;
  int min = (int)(fmin*60);
  float fsec = fmin*60 - min;
  int sec = (int)(fsec*60);
  sprintf(p, "%2d hr %2d' %2d\" RA", hr, min, sec);
}

static void dec_string(float x, char* p) {
  int deg = (int) x;
  float fmin = x-deg;
  int min = (int)(fmin*60);
  float fsec = fmin*60 - min;
  int sec = (int)(fsec*60);
  sprintf(p, "%s%d deg %2d' %2d\" Dec", (x<0?"-":"+"), deg, abs(min), abs(sec));
}


void AP_GRAPHICS::get_analysis_info_string(char* buf) {
  sprintf( buf,
           "Telescope time: %.2f sec\n"
           "Dispersion: %.1f pc/ccm\n"
           "%s",
           ap_shmem->ap_gdata.state.data_chunk_now/625000.0,
           (ap_shmem->ap_gdata.state.dm_chunk_large_now +
            ap_shmem->ap_gdata.state.dm_chunk_small_now +
            ap_shmem->ap_gdata.state.dm_now)/18.3,
	    ap_shmem->ap_gdata.status
         );
}

void AP_GRAPHICS::get_user_info_string(char* buf) {
  sprintf( buf, "Name: %s\n"
           "Team: %s\n"
           "Total credit: %.2f\n",
           app_init_data.user_name,
           app_init_data.team_name,
           app_init_data.user_total_credit
         );
}

void AP_GRAPHICS::draw_time_graph() {
  if (time_series.length && &(time_series.data[0])) time_series_graph.draw(&(time_series.data[0]), time_series.length);
}

void AP_GRAPHICS::get_data_info_string(char* buf) {
  char ra_buf[256], dec_buf[256];

  if (isprint(ap_shmem->ap_gdata.state.wuname[0])) {

    ra_string(ap_shmem->ap_gdata.state.ra0,ra_buf);
    dec_string(ap_shmem->ap_gdata.state.dec0,dec_buf);


    char date_string[64];
    time_t rec_time;

    rec_time = jd_to_time_t(ap_shmem->ap_gdata.state.jd0);
    if (rec_time > 0)
      strftime( date_string, 64, "%b %d %H:%M:%S %Y", localtime(&rec_time) );
    else
      sprintf( date_string, "Unknown" );

    sprintf(
      buf,
      "From: %s, %s\n"
      "Recorded on: %s\n"
      "Recorded at: %s\n",
      ra_buf, dec_buf,
      date_string,
      ap_shmem->ap_gdata.state.recvname
    );
  } else {
    strcpy(buf,"");
  }
}

void AP_GRAPHICS::init_text_panels() {
  float pos[3], size[2];
  COLOR color;
  double dtheta;
  double char_height = 0.15f;
  double line_width = 2.0f;
  double line_spacing = 0.3f;
  double margin = 0.2f;
  color.a = .7f;
  double lum = .6f;
  double sat = .9f;

  double hue = start_hue;
  HLStoRGB(hue, lum, sat, color);

  pos[0] = -1;
  pos[1] = -1;
  pos[2] = -2;
  size[0] = 4.5f;
  size[1] = 2.6f;
  dtheta = .3f;
  text_panels[0].init(
    pos, size, color, dtheta, char_height, line_width, line_spacing, margin
  );

  hue = start_hue + hue_change;
  if (hue > 1) hue -= 1;
  if (hue < 1) hue += 1;
  HLStoRGB(hue, lum, sat, color);
  pos[0] = -3;
  pos[1] = 0;
  pos[2] = -1.8f;
  dtheta = -.6f;
  text_panels[1].init(
    pos, size, color, dtheta, char_height, line_width, line_spacing, margin
  );
}

void AP_GRAPHICS::render_pillars(double time_of_day, double dt) {
  float pos[3] = {0., 0., 0.};
  char buf[512];

  draw_pillars();
  mode_lines();

  glColor3f(1.f, 1.f, 1.f);                  // white text
  pos[0] = 2.0f;
  pos[1] = 2.15f;
  pos[2] = 0.0f;
  draw_text_line(pos, 0.2f, 2.0f, "Data info");
  pos[0] = 1.2f;
  pos[1] -= 0.2f;

  get_data_info_string(buf);
  draw_text(pos, 0.1f, 1.0f, 0.15f, buf );
  pos[0] = 2.0f;
  pos[1] = .8f;
  draw_text_line(pos, 0.2f, 2.0f, "User info");
  pos[0] = 1.2f;
  pos[1] -= .2f;

  get_user_info_string(buf);
  draw_text(pos, 0.1f, 1.0f, 0.15f, buf);
  pos[0] = -2.0f;
  pos[1] = 2.15f;
  draw_text_line(pos, 0.2f, 2.0f, "AstroPulse");
  pos[0] = -3.5f;
  pos[1] = 1.9f;

  get_analysis_info_string(buf);
  draw_text(pos, 0.1f, 1.0f, 0.15f, buf);

  pos[1] = 0.3f;
  double cur_cpu_time;
  char time_buf[256];
  /* Boinc API: places total CPU time spent on this workunit
   * (not counting time to render graphics) into $cur_cpu_time.
   */
  // boinc_wu_cpu_time(cur_cpu_time);
  cur_cpu_time = ap_shmem->ap_gdata.cpu_time;
  ndays_to_string(cur_cpu_time/SECONDS_PER_DAY, -1, time_buf );
  sprintf(buf,
          "Overall %.3f%% done    CPU time: %s",
          ap_shmem->ap_gdata.state.frac_done*100, time_buf
         );
  draw_text_line(pos, 0.1f, 1.0f, buf);
  mode_unshaded();
  outer_progress.draw(ap_shmem->ap_gdata.state.frac_done);

  draw_time_graph();
}

void AP_GRAPHICS::render_panels(double time_of_day, double dt) {
  double cur_cpu_time;
  float pos[3] = {0.f, 0.f, 0.f};
  float size[3] = {0.f, 0.f, 0.f};
  char buf[512];
  char time_buf[512];
  GLfloat char_height = 0.2f;
  GLfloat line_width = 2.0f;
  GLfloat line_spacing = 0.4f;
  GLfloat margin = 0.2f;
  MOVING_TEXT_PANEL tp[3];
  int i;

  get_user_info_string(buf);
  get_data_info_string(time_buf);
  strcat(buf, time_buf);
  text_panels[0].set_text(0, buf);

  get_analysis_info_string(buf);
  text_panels[1].set_text(0, buf);

  boinc_wu_cpu_time(cur_cpu_time);
  ndays_to_string(cur_cpu_time/SECONDS_PER_DAY, 0, time_buf );
  sprintf(buf,
          "Overall %.3f%% done    CPU time: %s",
          ap_shmem->ap_gdata.state.frac_done*100, time_buf
         );
  text_panels[1].set_text(6, buf);

  text_panels[1].get_pos(7, pos);
  pos[2]+=.01;
  total_progress.set_pos(pos);

  pos[0] = 0.03;
  pos[1] = 0.76;
  pos[2] = 0;

  mode_unshaded();

  total_progress.draw(ap_shmem->ap_gdata.state.frac_done);
  glColor4f(1,1,1,1);

  for (i=0; i<2; i++) {
    tp[i] = text_panels[i];
  }
  MOVING_TEXT_PANEL::sort(tp, 2);
  for (i=0; i<2; i++) {
    tp[i].draw();
  }
  for (i=0; i<2; i++) {
    text_panels[i].move(dt);
  }

  text_panels[1].get_pos(5, pos);
  pos[1]-=.15f;
  time_series_graph.set_pos(pos);
  draw_time_graph();
}

void AP_GRAPHICS::render_headsup(double time_of_day, double dt) {
  double cur_cpu_time;
  float pos[3] = {0.03f, 0.76f, 1.0f};
  float pos1[3] = {.03f,.94f,1.0f};
  float pos2[3] = {.97f,.94f,1.0f};
  GLfloat char_height = 0.015f;
  GLfloat line_width = 0.25f;
  GLfloat line_spacing = 0.03f;
  GLfloat margin = 0.2f;
  char buf[512];
  char time_buf[512];

  glColor4d(1,1,1,1);
  mode_ortho();

  get_analysis_info_string(buf);
  draw_text(pos1, char_height, line_width, line_spacing, buf);

  get_user_info_string(buf);
  get_data_info_string(time_buf);
  strcat(buf, time_buf);
  draw_text_right(pos2, char_height, line_width, line_spacing, buf);

  boinc_wu_cpu_time(cur_cpu_time);
  ndays_to_string(cur_cpu_time/SECONDS_PER_DAY, 0, time_buf );
  float progress = ap_shmem->ap_gdata.state.frac_done;
  sprintf(buf, "Overall %.3f%% done    CPU time: %s", progress*100, time_buf);
  draw_text(pos, char_height, line_width, line_spacing, buf);
  mode_unshaded();

  draw_time_graph();
  mode_unshaded();
  total_progress.draw(progress);
  ortho_done();

}

/* This function is called periodically by the graphics thread, via
 * the BOINC api
 */
void app_graphics_render(int xs, int ys, double time_of_day) {
  ap_graphics.render(xs, ys, time_of_day);
}

/* This function is called by the graphics thread when a window
 * is created.  It is required by the BOINC api
 */
void app_graphics_init() {
   ap_graphics.graphics_thread_init();
}

/* This function is called by the graphics thread whenever the user's project
 * preferences change.  It is required by the BOINC api
 */

#if 0
void app_graphics_reread_prefs() {
  ap_graphics.reread_prefs();
}
#endif

void AP_GRAPHICS::resize(int w, int h) {
  AP_GRAPHICS_BASE::resize(w, h);
}

void AP_GRAPHICS::graphics_thread_init() {
  AP_GRAPHICS_BASE::graphics_thread_init();
  setup_given_prefs();
}

void AP_GRAPHICS::reread_prefs() {
//  AP_GRAPHICS_BASE::reread_prefs();
  setup_given_prefs();
}

void AP_GRAPHICS::setup_given_prefs() {
  icon_pos1[0] = .05f; //top left
  icon_pos1[1] = .2f;
  icon_pos1[2] = 0.0f;
  icon_pos2[0] = .2f; //bottom right
  icon_pos2[1] = .05f; //bottom right
  icon_pos2[2] = 0.0f; //bottom right

  float red[] = {1.f, 0.f, 0.f, 1.f};
  float reda[] = {1.f, 0.f, 0.f, .8f};
  float white[] = {1.f, 1.f, 1.f, 1.f};
  float green[] = {0.f, 1.f, 0.f, 1.f};
  float outer[] = {1.0f, 0.f, 0.2f, 0.4f};
  float inner[] = {0.2f, 0.0f, 0.7f, 0.9f};
  float size[3],pos[3];

  AP_GRAPHICS_BASE::setup_given_prefs();

  switch (text_style) {
    case TEXT_STYLE_PILLARS:
      pos[0] = -3.5f;
      pos[1] = 0.60f;
      pos[2] = 0.0f;
      outer_progress.init(pos, 4.0f, 0.1f, 0.08f, outer,inner);

      pos[0] = -3.5f;
      pos[1] = 0.8f;
      pos[2] = -.4f;
      size[0] = 4.0f;
      size[1] = .5f;
      size[2] = .4f;
      time_series_graph.init(pos, size, red, green);

      break;
    case TEXT_STYLE_HEADSUP:
      pos[0] = .03f;
      pos[1] = .79f;
      pos[2] = 1.0f;
      size[0] = .3f;
      size[1] = .08f;
      size[2] = 0.0f;
      time_series_graph.init(pos, size, red, green);

      pos[0] = .03f;
      pos[1] = 0.745f;
      pos[2] = 0.0f;
      total_progress.init(pos,.35f,.02f,.015f,outer,inner);
      break;
    case TEXT_STYLE_PANELS:
      init_text_panels();
      size[0] = 3.5;
      size[1] = .8;
      size[2] = .1;
      time_series_graph.init(pos, size, red, green);
      pos[0] = .03;
      pos[1] = .785;
      pos[2] = 0;
      outer[3]=.7;
      total_progress.init(pos,3,.2,.15,outer,inner);
      break;
  }
  pos[0] = -3.50;
  pos[1] = -2.50;
  pos[2] = -2.00;
  size[0] = 7.00;
  size[1] = 2.00;
  size[2] = 4.00;
  
  rarray.init_display(
    graph_style, pos, size, start_hue, hue_change, graph_alpha,
    "Time (sec)","Power","Dispersion"
  );
}

static void app_init_camera(double dist) {
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(
    45.0,       // field of view in degree
    1.0,        // aspect ratio
    1.0,        // Z near clip
    1000.0     // Z far
  );

  set_viewpoint(dist);
}

void AP_GRAPHICS::render(int xs, int ys, double time_of_day) {
  static double last_time=0;
  double dt = 0;

  if(!ap_shmem) {
    ap_shmem = (AP_SHMEM*)boinc_graphics_get_shmem("astropulse");
    ap_gdata = &(ap_shmem->ap_gdata);
  }

  if (last_time != 0) {
    dt = time_of_day - last_time;
  }
  last_time = time_of_day;

  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  if (starfield_size) {
    starfield.update_stars(dt);
  }

  render_logos();
  app_init_camera(viewpoint_distance);
  scale_screen(width, height);

  start_rotate();
  incr_rotate(dt);

  render_background();

  switch (text_style) {
    case TEXT_STYLE_PILLARS:
      render_pillars(time_of_day, dt);
      break;
    case TEXT_STYLE_PANELS:
      render_panels(time_of_day, dt);
      break;
    case TEXT_STYLE_HEADSUP:
      render_headsup(time_of_day, dt);
      break;
  }
  render_3d_graph(time_of_day);
  end_rotate();
  glFlush();
}

/* This function is called by the BOINC api whenever a window is
 * resized.
 */
void app_graphics_resize(int w, int h) {
  ap_graphics.resize(w, h);
}


void boinc_app_mouse_move(int x, int y, int left, int middle, int right) {
  if (left) {
    ap_graphics.pitch_angle += (y-mouse_y)*.1;
    ap_graphics.roll_angle += (x-mouse_x)*.1;
    mouse_y = y;
    mouse_x = x;
  } else if (right) {
    double d = (y-mouse_y);
    ap_graphics.viewpoint_distance *= exp(d/100.);
    mouse_y = y;
    mouse_x = x;
  } else {
    mouse_down = false;
  }
}

void boinc_app_mouse_button(int x, int y, int which, int is_down) {
  if (is_down) {
    mouse_down = true;
    mouse_x = x;
    mouse_y = y;
  } else {
    mouse_down = false;
  }
}

void boinc_app_key_press(int, int) {}

void boinc_app_key_release(int, int) {}

#if 0
void app_unload_gl() {
  glDeleteTextures(1,g_Texture);
  if (listBase[0] != 0) {
    glDeleteLists(listBase[0], 96);
  }
}
#endif

#endif

