#define	LIBQBUILD_CORE
#include "../include/libqbuild.h"

int headclipnode;						/* 4 */
int firstface;							/* 4 */

int *planemapping;

/*=========================================================================== */

/*
 * ==================
 * FindFinalPlane
 * 
 * Used to find plane index numbers for clip nodes read from child processes
 * ==================
 */
int FindFinalPlane(__memBase, struct dplane_t *p)
{
  int i;
  struct dplane_t *dplane;

  for (i = 0, dplane = bspMem->shared.quake1.dplanes; i < bspMem->shared.quake1.numplanes; i++, dplane++) {
    if (p->type != dplane->type)
      continue;
    if (p->dist != dplane->dist)
      continue;
    if (p->normal[0] != dplane->normal[0])
      continue;
    if (p->normal[1] != dplane->normal[1])
      continue;
    if (p->normal[2] != dplane->normal[2])
      continue;
    return i;
  }

  /* new plane */
  if (bspMem->shared.quake1.numplanes == bspMem->shared.quake1.max_numplanes)
    ExpandClusters(bspMem, LUMP_PLANES);
  dplane = &bspMem->shared.quake1.dplanes[bspMem->shared.quake1.numplanes];
  *dplane = *p;
  bspMem->shared.quake1.numplanes++;

  return bspMem->shared.quake1.numplanes - 1;
}

void WriteNodePlanes_r(__memBase, register struct node *node)
{
  struct plane *plane;
  struct dplane_t *dplane;

  if (node->planenum == -1)
    return;
  if (planemapping[node->planenum] == -1) {			/* a new plane */

    planemapping[node->planenum] = bspMem->shared.quake1.numplanes;

    if (bspMem->shared.quake1.numplanes == bspMem->shared.quake1.max_numplanes)
      ExpandClusters(bspMem, LUMP_PLANES);
#ifdef EXHAUSIVE_CHECK
    if (node->planenum >= bspMem->numbrushplanes || node->planenum < 0)
      Error("looking for nonexisting plane %d\n", node->planenum);
#endif
    plane = &bspMem->brushplanes[node->planenum];
    dplane = &bspMem->shared.quake1.dplanes[bspMem->shared.quake1.numplanes];
    dplane->normal[0] = plane->normal[0];
    dplane->normal[1] = plane->normal[1];
    dplane->normal[2] = plane->normal[2];
    dplane->dist = plane->dist;
    dplane->type = plane->type;

    bspMem->shared.quake1.numplanes++;
  }

  node->outputplanenum = planemapping[node->planenum];

  WriteNodePlanes_r(bspMem, node->children[0]);
  WriteNodePlanes_r(bspMem, node->children[1]);
}

/*
 * ==================
 * WriteNodePlanes
 * 
 * ==================
 */
void WriteNodePlanes(__memBase, struct node *nodes)
{
  if (!(planemapping = (int *)tmalloc(sizeof(int) * bspMem->numbrushplanes)))
      Error(failed_memoryunsize, "planemapping");
  memset(planemapping, -1, sizeof(int) * bspMem->numbrushplanes);

  WriteNodePlanes_r(bspMem, nodes);
  tfree(planemapping);
}

/*=========================================================================== */

/*
 * ==================
 * WriteClipNodes_r
 * 
 * ==================
 */
int WriteClipNodes_r(__memBase, register struct node *node)
{
  int c;
  short int i;
  struct dclipnode_t *cn;
  int num;

  /* FIXME: tfree more stuff?       */
  if (node->planenum == -1) {
    num = node->contents;
    tfree(node);
    return num;
  }

  if (bspMem->shared.quake1.numclipnodes == bspMem->shared.quake1.max_numclipnodes)
    ExpandClusters(bspMem, LUMP_CLIPNODES);
  /* emit a clipnode */
  c = bspMem->shared.quake1.numclipnodes;
  cn = &bspMem->shared.quake1.dclipnodes[c];
  bspMem->shared.quake1.numclipnodes++;

  cn->planenum = node->outputplanenum;
  for (i = 0; i < 2; i++)
    cn->children[i] = WriteClipNodes_r(bspMem, node->children[i]);

  tfree(node);
  return c;
}

/*
 * ==================
 * WriteClipNodes
 * 
 * Called after the clipping hull is completed.  Generates a disk format
 * representation and frees the original memory.
 * ==================
 */
void WriteClipNodes(__memBase, struct node *nodes)
{
  headclipnode = bspMem->shared.quake1.numclipnodes;
  WriteClipNodes_r(bspMem, nodes);
}

/*=========================================================================== */

/*
 * ==================
 * WriteLeaf
 * ==================
 */
void WriteLeaf(__memBase, register struct node *node)
{
  struct visfacet **fp, *f;
  struct dleaf_t *leaf_p;

  /* emit a leaf */
  if (bspMem->shared.quake1.numleafs == bspMem->shared.quake1.max_numleafs)
    ExpandClusters(bspMem, LUMP_LEAFS);
  leaf_p = &bspMem->shared.quake1.dleafs[bspMem->shared.quake1.numleafs];
  bspMem->shared.quake1.numleafs++;

  leaf_p->contents = node->contents;

  /* write bounding box info */
  VectorCopy(node->mins, leaf_p->mins);
  VectorCopy(node->maxs, leaf_p->maxs);

  leaf_p->visofs = -1;						/* no vis info yet */

  /* write the marksurfaces */
  leaf_p->firstmarksurface = bspMem->shared.quake1.nummarksurfaces;

  for (fp = node->markfaces; *fp; fp++) {
    /* emit a marksurface */
    f = *fp;
    do {
      if (bspMem->shared.quake1.nummarksurfaces == bspMem->shared.quake1.max_nummarksurfaces)
	ExpandClusters(bspMem, LUMP_MARKSURFACES);
      bspMem->shared.quake1.dmarksurfaces[bspMem->shared.quake1.nummarksurfaces] = f->outputnumber;
      bspMem->shared.quake1.nummarksurfaces++;
      f = f->original;						/* grab tjunction split faces */
    } while (f);
  }

  leaf_p->nummarksurfaces = bspMem->shared.quake1.nummarksurfaces - leaf_p->firstmarksurface;
}

/*
 * ==================
 * WriteDrawNodes_r
 * ==================
 */
void WriteDrawNodes_r(__memBase, register struct node *node)
{
  struct dnode_t *n;
  short int i;

  /* emit a node   */
  if (bspMem->shared.quake1.numnodes == bspMem->shared.quake1.max_numnodes)
    ExpandClusters(bspMem, LUMP_NODES);
  n = &bspMem->shared.quake1.dnodes[bspMem->shared.quake1.numnodes];
  bspMem->shared.quake1.numnodes++;

  VectorCopy(node->mins, n->mins);
  VectorCopy(node->maxs, n->maxs);

  n->planenum = node->outputplanenum;
  n->firstface = node->firstface;
  n->numfaces = node->numfaces;

  /* recursively output the other nodes */
  for (i = 0; i < 2; i++) {
    if (node->children[i]->planenum == -1) {
      if (node->children[i]->contents == CONTENTS_SOLID)
	n->children[i] = -1;
      else {
	n->children[i] = -(bspMem->shared.quake1.numleafs + 1);
	WriteLeaf(bspMem, node->children[i]);
      }
    }
    else {
      n->children[i] = bspMem->shared.quake1.numnodes;
      WriteDrawNodes_r(bspMem, node->children[i]);
    }
  }
}

/*
 * ==================
 * WriteDrawNodes
 * ==================
 */
void WriteDrawNodes(__memBase, struct node *headnode)
{
  short int i;
  int start;
  struct dmodel_t *bm;

#if 0
  if (headnode->contents < 0)
    Error("FinishBSPModel: empty model");
#endif

  /* emit a model */
  if (bspMem->shared.quake1.nummodels == bspMem->shared.quake1.max_nummodels)
    ExpandClusters(bspMem, LUMP_MODELS);
  bm = &bspMem->shared.quake1.dmodels[bspMem->shared.quake1.nummodels];
  bspMem->shared.quake1.nummodels++;

  bm->headnode[0] = bspMem->shared.quake1.numnodes;
  bm->firstface = firstface;
  bm->numfaces = bspMem->shared.quake1.numfaces - firstface;
  firstface = bspMem->shared.quake1.numfaces;

  start = bspMem->shared.quake1.numleafs;

  if (headnode->contents < 0)
    WriteLeaf(bspMem, headnode);
  else
    WriteDrawNodes_r(bspMem, headnode);
  bm->visleafs = bspMem->shared.quake1.numleafs - start;

  for (i = 0; i < 3; i++) {
    bm->mins[i] = headnode->mins[i] + SIDESPACE + 1;		/* remove the padding */
    bm->maxs[i] = headnode->maxs[i] - SIDESPACE - 1;
  }
  /* FIXME: are all the children decendant of padded nodes? */
}

/*
 * ==================
 * BumpModel
 * 
 * Used by the clipping hull processes that only need to store headclipnode
 * ==================
 */
void BumpModel(__memBase, int hullnum)
{
  struct dmodel_t *bm;

  /* emit a model */
  if (bspMem->shared.quake1.nummodels == bspMem->shared.quake1.max_nummodels)
    ExpandClusters(bspMem, LUMP_MODELS);
  bm = &bspMem->shared.quake1.dmodels[bspMem->shared.quake1.nummodels];
  bspMem->shared.quake1.nummodels++;

  bm->headnode[hullnum] = headclipnode;
}

/*============================================================================= */

#define	MAX_MULTIPLE	32

/*
 * ==================
 * WriteMiptex
 * ==================
 */
void WriteMiptex(__memBase)
{
  if (!(bspMem->bspOptions & QBSP_NOTEXTURES)) {
    struct wadheader Header[MAX_MULTIPLE + 1];
    struct wadentry *allEntries[MAX_MULTIPLE + 1];
    char *wadPath[MAX_MULTIPLE + 1];
    HANDLE wadFile[MAX_MULTIPLE + 1];
    int wadAvail = 0;

    char *dirPath[MAX_MULTIPLE + 1];
    DIR *dirDir[MAX_MULTIPLE + 1];
    int dirAvail = 0;

    /* TODO: multiple wadFiles */
    if (bspMem->mapentities) {
      wadPath[0] = ValueForKey(&bspMem->mapentities[0], "_wad");
      if (!wadPath[0] || !wadPath[0][0])
	wadPath[0] = ValueForKey(&bspMem->mapentities[0], "wad");
    }
    else
      wadPath[0] = 0;
    if (!wadPath[0] || !wadPath[0][0])
      wadPath[0] = getenv("QUAKE_WADFILE");

    if (wadPath[0] && wadPath[0][0]) {				/* do only if there exists really a wad */
      for (wadAvail = 0; wadAvail < MAX_MULTIPLE;) {
	char *hit;

	if ((hit = (char *)__strchr(wadPath[wadAvail], ';'))) {	/* cut off next entry */
	  *hit = ' ';
	  while ((hit[-1] == ' ') || (hit[-1] == '\t'))		/* delete whitespace */
	    hit--;
	  *hit++ = '\0';
	  while ((*hit == ' ') || (*hit == '\t'))		/* delete whitespace */
	    hit++;
	}
#ifdef DEBUG
	printf("wadPath %2d: \"%s\"\n", wadAvail, wadPath[wadAvail]);
#endif

	if ((wadPath[wadAvail][0]) && (wadFile[wadAvail] = __open(wadPath[wadAvail], H_READ_BINARY)) > 0) {
	  if (CheckWAD2(wadFile[wadAvail], &Header[wadAvail], FALSE)) {
	    FindWAD2(wadFile[wadAvail], 0, &Header[wadAvail], &allEntries[wadAvail], 0);
	    wadAvail++;
	  }
	  else {
	    __close(wadFile[wadAvail]);
	    eprintf("file \"%s\" not a wad\n", wadPath[wadAvail]);
	  }
	}
	else
	  eprintf(failed_fileopen, wadPath[wadAvail]);

	if (!hit)						/* break if nothing more found */
	  break;
	else
	  wadPath[wadAvail] = hit;				/* register next entry */
      }
    }

    /* TODO: multiple dirDirs */
    if (bspMem->mapentities) {
      dirPath[0] = ValueForKey(&bspMem->mapentities[0], "_dir");
      if (!dirPath[0] || !dirPath[0][0])
	dirPath[0] = ValueForKey(&bspMem->mapentities[0], "dir");
    }
    else
      dirPath[0] = 0;
    if (!dirPath[0] || !dirPath[0][0])
      dirPath[0] = getenv("QUAKE_WADDIR");
    if (!dirPath[0] || !dirPath[0][0])
      dirPath[0] = "\0";

    for (dirAvail = 0; dirAvail < MAX_MULTIPLE;) {		/* do ever, 'cause we have ever a valid dir (current dir) */
      char *hit;

      if ((hit = (char *)__strchr(dirPath[dirAvail], ';'))) {	/* cut off next entry */
	*hit = ' ';
	while ((hit[-1] == ' ') || (hit[-1] == '\t'))		/* delete whitespace */
	  hit--;
	*hit++ = '\0';
	while ((*hit == ' ') || (*hit == '\t'))			/* delete whitespace */
	  hit++;
      }
#ifdef DEBUG
      printf("dirPath %2d: \"%s\"\n", dirAvail, dirPath[dirAvail]);
#endif

      if ((dirDir[dirAvail] = opendir(dirPath[dirAvail])))
	dirAvail++;						/* skip unavailable entries */
      else
	eprintf("dir \"%s\" is not a dir, or does not exists\n", dirPath[dirAvail]);

      if (!hit)							/* break if nothing more found */
	break;
      else
	dirPath[dirAvail] = hit;				/* register next entry */
    }
#ifdef DEBUG
    printf("wads %2d, dirs %2d\n", wadAvail, dirAvail);
#endif

    if ((wadAvail || dirAvail) && bspMem->nummaptexstrings) {
      int i;
      unsigned char *mipFlow;
      struct dmiptexlump_t *mipBlock;
      int texstrings = bspMem->nummaptexstrings;

      if (!bspMem->shared.quake1.dtexdata)
	AllocClusters(bspMem, LUMP_TEXTURES);
      mipBlock = (struct dmiptexlump_t *)bspMem->shared.quake1.dtexdata;
      mipFlow = (unsigned char *)&mipBlock->dataofs[bspMem->nummaptexstrings];
      mipBlock->nummiptex = bspMem->nummaptexstrings;
      bspMem->shared.quake1.texdatasize = mipFlow - bspMem->shared.quake1.dtexdata;

      for (i = 0; i < texstrings; i++) {
	struct rawdata *inPut;

	void GetInput(char *inName) {
	  int j;
	  struct wadentry *Entry;

	    inPut = 0;

	  /* first search in wadFiles */
	  for (j = 0; j < wadAvail; j++)
	    if ((Entry = SearchWAD2(inName, &Header[j], allEntries[j], TYPE_MIPMAP)))
	        inPut = GetWAD2Raw(wadFile[j], Entry);

	  /* if nothing found, search in dirDirs */
	  for (j = 0; (j < dirAvail) && !(inPut); j++) {
	    struct dirent *dirEnt = 0;

	    while ((dirEnt = readdir(dirDir[j]))) {
#ifdef DEBUG
	      printf("dirname: %s wadname: %s\n", dirEnt->d_name, inName);
#endif
	      if (!__strncasecmp(dirEnt->d_name, inName, strlen(inName)))	/* metal1 matches metal10* */
		if (dirEnt->d_name[strlen(inName)] == '.')	/* metal1 matches metal1.* */
		  break;
	    } if (dirEnt) {
	      char *fileExt = GetExt(dirEnt->d_name);
	      FILE *inFile;
	      struct palpic *inPic = 0;
	      char *fileName;

	      if ((fileName = (char *)tmalloc(NAMELEN_PATH + 1))) {
		__strncpy(fileName, dirPath[j], NAMELEN_PATH);
		__strncat(fileName, "/", NAMELEN_PATH);
		__strncat(fileName, dirEnt->d_name, NAMELEN_PATH);

		if ((inFile = __fopen(fileName, F_READ_BINARY))) {
		  if (!__strcmp(fileExt, "mip"))
		    inPut = GetRaw(fileno(inFile), inName, 0);
		  else if (!__strcmp(fileExt, "lmp"))
		    inPic = GetLMP(fileno(inFile), inName);
		  else {
		    short int alignX = 16, alignY = 16;

		    if (fileName[0] == WARP_MIPMAP)
		      alignX = alignY = WARP_X;
		    else if (!__strncasecmp(fileName, SKY_MIPMAP, 3)) {
		      alignX = -(SKY_X);
		      alignY = -(SKY_Y);
		    }
		    inPic = GetImage(inFile, inName, alignX, alignY);
		  }

		  if (inPic) {
		    if ((inPut = rmalloc(MIP_MULT(inPic->width * inPic->height) + sizeof(struct mipmap), inName)))
		        PasteMipMap((struct mipmap *)inPut->rawdata, inPic);

		    pfree(inPic);
		  }
		  else
		    eprintf("unknown fileformat %s\n", fileName);

		  __fclose(inFile);
		}
		else
		  eprintf(failed_fileopen, fileName);

		tfree(fileName);
	      }
	    }

	    rewinddir(dirDir[j]);
	  }
	}

	void PutInput(char *inName) {
	  if (inPut) {
	    mipBlock->dataofs[i] = mipFlow - (unsigned char *)mipBlock;
	    mprintf("    - load texture %s\n", inPut->name);

	    if ((bspMem->shared.quake1.texdatasize + inPut->size) >= bspMem->shared.quake1.max_texdatasize) {
	      ExpandClusters(bspMem, LUMP_TEXTURES);
	      mipBlock = (struct dmiptexlump_t *)bspMem->shared.quake1.dtexdata;
	      mipFlow = bspMem->shared.quake1.dtexdata + bspMem->shared.quake1.texdatasize;
	    } bspMem->shared.quake1.texdatasize += inPut->size;

	    memcpy(mipFlow, inPut->rawdata, inPut->size);
	    mipFlow += inPut->size;
	    rfree(inPut);
	  }
	  else {
	    mipBlock->dataofs[i] = -1;
	    eprintf("texture %s not found!\n", inName);
	  }
	}

	if (bspMem->maptexstrings[i][0] == '+') {
	  int j;
	  char name[20];

	  __strcpy(name, bspMem->maptexstrings[i]);

	  for (j = 0; j < 20; j++) {
	    if (j < 10)
	      name[1] = '0' + j;
	    else
	      name[1] = 'A' + j - 10;				/* alternate animation */

	    GetInput(name);					/* see if this name exists in the wadfile */
	    PutInput(name);					/* put only if it exists */
	    /*FindMiptex(bspMem, name);                                         // add to the miptex list, if somebody after us needs it */
	  }
	}
	else {
	  GetInput(bspMem->maptexstrings[i]);
	  PutInput(bspMem->maptexstrings[i]);
	}
      }

      for (i = 0; i < wadAvail; i++)
	__close(wadFile[i]);
      for (i = 0; i < dirAvail; i++)
	closedir(dirDir[i]);
    }
    else
      eprintf(failed_fileopen, "wadfile(s) or dir(s)");
  }
}

/*=========================================================================== */

/*
 * ==================
 * BeginBSPFile
 * ==================
 */
void BeginBSPFile(__memBase)
{
  bspMem->availHeaders = 0;
  AllocClusters(bspMem, ((ALL_QUAKE1_LUMPS) | (ALL_MAPS)) & (~((LUMP_LIGHTING) | (LUMP_VISIBILITY))));

  /* edge 0 is not used, because 0 can't be negated */
  bspMem->shared.quake1.numedges = 1;

  /* leaf 0 is common solid with no faces */
  bspMem->shared.quake1.numleafs = 1;
  bspMem->shared.quake1.dleafs[0].contents = CONTENTS_SOLID;

  firstface = 0;
}

/*
 * ==================
 * FinishBSPFile
 * ==================
 */
void FinishBSPFile(__memBase, HANDLE bspFile)
{
  mprintf("----- FinishBSPFile -----\n");

  WriteMiptex(bspMem);
  WriteBSP(bspFile, bspMem, BSP_VERSION_Q1);
  PrintClusters(bspMem, 0, TRUE);
}
