// Filename: pgrid.cxx
// Created by:  drose (03Apr02)
//
////////////////////////////////////////////////////////////////////
//
// PANDA 3D SOFTWARE
// Copyright (c) Carnegie Mellon University.  All rights reserved.
//
// All use of this software is subject to the terms of the revised BSD
// license.  You should have received a copy of this license along
// with this source code in a file named "LICENSE."
//
////////////////////////////////////////////////////////////////////

#include "pandaFramework.h"
#include "pandaNode.h"
#include "transformState.h"
#include "clockObject.h"
#include "string_utils.h"
#include "pvector.h"

#ifdef HAVE_GETOPT
#include <getopt.h>
#else
#include "gnu_getopt.h"
#endif


#define RANDFRAC (rand()/(float)(RAND_MAX))

class GriddedFilename {
public:
  Filename _filename;
  int _count;
  NodePath _model;
};
typedef pvector<GriddedFilename> GriddedFilenames;

typedef struct {
  // for rot moving
  float xcenter,ycenter;
  float xoffset,yoffset;
  float ang1,ang1_vel;
  float ang2,ang2_vel;
  
  float radius;
  
  // for moving
  float xstart,ystart;
  float xend,yend;
  float xdel,ydel,timedel;
  double starttime,endtime;
  double vel;
  LMatrix4f rotmat;

  PandaNode *node;
} gridded_file_info;

typedef pvector<gridded_file_info> GriddedInfoArray;

typedef enum {None,Rotation,LinearMotion} GriddedMotionType;

#define GRIDCELLSIZE 5.0
static int gridwidth;  // cells/side

#define MIN_WANDERAREA_DIMENSION 120.0f

static float grid_pos_offset;  // origin of grid
static float wander_area_pos_offset;

static GriddedMotionType gridmotiontype = None;


// making these fns to get around ridiculous VC++ matrix inlining bugs at Opt2
static void
move_gridded_stuff(GriddedMotionType gridmotiontype,
                   gridded_file_info *InfoArr, int size) {
  double now = ClockObject::get_global_clock()->get_frame_time();

  LMatrix4f tmat1,tmat2,xfm_mat;

  for(int i = 0; i < size; i++) {
    double time_delta = (now-InfoArr[i].starttime);
#define DO_FP_MODULUS(VAL,MAXVAL)  \
    {if(VAL > MAXVAL) {int idivresult = (int)(VAL / (float)MAXVAL);  VAL=VAL-idivresult*MAXVAL;} else  \
    if(VAL < -MAXVAL) {int idivresult = (int)(VAL / (float)MAXVAL);  VAL=VAL+idivresult*MAXVAL;}}
  
    // probably should use panda lerps for this stuff, but I dont understand how

    if(gridmotiontype==Rotation) {

      InfoArr[i].ang1=time_delta*InfoArr[i].ang1_vel;
      DO_FP_MODULUS(InfoArr[i].ang1,360.0);
      InfoArr[i].ang2=time_delta*InfoArr[i].ang2_vel;
      DO_FP_MODULUS(InfoArr[i].ang2,360.0);

      // xforms happen left to right
      LVector2f new_center = LVector2f(InfoArr[i].radius,0.0) *
        LMatrix3f::rotate_mat(InfoArr[i].ang1);

      LVector3f translate_vec(InfoArr[i].xcenter+new_center._v.v._0,
                              InfoArr[i].ycenter+new_center._v.v._1,
                              0.0);

      const LVector3f rotation_axis(0.0, 0.0, 1.0);

      tmat1 = LMatrix4f::rotate_mat_normaxis(InfoArr[i].ang2,rotation_axis);
      tmat2 = LMatrix4f::translate_mat(translate_vec);
      xfm_mat = tmat1 * tmat2;
    } else {

      float xpos,ypos;

      if(now>InfoArr[i].endtime) {
        InfoArr[i].starttime = now;

        xpos = InfoArr[i].xstart = InfoArr[i].xend;
        ypos = InfoArr[i].ystart = InfoArr[i].yend;

        InfoArr[i].xend = RANDFRAC*fabs(2.0*wander_area_pos_offset) + wander_area_pos_offset;
        InfoArr[i].yend = RANDFRAC*fabs(2.0*wander_area_pos_offset) + wander_area_pos_offset;

        float xdel = InfoArr[i].xdel = InfoArr[i].xend-InfoArr[i].xstart;
        float ydel = InfoArr[i].ydel = InfoArr[i].yend-InfoArr[i].ystart;

        InfoArr[i].endtime = now + csqrt(xdel*xdel+ydel*ydel)/InfoArr[i].vel;
        InfoArr[i].timedel = InfoArr[i].endtime - InfoArr[i].starttime;

        const LVector3f rotate_axis(0.0, 0.0, 1.0);

        float ang = rad_2_deg(atan2(-xdel,ydel));

        InfoArr[i].rotmat= LMatrix4f::rotate_mat_normaxis(ang,rotate_axis);
      } else {
        float timefrac= time_delta/InfoArr[i].timedel;

        xpos = InfoArr[i].xdel*timefrac+InfoArr[i].xstart;
        ypos = InfoArr[i].ydel*timefrac+InfoArr[i].ystart;
      }

      LVector3f translate_vec(xpos, ypos, 0.0);
      LMatrix4f tmat2 = LMatrix4f::translate_mat(translate_vec);

      xfm_mat = InfoArr[i].rotmat * tmat2;
    }
    InfoArr[i].node->set_transform(TransformState::make_mat(xfm_mat));
  }
}

bool
get_command_line_opts(int &argc, char **&argv) {
  // Use getopt() to decode the optional command-line parameters.
  //  extern char *optarg;
  extern int optind;
  const char *options = "rm";
  int flag = getopt(argc, argv, options);
  while (flag != EOF) {
    switch (flag) {
    case 'r':
      gridmotiontype = Rotation;
      break;

    case 'm':
      gridmotiontype = LinearMotion;
      break;

    case '?':
      nout << "Invalid parameter.\n";
      return false;
    }

    flag = getopt(argc, argv, options);
  }

  argv += (optind - 1);
  argc -= (optind - 1);

  return true;
}

void
get_command_line_filenames(int argc, char *argv[],
                           pvector<Filename> &static_filenames,
                           GriddedFilenames &gridded_filenames) {
  for (int i = 1; i < argc && argv[i] != (char *)NULL; i++) {
    const string &arg = argv[i];
    size_t comma = arg.find(',');
    if (comma == string::npos) {
      // No comma in the filename, so it must be an ordinary static file.
      static_filenames.push_back(Filename::from_os_specific(arg));

    } else {
      // A comma in the filename indicates a gridded file.  The syntax
      // is filename,count where count represents the number of times
      // the file is repeated.
      string name = arg.substr(0, comma);
      string count_str = arg.substr(comma + 1);
      int count;
      if (!string_to_int(count_str, count)) {
        nout << "Ignoring invalid number: " << count_str << "\n";
        count = 1;
      } else if (count <= 0) {
        nout << "Ignoring inappropriate number: " << count << "\n";
        count = 1;
      }

      GriddedFilename gf;
      gf._filename = Filename::from_os_specific(name);
      gf._count = count;
      gridded_filenames.push_back(gf);
    }
  }
}

void
load_gridded_models(WindowFramework *window, 
                    GriddedFilenames &filenames,
                    GriddedInfoArray &info_arr) {
  // Load up all the files indicated in the list of gridded filenames
  // and store them in the given vector.

  // First, load up each model from disk once, and store them all
  // separate from the scene graph.  Also count up the total number of
  // models we'll be putting in the grid.
  int grid_count = 0;
  NodePath models("models");
  GriddedFilenames::iterator fi;
  for (fi = filenames.begin(); fi != filenames.end(); ++fi) {
    GriddedFilename &gf = (*fi);
    gf._model = window->load_model(models, gf._filename);
    if (!gf._model.is_empty()) {
      grid_count += gf._count;
    }
  }

  info_arr.clear();
  info_arr.reserve(grid_count);

  // Compute the integer square root of grid_count, so that we put our
  // models in a nice square grid.
      
  gridwidth=1;
  while(gridwidth*gridwidth < grid_count) {
    gridwidth++;
  }
  
  grid_pos_offset = -gridwidth*GRIDCELLSIZE/2.0;
  wander_area_pos_offset = -max((float)fabs(grid_pos_offset), MIN_WANDERAREA_DIMENSION/2.0f);

  // Now walk through the list again, copying models into the scene
  // graph as we go.
  
  float xpos = grid_pos_offset;
  float ypos = grid_pos_offset;
      
  srand( (unsigned)time( NULL ) );
  double now = ClockObject::get_global_clock()->get_frame_time();

  int model_count = 0;
  int passnum = 0;
  bool loaded_any;

  NodePath render = window->get_render();
  do {
    loaded_any = false;

    for (fi = filenames.begin(); fi != filenames.end(); ++fi) {
      const GriddedFilename &gf = (*fi);
      if (!gf._model.is_empty() && gf._count > passnum) {
        loaded_any = true;
        // Copy this model into the scene graph, and assign it a
        // position on the grid.

        string model_name = format_string(++model_count);
        NodePath model = render.attach_new_node(model_name);
        gf._model.copy_to(model);

        gridded_file_info info;
        info.node = model.node();

        LMatrix4f xfm_mat,tmat1,tmat2;

        if(gridmotiontype==Rotation) {

#define MIN_REVOLUTION_ANGVEL 30
#define MAX_REVOLUTION_ANGVEL 60

#define MIN_ROTATION_ANGVEL 30
#define MAX_ROTATION_ANGVEL 600

#define MAX_RADIUS 4.0*GRIDCELLSIZE
#define MIN_RADIUS 0.1*GRIDCELLSIZE

          info.starttime = now;

          info.xcenter=xpos;
          info.ycenter=ypos;
          info.ang1=RANDFRAC * 360.0;
          info.ang1_vel=((MAX_REVOLUTION_ANGVEL-MIN_REVOLUTION_ANGVEL) * RANDFRAC) + MIN_REVOLUTION_ANGVEL;

          info.ang2=RANDFRAC * 360.0;
          info.ang2_vel=((MAX_ROTATION_ANGVEL-MIN_ROTATION_ANGVEL) * RANDFRAC) + MIN_ROTATION_ANGVEL;

          info.radius = (RANDFRAC * (MAX_RADIUS-MIN_RADIUS)) + MIN_RADIUS;

          if(RANDFRAC>0.5) {
            info.ang1_vel=-info.ang1_vel;
          }

          if(RANDFRAC>0.5) {
            info.ang2_vel=-info.ang2_vel;
          }

          // xforms happen left to right
          LVector2f new_center = LVector2f(info.radius,0.0) *
            LMatrix3f::rotate_mat(info.ang1);

          const LVector3f rotate_axis(0.0, 0.0, 1.0);

          LVector3f translate_vec(xpos+new_center._v.v._0,
                                  ypos+new_center._v.v._1,
                                  0.0);

          tmat1.set_rotate_mat_normaxis(info.ang2,rotate_axis);
          tmat2 = LMatrix4f::translate_mat(translate_vec);
          xfm_mat = tmat1 * tmat2;
        } else if(gridmotiontype==LinearMotion) {

#define MIN_VEL 2.0
#define MAX_VEL (fabs(wander_area_pos_offset))

          info.vel=((MAX_VEL-MIN_VEL) * RANDFRAC) + MIN_VEL;

          info.xstart=xpos;
          info.ystart=ypos;

          info.xend = RANDFRAC*fabs(2.0*wander_area_pos_offset) + wander_area_pos_offset;
          info.yend = RANDFRAC*fabs(2.0*wander_area_pos_offset) + wander_area_pos_offset;

          info.starttime = now;

          float xdel = info.xdel = info.xend-info.xstart;
          float ydel = info.ydel = info.yend-info.ystart;

          info.endtime = csqrt(xdel*xdel+ydel*ydel)/info.vel;

          info.timedel = info.endtime - info.starttime;

          const LVector3f rotate_axis(0.0, 0.0, 1.0);
          float ang = rad_2_deg(atan2(-xdel,ydel));

          info.rotmat.set_rotate_mat_normaxis(ang,rotate_axis);

          LVector3f translate_vec(xpos, ypos, 0.0);
          LMatrix4f tmat2 = LMatrix4f::translate_mat(translate_vec);

          xfm_mat = info.rotmat * tmat2;
        } else {
          LVector3f translate_vec(xpos, ypos, 0.0);
          xfm_mat = LMatrix4f::translate_mat(translate_vec);
        }

        info.node->set_transform(TransformState::make_mat(xfm_mat));

        info_arr.push_back(info);

        if((model_count % gridwidth) == 0) {
          xpos= -gridwidth*GRIDCELLSIZE/2.0;
          ypos+=GRIDCELLSIZE;
        } else {
          xpos+=GRIDCELLSIZE;
        }
      }
    }

    passnum++;
  } while (loaded_any);

  // Finally, remove the source models we loaded up.  Not a real big deal.
  for (fi = filenames.begin(); fi != filenames.end(); ++fi) {
    GriddedFilename &gf = (*fi);
    if (!gf._model.is_empty()) {
      gf._model.remove_node();
    }
  }
}

int
main(int argc, char *argv[]) {
  PandaFramework framework;
  vector_string args;
  framework.open_framework(argc, argv);
  framework.set_window_title("Gridded Object Viewer");

  if (!get_command_line_opts(argc, argv)) {
    return (1);
  }

  // Extract the remaining arguments into two lists of files: those
  // with a grid parameter, and those without.
  pvector<Filename> static_filenames;
  GriddedFilenames gridded_filenames;
  get_command_line_filenames(argc, argv, static_filenames, gridded_filenames);

  WindowFramework *window = framework.open_window();
  if (window != (WindowFramework *)NULL) {
    // We've successfully opened a window.

    window->enable_keyboard();
    window->setup_trackball();
    window->load_models(window->get_render(), static_filenames);

    GriddedInfoArray info_arr;
    load_gridded_models(window, gridded_filenames, info_arr);

    window->loop_animations();
    window->stagger_animations();

    framework.enable_default_keys();

    Thread *current_thread = Thread::get_current_thread();
    while (framework.do_frame(current_thread)) {
      if (!info_arr.empty() && gridmotiontype) {
        move_gridded_stuff(gridmotiontype, &info_arr[0], info_arr.size());
      }
    }
  }

  framework.report_frame_rate(nout);
  return (0);
}
