/*--------------------------------------------------------------------------
 * File: 3ds.c
 * Written by: Fredrik Kling, 1997-06-14
 * Description: 3Dstudio object loader (NOT scenes)
 *
 * Updates:
 * -- Date -- | ----- Name ----- |-- Did what....
 * 1997-06-14 | Fredrik Kling    | Implementation
 * 1997-06-16 | Fredrik Kling    | Error fix....
 *
 * Todo:
 *
 * Surface import! (Attributes etc...)
 *
 * Comments:
 *   Implementation „r ganska hobbex...  * URK *
 *
 -------------------------------------------------------------------------------*/

#include "system/xstdlib.h"
#include "system/xstdio.h"
#include "system/xstring.h"
#include "system/xmath.h"
#include "formats/v3o.h"
#include "formats/3ds.h"
#include "formats/v3d.h"
#include "objects/object.h"
#include "vmath/vector.h"
#include "misc/col.h"
#include "misc/spline.h"

typedef struct s_OBJNAME OBJNAME;
struct s_OBJNAME
{
	char name[64];
	OBJECT *obj;
};


#if !defined(M_PI)
#define M_PI 3.141592654
#endif

static int debug = TRUE;
#define dprintf if (debug) printf

int num_object=0;
int use_objname=0;		// 0 - Dont use object names as filenames...
											// 1 - Use 'em...

static OBJNAME namelist[1024];  // MAXOBJECTS   should be alright

typedef enum
{
	ERR_NOERR,
	ERR_NOFILE,
	ERR_NOMEM,
	ERR_CUSTOM,
} ERROR;

static ERROR error = ERR_NOERR;
static char errstr[256];


static void maptextures(V3O *obj);
//static void fixnormals(V3O *obj);
//static void centerobject(V3O *obj);

OBJECT *getobjname (V3D *v3d,char *name)
{
	int i;

	for (i=0;i<num_object;i++) if (!strcmp (name,namelist[i].name)) return namelist[i].obj;

	return NULL;
}
static void skip (XFILE *f, int next)
{
	char dummy;
	while ((xftell(f) < next) && (!xfeof (f))) xfread (&dummy,1,1,f);
}
static void chunk (XFILE *f)
{
	short int id=0;
	int subend=0;
	xfread (&id, 2, 1, f);
  xfread (&subend, 4, 1, f);
}
/*#############################################################################
#
# Docamera
#
#############################################################################*/
static CAMERA *docamera (XFILE *f, int next)
{
	WORD num,id;
	DWORD subend;
	VECTOR pos,target;
	float focal,banking;
	CAMERA *cam;


	xfread (&pos.x,sizeof (float),1,f);
	xfread (&pos.y,sizeof (float),1,f);
	xfread (&pos.z,sizeof (float),1,f);

	xfread (&target.x,sizeof (float),1,f);
	xfread (&target.y,sizeof (float),1,f);
	xfread (&target.z,sizeof (float),1,f);

	xfread (&focal,sizeof (float),1,f);
	xfread (&banking,sizeof (float),1,f);

	dprintf ("  - Camera pos....: %f,%f,%f\n",pos.x,pos.y,pos.z);
	dprintf ("  - Camera target.: %f,%f,%f\n",target.x,target.y,target.z);
	dprintf ("  - Camera focus..: %f\n",focal);
	dprintf ("  - Camera banking: %f\n",banking);

	cam = camera_create ();

	// Always this kind in 3d-studio...
	cam -> flags += CAMERAFLAG_POSTARGET;


	while ((xftell (f) < next) && (!xfeof (f)))
	{
		xfread (&id, 2, 1, f);
		xfread (&subend, 4, 1, f);
		subend = xftell (f) - 6 + subend;

		dprintf ("   obj - id %x\n",id);


		switch (id)
		{
			default :
					dprintf ("[#] Camera chunk: %x\n",id);
					skip (f,subend);
					break;
		}
	}
	return cam;

}
/*#############################################################################
#
# Vertextlist Chunk
#
#############################################################################*/

static int vlist(XFILE *f,V3O *obj)
{
	short int num;
	int i;

	xfread (&num, 2, 1, f);
	//vobj -> vertices = (int) num;
	obj->numvertex = (int) num;

	dprintf("[#] Found points: %d\n", num);
	printf("[-] Found %d vertices\n", num);

	// Allocate memory for functions...

  obj->orgvertex=(VECTOR *)xsafe_malloc (sizeof(VECTOR) * obj->numvertex);
	obj->rotvertex=(VECTOR *)xsafe_malloc (sizeof(VECTOR) * obj->numvertex);
	obj->projvertex=(VECTOR *)xsafe_malloc (sizeof(VECTOR) * obj->numvertex);
	obj->orgnormal=(VECTOR *)xsafe_malloc (sizeof(VECTOR) * obj->numvertex);
	obj->rotnormal=(VECTOR *)xsafe_malloc (sizeof(VECTOR) * obj->numvertex);

	if (obj->orgvertex == NULL || obj->rotvertex == NULL || obj->projvertex == NULL || obj->orgnormal==NULL || obj->rotnormal==NULL)
	{
		error = ERR_NOMEM;
		return 0;
	}

	for ( i=0; i<obj->numvertex; i++)
	{
		obj->orgvertex[i].x = (float)xfrbe_float( f);
		obj->orgvertex[i].z = (float)xfrbe_float( f);
		obj->orgvertex[i].y = -(float)xfrbe_float( f);
	}

	dprintf("[#] Done\n");
	return 1;
}

/*#############################################################################
#
# Surface Names Chunk
#
#############################################################################*/

static int srfs(XFILE *f,V3O *obj, int next)
{
	short int num,id,dummy;
	int i,subend,a,b,c;
	int material=1;

	xfread (&num, 2, 1, f);
	dprintf ("[-] Found %d surfaces\n",num);

	obj->numsurface = (int)num;


	if ( !material)
	{
		printf("[&] Empty surface chunk, skipping!\n");
		return 1;
	}

	obj->nummaterial = material;
	if ((obj->material = (V3OMATERIAL *)xmalloc( obj->nummaterial * sizeof(V3OMATERIAL)))==NULL)
	{
		error = ERR_NOMEM;
		return 0;
	}

	for( i=0; i<obj->nummaterial; i++)
	{
    obj->material[i].flags = V3OMATERIALFLAG_FLAT;
		obj->material[i].color.r = 200;
		obj->material[i].color.g = 200;
		obj->material[i].color.b = 200;
		sprintf (obj->material[i].name,"Std Mtrl!");
	}

  if ((obj->surface = (V3OSURFACE *)xmalloc( obj->numsurface * sizeof( V3OSURFACE)))==NULL)
	{
		error = ERR_NOMEM;
		return 0;
	}

	for (i=0;i<obj->numsurface;i++)
	{
  	obj->surface[i].flags = V3OSURFACEFLAG_POLY;
		a = xfrbe_word( f);
		b = xfrbe_word( f);
		c = xfrbe_word( f);

		obj->surface[i].v1 = c;
		obj->surface[i].v2 = b;
		obj->surface[i].v3 = a;
		dummy = xfrbe_word (f); // Read flags...  Unused...

		obj->surface[i].material = 0; // Eller ska det st† 1 h„r?
	}

	// Skip subchunks of no intrest...
	while ((xftell (f) < next) && (!xfeof (f)))
	{
		xfread (&id, 2, 1, f);
		xfread (&subend, 4, 1, f);
		subend = xftell (f) - 6 + subend;
		xfseek (f, subend, SEEK_SET);
	}
	dprintf("[#] Done\n");
	return 1;
}

/*#############################################################################
#
# Trimesh chunk
#
#############################################################################*/
static V3O *trimesh (XFILE *f, int next, char *objn)
{
	short int id;
	int subend;
	V3O *obj;


  if ((obj = v3o_create ())==NULL)
	{
		error = ERR_CUSTOM;
		strcpy (errstr,v3o_geterror());
		return NULL;
	}


	dprintf ("        - Trimesh.\n");

	while ((xftell (f) < next) && (!xfeof (f)))
	{
		xfread (&id, 2, 1, f);
		xfread (&subend, 4, 1, f);
		subend = xftell (f) - 6 + subend;

		switch (id)
		{
			case 0x4110 : if (!vlist (f, obj)) return NULL; break;						/* Chunk $4110 - Vertex list. */
			case 0x4120 : if (!srfs (f,obj,subend)) return NULL; break;
			case 0x4140 : dprintf ("          - Texture coords: %x\n",xftell (f)); break;
			case 0x4160 : dprintf ("          - Mesh matrix   : %x\n",xftell (f)); break;
			case 0x4170 : dprintf ("          - Texture info  : %x\n",xftell (f)); break;
			case 0x4165 : dprintf ("          - Mesh color    : %x\n",xftell (f)); break;
		}
		xfseek (f, subend, SEEK_SET);
  }
	v3o_center (obj);
	return obj;
}
/*#############################################################################
#
# Objekt chunk
#
#############################################################################*/
static int obj_3ds (XFILE *f, int next,V3D *v3d)
{
	short int id;
	int subend,i;
	char obj_name[256],c;
	char sname[256];
	OBJECT *obj;



	for (i=0;c!='\0';i++)
	{
		xfread (&obj_name[i], 1, 1,f);
		c=obj_name[i];
	}
	dprintf ("-------------------------------------------------\n");

	dprintf ("þ Named object!:  %x,%x\n",xftell (f),next);
	dprintf ("    - Name: %s\n",obj_name);


	obj = object_create ();
	sprintf (namelist[num_object].name,"%s",obj_name);

	while ((xftell(f) < next) && (!xfeof(f)))
	{
		xfread (&id, 2, 1, f);
		xfread (&subend, 4, 1, f);
		subend = xftell (f) - 6 + subend;

		dprintf ("   obj - id %x\n",id);

		switch (id)
		{
			case 0x4100 : if ((obj->v3o=trimesh (f, subend, obj_name))==NULL)
										{
											error = ERR_CUSTOM;
											strcpy (errstr,"Unable to create object!.");
											return FALSE;
										}
										obj->type = T_V3O;
										v3d_obj2scene (v3d,obj);
										namelist[num_object].obj = obj;

/*
										v3o_center (obj);
										if (!use_objname)
										  sprintf (sname,"obj%i.v3o",num_object);
											else sprintf (sname,"%s.v3o",obj_name);
									  printf ("[-] Saving v3o object: %s\n",sname);
										v3o_save (sname,obj,V3O_SAVE_NORMAL);
										v3o_free (obj);
*/
										break;
			case 0x4600 : dprintf ("[#] Lightchunk, unsupported.\n"); break;
      case 0x4700 : v3d->activecamera = docamera (f, subend);
										namelist[num_object].obj = (OBJECT *) v3d->activecamera;
										break;
		}
		xfseek (f, subend, SEEK_SET);
	}

	num_object++;
	return TRUE;
}
/*#############################################################################
#
# Mesh chunk...
#
#############################################################################*/
static int mesh (XFILE *f,int next,V3D *v3d)
{
//	V3O *obj;
	short int id=0;
	int subend=0;

	dprintf ("[#] Meshdata : %x, %x\n",xftell (f),next);

	while ((xftell(f) < next) && (!xfeof (f)))
	{
		xfread (&id, 2, 1, f);
		xfread (&subend, 4, 1, f);
		subend = xftell (f) - 6 + subend;

		switch (id)
		{
			case 0x4000 : if (obj_3ds (f,subend,v3d)==FALSE) return FALSE; break;
			case 0x5000 : dprintf ("[#] 2D-poly\n"); break;
			case 0x6000 : dprintf ("[#] 3D-path\n"); break;
			case 0xa000 : dprintf ("[#] Material group\n"); break;
		}
		xfseek (f, subend, SEEK_SET);
	}

	return TRUE;
}
/*#############################################################################
	#
	# Keyframer node data
	#
	#############################################################################*/
static ANIM *kf_objnod (XFILE *f, int next,WORD oid,V3D *v3d)
{
	WORD id=0,node_id,nodeflag,nodepar;
	KEY *k;
	char nodename[20],instname[20];
	WORD flg;
	int subend=0,i,j,obj_num;
//	float axis[3];
	float ang;
	DWORD max,min,knum,tid;
	ANIM *anim;
	int key_max=0;
	int key_pos,key_rot,key_scl;
	VECTOR pos,rot,pivot,scale,axis;
	OBJECT *obj;
	CAMERA *cam;

	obj_num = 0;
	anim = NULL;
	while ((xftell(f) < next) && (!xfeof (f)))
	{
		xfread (&id, 2, 1,f);
		xfread (&subend, 4, 1, f);
		subend = xftell (f) - 6 + subend;
//		dprintf ("       obj - id %x,pos - %x\n",id,xftell (f));

		// If a node tag - read common tag info...
		if ((id == 0xb020) || (id == 0xb021) || (id==0xb022) || (id==0xb023) || (id==0xb024) || (id==0xb025) || (id==0xb027) || (id==0xb028))
		{
			xfread (&flg,sizeof (WORD),1,f);
			xfread (&min,sizeof (DWORD),1,f);
			xfread (&max,sizeof (DWORD),1,f);
			xfread (&knum,sizeof (DWORD),1,f);
		}

		switch (id)
		{
			case 0xB030 : // Node ID
						//chunk (f);
						xfread (&node_id,2,1,f);
						dprintf ("    [#] Node_ID: %d\n",node_id);
						break;
			case 0xB010 : // Node HDR
						//chunk (f);
						dprintf ("    [#] NodeHeader..\n");
						xfread (nodename,20,1,f);
						xfread (&nodeflag,2,1,f);
						xfread (&nodepar,2,1,f);
						dprintf ("       - Nodename: %s\n",nodename);
						dprintf ("       - Nodeflag: %d\n",nodeflag);
						if (nodepar==0xffff)
							dprintf ("     - No parent object.\n");
						else
							dprintf ("     - Node has parent: %x\n",nodepar);

						//obj = v3d_objfrmname (v3d,nodename);
						obj = getobjname (v3d,nodename);			// Replace with above when implemented...

						// Treat diffrent if camera...
						dprintf ("     - Oid: %x\n",oid);
						if ((oid == 0xb004) || (oid == 0xb003))
						{
							cam = (CAMERA *)getobjname (v3d,nodename);
							dprintf ("     - Camera animation!\n");
							if (cam->anim==NULL) anim = cam -> anim = spline_createanim (9);
								else anim = cam->anim;
						} else
							{
								anim = obj->anim = spline_createanim (9);		// Always create nine infochannels..
								obj -> flags += OBJFLAG_VECROT;	// Activate vector rotation...
							}

						obj_num++;
						break;
			case 0x8000 : // App data
						dprintf ("    [#] Appdata, skipping..\n");
						skip (f,subend);
						break;
			case 0xB011 : // Instname
						xfread (instname,20,1,f);
						dprintf ("    [#] Instance name: %s\n",instname);
						break;
			case 0xB013 : // Pivot

						//for (i=0;i<3;i++)	xfread (&pivot[i],sizeof (float),1,f);
						xfread (&pivot.x,sizeof (float), 1, f);
						xfread (&pivot.y,sizeof (float), 1, f);
						xfread (&pivot.z,sizeof (float), 1, f);

						dprintf ("    [#] Pivotpoint: (%f,%f,%f)\n",pivot.x,pivot.y,pivot.z);
						break;
			case 0xB014 : // Bounding box data
						dprintf ("    [#] Bounding box data, skipping..\n");
						skip (f,subend);
						break;
			case 0xB020 : // POS_TAG
						dprintf ("    [#] Position keys found, reading: %i,%i,%i keys\n",min,max,knum);
						key_pos = knum;
						for (i=0;i<knum;i++)
						{
						  xfread (&tid,sizeof (DWORD),1,f);
							xfread (&flg,sizeof (WORD),1,f);

							k = spline_addkey (anim,tid);

							xfread (&pos.x,sizeof (float),1,f);
							xfread (&pos.y,sizeof (float),1,f);
							xfread (&pos.z,sizeof (float),1,f);

							if (oid!=0xB004) // check old-id if we have a Camera-target position....
							{
								k->value[ANIM_POS_X] = pos.x;
								k->value[ANIM_POS_Y] = -pos.z;
								k->value[ANIM_POS_Z] = pos.y;
							} else
								{
									k->value[ANIM_TARGET_X] = pos.x;
									k->value[ANIM_TARGET_Y] = -pos.z;
									k->value[ANIM_TARGET_Z] = pos.y;
								}
							dprintf ("       key[%3i]: Time: %i, flags: %i, (%f,%f,%f)\n",i,tid,flg,pos.x,pos.y,pos.z);
						}
						break;
			case 0xB021 : //ROT_TAG
						dprintf ("    [#] Rotation keys found, reading: %i,%i,%i keys\n",min,max,knum);
						key_rot = knum;
						for (i=0;i<knum;i++)
						{
						  xfread (&tid,sizeof (DWORD),1,f);
							xfread (&flg,sizeof (WORD),1,f);
							xfread (&ang,sizeof (float),1,f);

							k = spline_addkey (anim,tid);

							//for (j=0;j<3;j++)	xfread (&axis[j],sizeof (float),1,f);

							xfread (&axis.x,sizeof (float), 1, f);
							xfread (&axis.y,sizeof (float), 1, f);
							xfread (&axis.z,sizeof (float), 1, f);

							k->value[ANIM_ANG_X] = axis.x;
							k->value[ANIM_ANG_Y] = axis.y;
							k->value[ANIM_ANG_Z] = axis.z;

							dprintf ("       key[%3i]: Time: %i, flags: %i, Angle:%f Axis(%f,%f,%f)\n",i,tid,flg,ang,axis.x,axis.y,axis.z);
						}
	 					break;
			case 0xB022 : //SCALE_TAG
						dprintf ("    [#] Scaling keys found, reading: %i,%i,%i keys\n",min,max,knum);
						key_scl = knum;
						for (i=0;i<knum;i++)
						{
						  xfread (&tid,sizeof (DWORD),1,f);
							xfread (&flg,sizeof (WORD),1,f);

							k = spline_addkey (anim,tid);

							xfread (&scale.x,sizeof (float),1,f);
							xfread (&scale.y,sizeof (float),1,f);
							xfread (&scale.z,sizeof (float),1,f);

							k->value[ANIM_SCL_X] = scale.x;
							k->value[ANIM_SCL_Y] = scale.y;
							k->value[ANIM_SCL_Z] = scale.z;

							dprintf ("       key[%3i]: Time: %i, flags: %i, (%f,%f,%f)\n",i,tid,flg,scale.x,scale.y,scale.z);
						}
						break;
			case 0xB024 : //ROLL_TAG
						dprintf ("    [#] Roll keys found, reading: %i,%i,%i keys\n",min,max,knum);
						for (i=0;i<knum;i++)
						{
						  xfread (&tid,sizeof (DWORD),1,f);
							xfread (&flg,sizeof (WORD),1,f);
							xfread (&ang,sizeof (float),1,f);
							dprintf ("       key[%3i]: Time: %i, flags: %i, Roll:%f\n",i,tid,flg,ang);
						}
						break;

			default :
				dprintf ("    [#] Found: '%x', skipping...\n",id);
				skip (f,subend);
				break;
		}
    xfseek (f, subend, SEEK_SET);
	}
	return anim;

}
/*#############################################################################
#
# Keyframer data...
#
#############################################################################*/
static int kfdata (XFILE *f,int next,V3D *v3d)
{
	short int id=0;
	WORD version;
	char dummy[10];
	DWORD len,segstart,segend;
	int subend=0;

	dprintf ("[#] Reading KeyFramer data!\n");
	while ((xftell(f) < next) && (!xfeof (f)))
	{
		xfread (&id, 2, 1,f);
		xfread (&subend, 4, 1, f);
		subend = xftell (f) - 6 + subend;

		switch (id)
		{
			case 0xB00A : // KF_HDR
					dprintf ("  [#] Keyframer header\n"); //skip (f,subend);
					xfread (&version,sizeof (WORD), 1, f);
					xfread (dummy,10,1,f);
					xfread (&len,sizeof(DWORD),1,f);
					dprintf ("    - Version: %i, Length: %i, Dummy: %s\n",version,len,dummy);
					break;
			case 0xB008 : // KF_SEG
					dprintf ("  [#] Keyframer segment\n"); //skip (f,subend);
					xfread (&segstart,sizeof (DWORD), 1, f);
					xfread (&segend,sizeof (DWORD), 1, f);
					dprintf ("    - SegStart: %i, SegEnd: %i\n",segstart,segend);
					break;
			// Read for all object types...
			case 0xB002 : // OBJECT
					dprintf ("  [#] Keyframer object-node\n");
					kf_objnod (f,subend,id,v3d);
 					break;
			case 0xB003 :	// CAMERA
					dprintf ("  [#] Camera object-node\n");
					kf_objnod (f,subend,id,v3d);
					break;
			case 0xB004 :	// CAMERA_TARGET
					dprintf ("  [#] Camera target-node\n");
					kf_objnod (f,subend,id,v3d);
					break;
			case 0xB005 :	// LIGHT
			case 0xB006 : // LIGHT_TARGET
			case 0xB007 :	// SPOTLIGHT
					kf_objnod (f,subend,id,v3d);
					break;
			default : dprintf ("  [#] Unsupported chunk: '%x' skipping.\n",id);
								skip (f,subend);
								break;

		}
    xfseek (f, subend, SEEK_SET);
	}
}
/*#############################################################################
#
# Detecting routine...
#
#############################################################################*/
static int detect_3ds (XFILE * fp)
{
	short int id=0;

	xfread (&id, 2, 1, fp);

	if (id != 0x4d4d) return 0;
		else return 1;
}
/*******************************************************************************
*
* Main 3DS loader
*
**********************************************************************************/

V3D *load_3ds (char *filename,int ufn)
{
	V3D *v3d;
  //V3O *obj;
  XFILE *f;
  short int id=0;
	int i;
  int objend,subend;

  if ( (f = xfopen( filename, "rb")) == NULL)
	{
		error = ERR_NOFILE;
		return( NULL);
	}

	if (!detect_3ds (f))
	{
		error = ERR_CUSTOM;
		strcpy( errstr, "[LOAD] Invalid or no 3ds-file.");
		xfclose (f);
		return NULL;
	}

	use_objname = ufn;		// Set save flag...

  xfread (&objend, 4, 1, f);
  objend = xftell (f) - 6 + objend;

	v3d = v3d_create ();

  while (xftell (f) < objend)
  {
    id = 0;
    xfread (&id, 2, 1, f);
    xfread (&subend, 4, 1, f);
    subend = (xftell (f) -6) + subend;
    switch (id)
    {
      case 0x3d3d : if (mesh (f,subend,v3d)==FALSE)
									 	{
											xfclose (f);
											return NULL;
										}
										break;
      case 0xb000 : kfdata (f,subend,v3d);

										break;
    }
    xfseek (f, subend, SEEK_SET);
  }


	dprintf ("[#] File contains: %d objects.\n",num_object);
	for (i=0;i<num_object;i++) dprintf ("  Name o%i: - %s\n",i,namelist[i].name);
	return(v3d);
}
/*################################################################
	# v3d - geterror
	#################################################################*/
char *geterror_3ds()
{
	switch( error)
	{
		case ERR_NOERR: return( "[3DS] No error");
		case ERR_NOFILE: return( "[3DS] Unable to open file");
		case ERR_NOMEM: return( "[3DS] Unable to allocate memory");
		case ERR_CUSTOM:
		{
			char str[256];
			strcpy( str, errstr);
			sprintf( errstr, "[3DS] %s", str);
			return( errstr);
		}
		default: return( "[3DS] Unknown error!");
	}
}



