#include "qbsp.h"

//
// command line flags
//
int subdivide_size = 240;

struct brushset *brushset;

int valid;

char bspfilename[NAMELEN_PATH];
char pointfilename[NAMELEN_PATH];
char portfilename[NAMELEN_PATH];
char hullfilename[NAMELEN_PATH];

char *argv0;								       // changed after fork();

bool worldmodel;

int hullnum;

int c_activefaces, c_peakfaces;
int c_activeleafs, c_peakleafs;
int c_activesurfaces, c_peaksurfaces;
int c_activeportals, c_peakportals;

//===========================================================================

void PrintMemory(void)
{
  int i, j;
  int a, b;

  mprintf(" faces   : %5i, %7i (%5i, %7i)\n", c_activefaces, j = c_activefaces * sizeof(struct visfacet),
                                              c_peakfaces, i = c_peakfaces * sizeof(struct visfacet));
  a = j;
  b = i;
  mprintf(" surfaces: %5i, %7i (%5i, %7i)\n", c_activesurfaces, j = c_activesurfaces * sizeof(struct surface),
                                              c_peaksurfaces, i = c_peaksurfaces * sizeof(struct surface));
  a += j;
  b += i;
  mprintf(" windings: %5i, %7i (%5i, %7i)\n", c_activewindings, j = c_activewindings * sizeof(struct winding),
                                              c_peakwindings, i = c_peakwindings * sizeof(struct winding));
  a += j;
  b += i;
  mprintf(" portals : %5i, %7i (%5i, %7i)\n", c_activeportals, j = c_activeportals * sizeof(struct portal),
                                              c_peakportals, i = c_peakportals * sizeof(struct portal));
  a += j;
  b += i;
  mprintf("----------------------------------\n");
  mprintf("                  %7i (        %7i)\n\n", a, b);
#ifdef	MEM_SIZETRACK
  mprintf(" memory  :        %7i (        %7i)\n", memcounter, mempeak);
#ifdef	MEM_ANALYSE
  mprintf("                  %7i (        %7i)\n", memallocs, mempeakallocs);
  mprintf("                  %7i (        %7i)\n", memcounter/memallocs, mempeak/mempeakallocs);
#endif
#endif
}

/*
 * ===========
 * AllocLeaf
 * ===========
 */
struct visleaf *AllocLeaf(register int prtals)
{
  struct visleaf *l;

  c_activeleafs++;
  if (c_activeleafs > c_peakleafs)
    c_peakleafs = c_activeleafs;

  if(!(l = (struct visleaf *)tmalloc(sizeof(struct visleaf))))
    Error("AllocLeaf: failed to allocate leaf!\n");
#ifdef DYNAMIC_EDGES
  /* at least one point available */
  if(prtals < 0)
    prtals = 0;
  if(!(l->portals = (struct visportal **)tmalloc((prtals) * sizeof(struct visportal *))))
    Error("AllocLeaf: failed to allocate portals!\n");
#endif
  return l;
}


void RecalcLeaf(register struct visleaf *l) {
#ifdef DYNAMIC_EDGES
  /* at least one point available */
  int prtals = l->numportals;
  if(prtals < 0)
    prtals = 0;
  if(!(l->portals = (struct visportal **)trealloc(l->portals , (prtals) * sizeof(struct visportal *))))
    Error("RecalcFace: failed to allocate face!\n");
#endif
}

void FreeLeaf(register struct visleaf *l)
{
  c_activeleafs--;
#ifdef DYNAMIC_EDGES
  tfree(l->portals);
#endif
  tfree(l);
}

/*
 * ===========
 * AllocFace
 * ===========
 */
struct visfacet *AllocFace(register int points)
{
  struct visfacet *f;

  c_activefaces++;
  if (c_activefaces > c_peakfaces)
    c_peakfaces = c_activefaces;

  if(!(f = (struct visfacet *)tmalloc(sizeof(struct visfacet))))
    Error("AllocFace: failed to allocate face!\n");
  //memset(f, 0, sizeof(struct visfacet));
  f->planenum = -1;
#ifdef DYNAMIC_EDGES
  /* at least one point available */
  if(points < 0)
    points = 0;
  if(!(f->pts   = (vec3_t *)tmalloc((points) * sizeof(vec3_t))))
    Error("AllocFace: failed to allocate points!\n");
  if(!(f->edges = (int *)   tmalloc((points) * sizeof(int)   )))
    Error("AllocFace: failed to allocate edges!\n");
#endif
  return f;
}

void CopyFace(register struct visfacet *out, register struct visfacet *in) {
#ifdef DYNAMIC_EDGES
  /* at least one point available */
  short int points = in->numpoints;
  vec3_t *pts;
  int *edges;
  
  if(points < 0)
    points = 0;
  tfree(out->pts);
  tfree(out->edges);
  if(!(pts   = (vec3_t *)tmalloc((points) * sizeof(vec3_t))))
    Error("CopyFace: failed to allocate face!\n");
  if(!(edges = (int *)   tmalloc((points) * sizeof(int)   )))
    Error("CopyFace: failed to allocate face!\n");
  if(points) {
    memcpy(pts  , in->pts  , points * sizeof(vec3_t));
    memcpy(edges, in->edges, points * sizeof(int)   );
  }
#endif
  memcpy(out, in, sizeof(struct visfacet));
#ifdef DYNAMIC_EDGES
  out->pts   = pts;
  out->edges = edges;
#endif
}

void RecalcFace(register struct visfacet *f) {
#ifdef DYNAMIC_EDGES
  /* at least one point available */
  short int points = f->numpoints;
  if(points < 0)
    points = 0;
  if(!(f->pts   = (vec3_t *)trealloc(f->pts  , (points) * sizeof(vec3_t))))
    Error("RecalcFace: failed to allocate face!\n");
  if(!(f->edges = (int *)   trealloc(f->edges, (points) * sizeof(int)   )))
    Error("RecalcFace: failed to allocate face!\n");
#endif
}

void FreeFace(register struct visfacet *f)
{
  c_activefaces--;
#ifdef DYNAMIC_EDGES
  tfree(f->pts);
  tfree(f->edges);
#endif
  tfree(f);
}

/*
 * ===========
 * AllocSurface
 * ===========
 */
struct surface *AllocSurface(void)
{
  struct surface *s;

  if(!(s = (struct surface *)tmalloc(sizeof(struct surface))))
    Error("AllocSurface: failed to allocate surface!\n");
  //memset(s, 0, sizeof(struct surface));

  c_activesurfaces++;
  if (c_activesurfaces > c_peaksurfaces)
    c_peaksurfaces = c_activesurfaces;

  return s;
}

void FreeSurface(register struct surface *s)
{
  c_activesurfaces--;
  tfree(s);
}

/*
 * ===========
 * AllocPortal
 * ===========
 */
struct portal *AllocPortal(void)
{
  struct portal *p;

  c_activeportals++;
  if (c_activeportals > c_peakportals)
    c_peakportals = c_activeportals;

  if(!(p = (struct portal *)tmalloc(sizeof(struct portal))))
    Error("AllocPortal: failed to allocate portal!\n");
  //memset(p, 0, sizeof(struct portal));

  return p;
}

void FreePortal(register struct portal *p)
{
  c_activeportals--;
  tfree(p);
}

/*
 * ===========
 * AllocNode
 * ===========
 */
struct node *AllocNode(void)
{
  struct node *n;

  if(!(n = (struct node *)tmalloc(sizeof(struct node))))
    Error("AllocNode: failed to allocate node!\n");
  //memset(n, 0, sizeof(struct node));

  return n;
}

/*
 * ===========
 * AllocBrush
 * ===========
 */
struct brush *AllocBrush(void)
{
  struct brush *b;

  if(!(b = (struct brush *)tmalloc(sizeof(struct brush))))
    Error("AllocNode: failed to allocate node!\n");
  //memset(b, 0, sizeof(struct brush));

  return b;
}

//===========================================================================

/*
 * ===============
 * ProcessEntity
 * ===============
 */
void ProcessEntity(__memBase, register int entnum)
{
  struct entity *ent;
  char mod[80];
  struct surface *surfs;
  struct node *nodes;
  struct brushset *bs;

  ent = &bspMem->mapentities[entnum];
  if (!ent->brushes)
    return;								       // non-bmodel entity

  if (entnum > 0) {
    worldmodel = false;
    if (entnum == 1)
      mprintf("----- Internal Entities ---\n");
    sprintf(mod, "*%i", bspMem->nummodels);

    if (hullnum == 0)
      mprintf("    - MODEL: %s\n", mod);
    SetKeyValue(ent, "model", mod);
  }
  else
    worldmodel = true;

//
  // take the brush_ts and clip off all overlapping and contained faces,
  // leaving a perfect skin of the model with no hidden faces
  //
  bs = Brush_LoadEntity(bspMem, ent, hullnum);

  if (!bs->brushes) {
    PrintEntity(ent);
    Error("Entity with no valid brushes");
  }

  brushset = bs;
  surfs = CSGFaces(bspMem, bs);

  if (hullnum != 0) {
    nodes = SolidBSP(bspMem, surfs, true);
    if (entnum == 0 && !(bspMem->bspOptions & QBSP_NOFILL))		       // assume non-world bmodels are simple
     {
      PortalizeWorld(bspMem, nodes);
      if (FillOutside(bspMem, nodes, pointfilename)) {
	surfs = GatherNodeFaces(bspMem, nodes);
	nodes = SolidBSP(bspMem, surfs, false);				       // make a really good tree
      }
      FreeAllPortals(nodes);
    }
    WriteNodePlanes(bspMem, nodes);
    WriteClipNodes(bspMem, nodes);
    BumpModel(bspMem, hullnum);
  }
  else {
    //
    // SolidBSP generates a node tree
    //
    // if not the world, make a good tree first
    // the world is just going to make a bad tree
    // because the outside filling will force a regeneration later
    nodes = SolidBSP(bspMem, surfs, entnum == 0);

    //
    // build all the portals in the bsp tree
    // some portals are solid polygons, and some are paths to other leafs
    //
    if (entnum == 0 && !(bspMem->bspOptions & QBSP_NOFILL))		       // assume non-world bmodels are simple
     {
      PortalizeWorld(bspMem, nodes);

      if (FillOutside(bspMem, nodes, pointfilename)) {
	FreeAllPortals(nodes);

	// get the remaining faces together into surfaces again
	surfs = GatherNodeFaces(bspMem, nodes);

	// merge polygons
	MergeAll(bspMem, surfs);

	// make a really good tree
	nodes = SolidBSP(bspMem, surfs, false);

	// make the real portals for vis tracing
	PortalizeWorld(bspMem, nodes);

	// save portal file for vis tracing
	WritePortalfile(bspMem, nodes, portfilename);
	
	// fix tjunctions
	if(!(bspMem->bspOptions & QBSP_NOTJUNC))
	  tjunc(nodes);
      }
      FreeAllPortals(nodes);
    }

    WriteNodePlanes(bspMem, nodes);
    MakeFaceEdges(bspMem, nodes);
    WriteDrawNodes(bspMem, nodes);
  }
}

/*
 * =================
 * UpdateEntLump
 * 
 * =================
 */
void UpdateEntLump(__memBase)
{
  int m, entnum;
  char mod[80];
  FILE *bspFile;

  m = 1;
  for (entnum = 1; entnum < bspMem->nummapentities; entnum++) {
    if (!bspMem->mapentities[entnum].brushes)
      continue;
    sprintf(mod, "*%i", m);
    SetKeyValue(&bspMem->mapentities[entnum], "model", mod);
    m++;
  }

  mprintf("    - updating bspMem->mapentities lump...\n");
  
  FreeClusters(bspMem, 0);
  if((bspFile = fopen(bspfilename, READWRITE_BINARY_OLD))) {
    bspMem = LoadBSP(bspFile, ALL_LUMPS);
    WriteEntitiesToString(bspMem);
    WriteBSP(bspFile, bspMem);
    FreeClusters(bspMem, 0);
    tfree(bspMem);
    fclose(bspFile);
  }
}

//===========================================================================

/*
 * =================
 * WriteClipHull
 * 
 * Write the clipping hull out to a text file so the parent process can get it
 * =================
 */
void WriteClipHull(__memBase)
{
  FILE *f;
  int i;
  struct dplane_t *p;
  struct dclipnode_t *d;

  hullfilename[strlen(hullfilename) - 1] = '0' + hullnum;

  mprintf("----- WriteClipHull -----\n");
  mprintf("    - writing %s\n", hullfilename);

  f = fopen(hullfilename, "w");
  if (!f)
    Error("Couldn't open %s", hullfilename);

  fprintf(f, "%i\n", bspMem->nummodels);

  for (i = 0; i < bspMem->nummodels; i++)
    fprintf(f, "%i\n", bspMem->dmodels[i].headnode[hullnum]);

  fprintf(f, "\n%i\n", bspMem->numclipnodes);

  for (i = 0; i < bspMem->numclipnodes; i++) {
    d = &bspMem->dclipnodes[i];
    p = &bspMem->dplanes[d->planenum];
    // the node number is only written out for human readability
    fprintf(f, "%5i : %g %g %g %g : %5i %5i\n", i, p->normal[0], p->normal[1], p->normal[2], p->dist, d->children[0], d->children[1]);
  }

  fclose(f);
}

/*
 * =================
 * ReadClipHull
 * 
 * Read the files written out by the child processes
 * =================
 */
void ReadClipHull(__memBase, register int hullnum)
{
  FILE *f;
  int i, j, n;
  int firstclipnode;
  struct dplane_t p;
  struct dclipnode_t *d;
  int c1, c2;
  float f1, f2, f3, f4;
  int junk;
  vec3_t norm;

  hullfilename[strlen(hullfilename) - 1] = '0' + hullnum;

  f = fopen(hullfilename, "r");
  if (!f)
    Error("Couldn't open %s", hullfilename);

  if (fscanf(f, "%i\n", &n) != 1)
    Error("Error parsing %s", hullfilename);

  if (n != bspMem->nummodels)
    Error("ReadClipHull: hull had %i models, base had %i", n, bspMem->nummodels);

  for (i = 0; i < n; i++) {
    fscanf(f, "%i\n", &j);
    bspMem->dmodels[i].headnode[hullnum] = bspMem->numclipnodes + j;
  }

  fscanf(f, "\n%i\n", &n);
  firstclipnode = bspMem->numclipnodes;

  for (i = 0; i < n; i++) {
    if (bspMem->numclipnodes == bspMem->max_numclipnodes)
      ExpandClusters(bspMem, LUMP_CLIPNODES);
    d = &bspMem->dclipnodes[bspMem->numclipnodes];
    bspMem->numclipnodes++;
    if (fscanf(f, "%i : %g %g %g %g : %i %i\n", &junk, &f1, &f2, &f3, &f4, &c1, &c2) != 7)
      Error("Error parsing %s", hullfilename);

    p.normal[0] = f1;
    p.normal[1] = f2;
    p.normal[2] = f3;
    p.dist = f4;

    norm[0] = f1;
    norm[1] = f2;
    norm[2] = f3;							       // vec_t precision

    p.type = PlaneTypeForNormal(norm);

    d->children[0] = c1 >= 0 ? c1 + firstclipnode : c1;
    d->children[1] = c2 >= 0 ? c2 + firstclipnode : c2;
    d->planenum = FindFinalPlane(bspMem, &p);
  }

}

/*
 * =================
 * CreateSingleHull
 * 
 * =================
 */
void CreateSingleHull(__memBase)
{
  int entnum;

// for each entity in the map file that has geometry
  for (entnum = 0; entnum < bspMem->nummapentities; entnum++)
    ProcessEntity(bspMem, entnum);

  if (hullnum)
    WriteClipHull(bspMem);
}

/*
 * =================
 * CreateHulls
 * 
 * =================
 */
void CreateHulls(__memBase)
{
// commanded to create a single hull only
  if (hullnum) {
    CreateSingleHull(bspMem);
    exit(0);
  }

// commanded to use the allready existing hulls 1 and 2
  if (bspMem->bspOptions & QBSP_USEHULLS) {
    CreateSingleHull(bspMem);
    return;
  }

// commanded to ignore the hulls altogether
  if (bspMem->bspOptions & QBSP_NOCLIP) {
    CreateSingleHull(bspMem);
    return;
  }

// create all the hulls

// create the hulls sequentially
  mprintf("    - building hulls sequentially...\n");

  hullnum = 1;
  CreateSingleHull(bspMem);

  bspMem->nummodels = 0;
  bspMem->numplanes = 0;
  bspMem->numclipnodes = 0;
  hullnum = 2;
  CreateSingleHull(bspMem);

  bspMem->nummodels = 0;
  bspMem->numplanes = 0;
  bspMem->numclipnodes = 0;
  hullnum = 0;
  CreateSingleHull(bspMem);
}

/*
 * =================
 * ProcessMem
 * =================
 */
void ProcessMem(__memBase, register char *filebase)
{
// create filenames
  strcpy(bspfilename, filebase);
  ReplaceExt(bspfilename, "bsp");
  strcpy(hullfilename, filebase);
  ReplaceExt(hullfilename, "h0");
  strcpy(portfilename, filebase);
  ReplaceExt(portfilename, "prt");
  strcpy(pointfilename, filebase);
  ReplaceExt(pointfilename, "pts");

  if (bspMem->bspOptions & QBSP_ONLYENTS)
    UpdateEntLump(bspMem);
  else {
    remove(bspfilename);
    if (!(bspMem->bspOptions & QBSP_USEHULLS)) {
      hullfilename[strlen(hullfilename) - 1] = '1';
      remove(hullfilename);
      hullfilename[strlen(hullfilename) - 1] = '2';
      remove(hullfilename);
    }
    remove(portfilename);
    remove(pointfilename);

// the clipping hulls will be written out to text files by forked processes
    CreateHulls(bspMem);

    ReadClipHull(bspMem, 1);
    ReadClipHull(bspMem, 2);

    WriteEntitiesToString(bspMem);
  }
}

bool qbsp(__memBase, int hullNum, int subDivide, char *filebase)
{
  if(hullNum) {
    hullnum = hullNum;
    mprintf("use hull %d\n", hullNum);
  }
  if(subDivide) {
    subdivide_size = subDivide;
    mprintf("subdivide %d\n", subDivide);
  }

  ProcessMem(bspMem, filebase);
  PrintMemory();

  return TRUE;
}

