// Filename: deadrec_send.cxx
// Created by:  cary (15Nov00)
//
////////////////////////////////////////////////////////////////////
//
// PANDA 3D SOFTWARE
// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
//
// All use of this software is subject to the terms of the Panda 3d
// Software license.  You should have received a copy of this license
// along with this source code; you will also find a current copy of
// the license at http://www.panda3d.org/license.txt .
//
// To contact the maintainers of this program write to
// panda3d@yahoogroups.com .
//
////////////////////////////////////////////////////////////////////

#include "framework.h"
#include <renderRelation.h>
#include <queuedConnectionManager.h>
#include <modelPool.h>
#include <transformTransition.h>
#include <lerp.h>
#include <guiFrame.h>
#include <guiButton.h>
#include <guiSign.h>
#include <clockObject.h>

#include <dconfig.h>

NotifyCategoryDeclNoExport(deadrec);
NotifyCategoryDef(deadrec, "");

Configure(deadrec);

ConfigureFn(deadrec) {
}

static PT_Node smiley;
static RenderRelation* my_arc;
static LPoint3f my_pos;
static LVector3f my_vel;
string hostname = deadrec.GetString("deadrec-rec", "localhost");
int hostport = deadrec.GetInt("deadrec-rec-port", 0xdead);

static QueuedConnectionManager cm;
PT(Connection) conn;
ConnectionWriter* writer;

enum TelemetryToken { T_End = 1, T_Pos, T_Vel, T_Num, T_Time, T_Sync };

static inline NetDatagram& add_time(NetDatagram& d) {
  d.add_uint8(T_Time);
  d.add_float64(ClockObject::get_global_clock()->get_frame_time());
  return d;
}

static inline NetDatagram& add_pos(NetDatagram& d) {
  d.add_uint8(T_Pos);
  d.add_float64(my_pos[0]);
  d.add_float64(my_pos[1]);
  d.add_float64(my_pos[2]);
  return d;
}

static inline NetDatagram& add_vel(NetDatagram& d) {
  d.add_uint8(T_Vel);
  d.add_float64(my_vel[0]);
  d.add_float64(my_vel[1]);
  d.add_float64(my_vel[2]);
  return d;
}

static inline void send(NetDatagram& d) {
  d.add_uint8(T_End);
  writer->send(d, conn);
}

static void sync_clock(void) {
  NetDatagram d;
  d.add_uint8(T_Sync);
  send(add_time(d));
}

static bool verify_connection(void) {
  static float time = 0.;

  if (conn.is_null()) {
    if (time <= 0.) {
      NetAddress host;
      if (!host.set_host(hostname, hostport)) {
        deadrec_cat->warning() << "unknown host: " << hostname << endl;
        time = 100.;
        return false;
      }
      PT(Connection) local_conn = cm.open_TCP_client_connection(host, 5000);
      if (local_conn.is_null()) {
        deadrec_cat->warning() << "no connection" << endl;
        time = 5.;
        return false;
      }
      conn = local_conn;
      // sync clock
      sync_clock();
      return true;
    }
    time -= ClockObject::get_global_clock()->get_dt();
    return false;
  }
  if (cm.reset_connection_available()) {
    PT(Connection) local_conn;
    if (cm.get_reset_connection(local_conn)) {
      if (local_conn == conn) {
        cm.close_connection(conn);
        conn = (Connection*)0L;
        time = 5.;
        return false;
      } else
        deadrec_cat->error()
          << "got a report of a closed connection that I've never heard of"
          << endl;
    } else
      deadrec_cat->error()
        << "a closed connection was reported, but none was listed" << endl;
  }
  return true;
}

static void event_frame(CPT_Event) {
  // send deadrec data
  if (verify_connection()) {
    NetDatagram d;
    send(add_time(add_pos(d)));
  }
}

enum MotionType { M_None, M_Line, M_SLine, M_Box, M_SBox, M_Circle, M_SCircle,
                  M_Random, M_SRandom };
PT(AutonomousLerp) curr_lerp;
MotionType curr_type, switching_to;
PT(GuiButton) lineButton;
PT(GuiButton) slineButton;
PT(GuiButton) boxButton;
PT(GuiButton) sboxButton;
PT(GuiButton) circleButton;
PT(GuiButton) scircleButton;
PT(GuiButton) randomButton;
PT(GuiButton) srandomButton;

// the various motion generators

void update_smiley(void) {
  LMatrix4f mat = LMatrix4f::translate_mat(my_pos);
  my_arc->set_transition(new TransformTransition(mat));
}

class MyPosFunctor : public LPoint3fLerpFunctor {
public:
  MyPosFunctor(LPoint3f start, LPoint3f end) : LPoint3fLerpFunctor(start,
                                                                   end) {}
  MyPosFunctor(const MyPosFunctor& p) : LPoint3fLerpFunctor(p) {}
  virtual ~MyPosFunctor(void) {}
  virtual void operator()(float t) {
    LPoint3f p = interpolate(t);
    my_vel = p - my_pos;
    my_pos = p;
    update_smiley();
  }
public:
  // type stuff
  static TypeHandle get_class_type(void) { return _type_handle; }
  static void init_type(void) {
    LPoint3fLerpFunctor::init_type();
    register_type(_type_handle, "MyPosFunctor",
                  LPoint3fLerpFunctor::get_class_type());
  }
  virtual TypeHandle get_type(void) const { return get_class_type(); }
  virtual TypeHandle force_init_type(void) {
    init_type();
    return get_class_type();
  }
private:
  static TypeHandle _type_handle;
};
TypeHandle MyPosFunctor::_type_handle;

class MyRotFunctor : public FloatLerpFunctor {
public:
  MyRotFunctor(float start, float end) : FloatLerpFunctor(start, end) {}
  MyRotFunctor(const MyRotFunctor& p) : FloatLerpFunctor(p) {}
  virtual ~MyRotFunctor(void) {}
  virtual void operator()(float t) {
    float p = interpolate(t);
    LVector3f tmp = my_pos;
    float x, y;
    x = 10. * cos(p);
    y = 10. * sin(p);
    my_pos = LVector3f::rfu(x, 0., y);
    my_vel = my_pos - tmp;
    update_smiley();
  }
public:
  // type stuff
  static TypeHandle get_class_type(void) { return _type_handle; }
  static void init_type(void) {
    FloatLerpFunctor::init_type();
    register_type(_type_handle, "MyRotFunctor",
                  FloatLerpFunctor::get_class_type());
  }
  virtual TypeHandle get_type(void) const { return get_class_type(); }
  virtual TypeHandle force_init_type(void) {
    init_type();
    return get_class_type();
  }
private:
  static TypeHandle _type_handle;
};
TypeHandle MyRotFunctor::_type_handle;

inline float unit_rand(void) {
  return ((float)rand() / (float)RAND_MAX);
}

static void init_funcs(void) {
  static bool inited = false;
  if (!inited) {
    MyPosFunctor::init_type();
    MyRotFunctor::init_type();
    inited = true;
  }
}

static void run_line(bool smooth) {
  static bool where = false;
  LerpBlendType* blend;

  init_funcs();
  if (smooth)
    blend = new EaseInOutBlendType();
  else
    blend = new NoBlendType();
  if (where) {
    curr_lerp =
      new AutonomousLerp(new MyPosFunctor(my_pos, LPoint3f::rfu(10., 0., 0.)),
                         5., blend, &event_handler);
    curr_lerp->set_end_event("lerp_done");
    curr_lerp->start();
  } else {
    curr_lerp =
      new AutonomousLerp(new MyPosFunctor(my_pos, LPoint3f::rfu(-10., 0., 0.)),
                         5., blend, &event_handler);
    curr_lerp->set_end_event("lerp_done");
    curr_lerp->start();
  }
  where = !where;
}

static void run_box(bool smooth) {
  static int where = 0;
  LerpBlendType* blend;

  init_funcs();
  if (smooth)
    blend = new EaseInOutBlendType();
  else
    blend = new NoBlendType();
  switch (where) {
  case 0:
    curr_lerp =
      new AutonomousLerp(new MyPosFunctor(my_pos,
                                          LPoint3f::rfu(-10., 0., 10.)),
                         5., blend, &event_handler);
    where = 1;
    break;
  case 1:
    curr_lerp =
      new AutonomousLerp(new MyPosFunctor(my_pos, LPoint3f::rfu(10., 0., 10.)),
                         5., blend, &event_handler);
    where = 2;
    break;
  case 2:
    curr_lerp =
      new AutonomousLerp(new MyPosFunctor(my_pos,
                                          LPoint3f::rfu(10., 0., -10.)),
                         5., blend, &event_handler);
    where = 3;
    break;
  case 3:
    curr_lerp =
      new AutonomousLerp(new MyPosFunctor(my_pos,
                                          LPoint3f::rfu(-10., 0., -10.)),
                         5., blend, &event_handler);
    where = 0;
    break;
  default:
    deadrec_cat->error() << "I'm a tard and box::where got out of range ("
                         << where << ")" << endl;
    where = 0;
    run_box(smooth);
  }
  curr_lerp->set_end_event("lerp_done");
  curr_lerp->start();
}

static void run_circle(bool smooth) {
  LerpBlendType* blend;

  init_funcs();
  if (smooth)
    blend = new EaseInOutBlendType();
  else
    blend = new NoBlendType();
  curr_lerp = new AutonomousLerp(new MyRotFunctor(0., 6.283185), 5., blend,
                                 &event_handler);
  curr_lerp->set_end_event("lerp_done");
  curr_lerp->start();
}

static void run_random(bool smooth) {
  LerpBlendType* blend;

  init_funcs();
  if (smooth)
    blend = new EaseInOutBlendType();
  else
    blend = new NoBlendType();
  float x = (20. * unit_rand()) - 10.;
  float y = (20. * unit_rand()) - 10.;
  LVector3f p = LVector3f::rfu(x, 0., y);
  curr_lerp = new AutonomousLerp(new MyPosFunctor(my_pos, p), 5., blend,
                                 &event_handler);
  curr_lerp->set_end_event("lerp_done");
  curr_lerp->start();
}

static void handle_lerp(void) {
  if (curr_lerp != (AutonomousLerp*)0L)
    curr_lerp->stop();
  curr_lerp = (AutonomousLerp*)0L;
  switch (curr_type) {
  case M_None:
    break;
  case M_Line:
    run_line(false);
    break;
  case M_SLine:
    run_line(true);
    break;
  case M_Box:
    run_box(false);
    break;
  case M_SBox:
    run_box(true);
    break;
  case M_Circle:
    run_circle(false);
    break;
  case M_SCircle:
    run_circle(true);
    break;
  case M_Random:
    run_random(false);
    break;
  case M_SRandom:
    run_random(true);
    break;
  default:
    deadrec_cat->error() << "unknown motion type (" << (int)curr_type << ")"
                         << endl;
  }
}

static void make_active(void) {
  switch (curr_type) {
  case M_None:
    break;
  case M_Line:
    lineButton->up();
    break;
  case M_SLine:
    slineButton->up();
    break;
  case M_Box:
    boxButton->up();
    break;
  case M_SBox:
    sboxButton->up();
    break;
  case M_Circle:
    circleButton->up();
    break;
  case M_SCircle:
    scircleButton->up();
    break;
  case M_Random:
    randomButton->up();
    break;
  case M_SRandom:
    srandomButton->up();
    break;
  default:
    deadrec_cat->error() <<" unknown motion type (" << (int)curr_type << ")"
                         << endl;
  }
}

static void event_button_up(CPT_Event e) {
  string s = e->get_name();
  s = s.substr(0, s.find("-up"));
  event_handler.remove_hook(s + "-up", event_button_up);
  event_handler.remove_hook(s + "-up-rollover", event_button_up);
  make_active();
  switch (switching_to) {
  case M_Line:
    lineButton->inactive();
    break;
  case M_SLine:
    slineButton->inactive();
    break;
  case M_Box:
    boxButton->inactive();
    break;
  case M_SBox:
    sboxButton->inactive();
    break;
  case M_Circle:
    circleButton->inactive();
    break;
  case M_SCircle:
    scircleButton->inactive();
    break;
  case M_Random:
    randomButton->inactive();
    break;
  case M_SRandom:
    srandomButton->inactive();
    break;
  default:
    deadrec_cat->error() << "switching to invalid motion type ("
                         << (int)switching_to << ")" << endl;
  }
  curr_type = switching_to;
  handle_lerp();
}

static void event_button_down(CPT_Event e) {
  string s = e->get_name();
  s = s.substr(0, s.find("-down"));
  if (s == "line") {
    if (curr_type != M_Line) {
      switching_to = M_Line;
      event_handler.add_hook(s + "-up", event_button_up);
      event_handler.add_hook(s + "-up-rollover", event_button_up);
    }
  } else if (s == "s-line") {
    if (curr_type != M_SLine) {
      switching_to = M_SLine;
      event_handler.add_hook(s + "-up", event_button_up);
      event_handler.add_hook(s + "-up-rollover", event_button_up);
    }
  } else if (s == "box") {
    if (curr_type != M_Box) {
      switching_to = M_Box;
      event_handler.add_hook(s + "-up", event_button_up);
      event_handler.add_hook(s + "-up-rollover", event_button_up);
    }
  } else if (s == "s-box") {
    if (curr_type != M_SBox) {
      switching_to = M_SBox;
      event_handler.add_hook(s + "-up", event_button_up);
      event_handler.add_hook(s + "-up-rollover", event_button_up);
    }
  } else if (s == "circle") {
    if (curr_type != M_Circle) {
      switching_to = M_Circle;
      event_handler.add_hook(s + "-up", event_button_up);
      event_handler.add_hook(s + "-up-rollover", event_button_up);
    }
  } else if (s == "s-circle") {
    if (curr_type != M_SCircle) {
      switching_to = M_SCircle;
      event_handler.add_hook(s + "-up", event_button_up);
      event_handler.add_hook(s + "-up-rollover", event_button_up);
    }
  } else if (s == "random") {
    if (curr_type != M_Random) {
      switching_to = M_Random;
      event_handler.add_hook(s + "-up", event_button_up);
      event_handler.add_hook(s + "-up-rollover", event_button_up);
    }
  } else if (s == "s-random") {
    if (curr_type != M_SRandom) {
      switching_to = M_SRandom;
      event_handler.add_hook(s + "-up", event_button_up);
      event_handler.add_hook(s + "-up-rollover", event_button_up);
    }
  } else {
    deadrec_cat->error() << "got invalid button event '" << s << "'" << endl;
  }
}

static inline GuiButton* make_button(const string& name, Node* font,
                                     EventHandler& eh) {
  GuiLabel* l1 = GuiLabel::make_simple_text_label(name, font);
  GuiLabel* l2 = GuiLabel::make_simple_text_label(name, font);
  GuiLabel* l3 = GuiLabel::make_simple_text_label(name, font);
  GuiLabel* l4 = GuiLabel::make_simple_text_label(name, font);
  GuiLabel* l5 = GuiLabel::make_simple_text_label(name, font);
  // up
  l1->set_background_color(1., 1., 1., 0.);
  // up-rollover
  l2->set_background_color(1., 1., 1., 0.5);
  // down
  l3->set_background_color(1., 1., 1., 0.);
  // down-rollover
  l4->set_background_color(1., 1., 1., 0.5);
  // 'inactive'
  l5->set_background_color(1., 0., 0., 0.7);
  GuiButton* b1 = new GuiButton(name, l1, l2, l3, l4, l5);
  eh.add_hook(name + "-down", event_button_down);
  eh.add_hook(name + "-down-rollover", event_button_down);
  return b1;
}

static void deadrec_setup(EventHandler& eh) {
  static bool done = false;
  if (done)
    return;
  // load smiley and put it in the scenegraph
  smiley = ModelPool::load_model("smiley");
  nassertv(smiley != (Node*)0L);
  my_arc = new RenderRelation(render, smiley);

  writer = new ConnectionWriter(&cm, 0);

  // create an interface
  GuiManager* mgr = GuiManager::get_ptr(main_win, mak);
  PT_Node font = ModelPool::load_model("ttf-comic");
  GuiFrame* f1 = new GuiFrame("motions");
  GuiLabel* l1 = GuiLabel::make_simple_text_label("motion:", font);
  l1->set_background_color(1., 1., 1., 0.);
  GuiSign* s1 = new GuiSign("motion", l1);
  s1->set_scale(0.08);
  f1->add_item(s1);
  lineButton = make_button("line", font, eh);
  lineButton->set_scale(0.08);
  f1->add_item(lineButton);
  slineButton = make_button("s-line", font, eh);
  slineButton->set_scale(0.08);
  f1->add_item(slineButton);
  boxButton = make_button("box", font, eh);
  boxButton->set_scale(0.08);
  f1->add_item(boxButton);
  sboxButton = make_button("s-box", font, eh);
  sboxButton->set_scale(0.08);
  f1->add_item(sboxButton);
  circleButton = make_button("circle", font, eh);
  circleButton->set_scale(0.08);
  f1->add_item(circleButton);
  scircleButton = make_button("s-circle", font, eh);
  scircleButton->set_scale(0.08);
  f1->add_item(scircleButton);
  randomButton = make_button("random", font, eh);
  randomButton->set_scale(0.08);
  f1->add_item(randomButton);
  srandomButton = make_button("s-random", font, eh);
  srandomButton->set_scale(0.08);
  f1->add_item(srandomButton);
  f1->pack_item(lineButton, GuiFrame::UNDER, s1);
  f1->pack_item(lineButton, GuiFrame::ALIGN_LEFT, s1);
  f1->pack_item(slineButton, GuiFrame::UNDER, s1);
  f1->pack_item(slineButton, GuiFrame::RIGHT, lineButton, 0.02);
  f1->pack_item(boxButton, GuiFrame::UNDER, s1);
  f1->pack_item(boxButton, GuiFrame::RIGHT, slineButton, 0.02);
  f1->pack_item(sboxButton, GuiFrame::UNDER, s1);
  f1->pack_item(sboxButton, GuiFrame::RIGHT, boxButton, 0.02);
  f1->pack_item(circleButton, GuiFrame::UNDER, s1);
  f1->pack_item(circleButton, GuiFrame::RIGHT, sboxButton, 0.02);
  f1->pack_item(scircleButton, GuiFrame::UNDER, s1);
  f1->pack_item(scircleButton, GuiFrame::RIGHT, circleButton, 0.02);
  f1->pack_item(randomButton, GuiFrame::UNDER, s1);
  f1->pack_item(randomButton, GuiFrame::RIGHT, scircleButton, 0.02);
  f1->pack_item(srandomButton, GuiFrame::UNDER, s1);
  f1->pack_item(srandomButton, GuiFrame::RIGHT, randomButton, 0.02);
  f1->align_to_left(0.05);
  f1->align_to_top(0.05);
  f1->recompute();
  f1->manage(mgr, eh);
}

static void event_lerp(CPT_Event) {
  handle_lerp();
}

static void deadrec_keys(EventHandler& eh) {
  deadrec_setup(eh);

  eh.add_hook("NewFrame", event_frame);
  eh.add_hook("lerp_done", event_lerp);
}

int main(int argc, char* argv[]) {
  define_keys = &deadrec_keys;
  return framework_main(argc, argv);
}
