/*
**  wt -- a 3d game engine
**
**  Copyright (C) 1994 by Chris Laurel
**  email:  claurel@mr.net
**  snail mail:  Chris Laurel, 5700 W Lake St #208,  St. Louis Park, MN  55416
**
**  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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#include <tcl/tcl.h>

#include "wt.h"
#include "error.h"
#include "wtmem.h"
#include "fixed.h"
#include "view.h"
#include "object.h"


#define OBJECT_POSITION          "-position"
#define OBJECT_VELOCITY          "-velocity"
#define OBJECT_ANGLE             "-angle"
#define OBJECT_ANGULAR_VELOCITY  "-angular_velocity"
#define OBJECT_MASS              "-mass"
#define OBJECT_DRAG              "-drag"
#define OBJECT_SIZE              "-size"


static int set_vector(Tcl_Interp *interp,
		      char *vector, double *v1, double *v2, double *v3);
     
/* Object whose viewpoint we will see from. */
static Object *viewpoint_object = NULL;
/* Object which we will control from the console. */
static Object *controlled_object = NULL;


static int object_command(ClientData clientData, Tcl_Interp *interp,
			  int argc, char *argv[]);
static int object_change(Tcl_Interp *interp, Object *o,
			 int argc, char *argv[]);
static int object_query(Tcl_Interp *interp, Object *o, char *param);
static int object_force(Tcl_Interp *interp, Object *o, char *force);
static int object_torque(Tcl_Interp *interp, Object *o, char *torque);
static int object_attachviewpoint(Object *o);
static int object_attachcontrol(Object *o);


Object *get_viewpoint_object(void)
{
     return viewpoint_object;
}


Object *get_controlled_object(void)
{
     return controlled_object;
}


int parse_create(ClientData clientData, Tcl_Interp *interp,
		 int argc, char *argv[])
{
     Object *o;
     Object template = { 1.0,
			 1.0, 1.0, 1.0,
 		         0.0, 0.0, 0.0,
			 0.0, 0.0, 0.0,
			 0.0, 0.0,
			 0.6 };
     char *name;
     int result;


     if (argc < 2) {
	  interp->result = "too few arguments";
	  return TCL_ERROR;
     }

     name = argv[1];

     o = new_object(0.0, 0.0, 0.0, 0.0, 0.0);
     *o = template;

     result = object_change(interp, o, argc - 2, &argv[2]);

     if (result != TCL_OK) {
	  wtfree(o);
	  return TCL_ERROR;
     }

     Tcl_CreateCommand(interp, name, object_command, (ClientData) o, NULL);

     return TCL_OK;
}


static int object_command(ClientData clientData, Tcl_Interp *interp,
			  int argc, char *argv[])
{
     Object *o = (Object *) clientData;

     
     if (argc < 2) {
	  interp->result =
	       "Wrong # args: should be 'object option [arg arg ...]'";
	  return TCL_ERROR;
     }

     if (strcmp(argv[1], "change") == 0) {
	  return object_change(interp, o, argc - 2, &argv[2]);
     } else if (strcmp(argv[1], "query") == 0) {
	  if (argc != 3) {
	       interp->result = "wrong # args to option force";
	       return TCL_ERROR;
	  } else
	       return object_query(interp, o, argv[2]);
     } else if (strcmp(argv[1], "force") == 0) {
	  if (argc != 3) {
	       interp->result = "wrong # args to option force";
	       return TCL_ERROR;
	  } else
	       return object_force(interp, o, argv[2]);
     } else if (strcmp(argv[1], "torque") == 0) {
	  if (argc != 3) {
	       interp->result = "wrong # args to option torque";
	       return TCL_ERROR;
	  } else
	       return object_torque(interp, o, argv[2]);
     } else if (strcmp(argv[1], "attachviewpoint") == 0)
	  return object_attachviewpoint(o);
     else if (strcmp(argv[1], "attachcontrol") == 0)
	  return object_attachcontrol(o);

     interp->result = "bad option";

     return TCL_ERROR;
}


/**** Subfunctions of object command ****/

/* Change object parameters directly. */
static int object_change(Tcl_Interp *interp, Object *o, int argc, char *argv[])
{
     int i;

     for (i = 0; i < argc; i += 2) {

	  if (argc < i + 1) {
	       interp->result = "wrong # arguments";
	       return TCL_ERROR;
	  }

	  if (strcmp(argv[i], OBJECT_POSITION) == 0) {

	       if (set_vector(interp, argv[i + 1],
			      &o->x, &o->y, &o->z) != TCL_OK) {
		    interp->result = "invalid position";
		    return TCL_ERROR;
	       }

	  } else if (strcmp(argv[i], OBJECT_VELOCITY) == 0) {

	       if (set_vector(interp, argv[i + 1],
			      &o->dx, &o->dy, &o->dz) != TCL_OK) {
		    interp->result = "invalid velocity";
		    return TCL_ERROR;
	       }

	  } else if (strcmp(argv[i], OBJECT_ANGLE) == 0) {

	       if (Tcl_GetDouble(interp, argv[i + 1], &o->angle) != TCL_OK) {
		    interp->result = "invalid angle";
		    return TCL_ERROR;
	       }

	  } else if (strcmp(argv[i], OBJECT_ANGULAR_VELOCITY) == 0) {

	       if (Tcl_GetDouble(interp, argv[i + 1],
				 &o->angular_v) != TCL_OK) {
		    interp->result = "invalid angular velocity";
		    return TCL_ERROR;
	       }

	  } else if (strcmp(argv[i], OBJECT_SIZE) == 0) {

	       if (set_vector(interp, argv[i + 1],
			      &o->xsize, &o->ysize, &o->height) != TCL_OK) {
		    interp->result = "invalid size";
		    return TCL_ERROR;
	       }
	       
	  } else if (strcmp(argv[i], OBJECT_DRAG) == 0) {

	       if (Tcl_GetDouble(interp, argv[i + 1], &o->drag) != TCL_OK) {
		    interp->result = "invalid drag";
		    return TCL_ERROR;
	       }
	       
	  } else if (strcmp(argv[i], OBJECT_MASS) == 0) {

	       if (Tcl_GetDouble(interp, argv[i + 1], &o->mass) != TCL_OK) {
		    interp->result = "invalid mass";
		    return TCL_ERROR;
	       }
	       
	  } else {

	       interp->result = "unknown object parameter";
	       return TCL_ERROR;

	  }

     }

     return TCL_OK;
}


/* Change object parameters directly. */
static int object_query(Tcl_Interp *interp, Object *o, char *param)
{
     if (strcmp(param, OBJECT_POSITION) == 0)
	  sprintf(interp->result, "{%.5f %.5f %.5f}", o->x, o->y, o->z);
     else if (strcmp(param, OBJECT_VELOCITY) == 0)
	  sprintf(interp->result, "{%.5f %.5f %.5f}", o->dx, o->dy, o->dz);
     else if (strcmp(param, OBJECT_ANGLE) == 0)
	  sprintf(interp->result, "%.5f", o->angle);
     else if (strcmp(param, OBJECT_ANGULAR_VELOCITY) == 0)
	  sprintf(interp->result, "%.5f", o->angular_v);
     else if (strcmp(param, OBJECT_SIZE) == 0)
	  sprintf(interp->result, "{%.5f %.5f %.5f}",
		  o->xsize, o->ysize, o->height);
     else if (strcmp(param, OBJECT_DRAG) == 0)
	  sprintf(interp->result, "%.5f", o->drag);
     else if (strcmp(param, OBJECT_MASS) == 0)
	  sprintf(interp->result, "%.5f", o->mass);
     else {
	  interp->result = "unknown object parameter";
	  return TCL_ERROR;
     }

     return TCL_OK;
}


/* Apply a force to the object. */
static int object_force(Tcl_Interp *interp, Object *o, char *force)
{
     double fx, fy, fz;

     if (set_vector(interp, force, &fx, &fy, &fz) != TCL_OK) {
	  interp->result = "invalid force";
	  return TCL_ERROR;
     }

     object_apply_force(o, fx, fy, fz);

     return TCL_OK;
}


/* Apply a torque to the object */
static int object_torque(Tcl_Interp *interp, Object *o, char *torque)
{
     double t;

     if (Tcl_GetDouble(interp, torque, &t) != TCL_OK) {
	  interp->result = "invalid torque";
	  return TCL_ERROR;
     }

     object_apply_torque(o, t);

     return TCL_OK;
}


static int object_attachviewpoint(Object *o)
{
     viewpoint_object = o;

     return TCL_OK;
}	


static int object_attachcontrol(Object *o)
{
     controlled_object = o;

     return TCL_OK;
}


static int set_vector(Tcl_Interp *interp,
		      char *vector, double *v1, double *v2, double *v3)
{
     int argc;
     char **argv;
     double x, y, z;

     if (Tcl_SplitList(interp, vector, &argc, &argv) != TCL_OK || argc != 3)
	  return TCL_ERROR;


     if (Tcl_GetDouble(interp, argv[0], &x) != TCL_OK ||
	 Tcl_GetDouble(interp, argv[1], &y) != TCL_OK ||
	 Tcl_GetDouble(interp, argv[2], &z) != TCL_OK) {
	  wtfree(argv);
	  return TCL_ERROR;
     }
	 
     *v1 = x;
     *v2 = y;
     *v3 = z;

     wtfree(argv);

     return TCL_OK;
}
