#define	LIBQBUILD_CORE
#include "../include/libqbuild.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);
}

/*
 * ===========
 * 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(failed_memoryunsize, "leaf");

#ifdef DYNAMIC_EDGES
  /* at least one point available */
  if (prtals < 0)
    prtals = 0;
  if (!(l->portals = (struct visportal **)tmalloc((prtals) * sizeof(struct visportal *))))
      Error(failed_memoryunsize, "portals");

#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(failed_memoryunsize, "face");

#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(failed_memoryunsize, "face");

  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(failed_memoryunsize, "points");
  if (!(f->edges = (int *)tmalloc((points) * sizeof(int))))
      Error(failed_memoryunsize, "edges");

#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(failed_memoryunsize, "face");
  if (!(edges = (int *)tmalloc((points) * sizeof(int))))
      Error(failed_memoryunsize, "face");

  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(failed_memoryunsize, "face");
  if (!(f->edges = (int *)trealloc(f->edges, (points) * sizeof(int))))
      Error(failed_memoryunsize, "face");

#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(failed_memoryunsize, "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(failed_memoryunsize, "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(failed_memoryunsize, "node");

  return n;
}

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

  if (!(b = (struct brush *)tmalloc(sizeof(struct brush))))
      Error(failed_memoryunsize, "node");

  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->shared.quake1.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];
  HANDLE 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 = __open(bspfilename, H_READWRITE_BINARY_OLD)) > 0) {
    bspMem = LoadBSP(bspFile, ALL_QUAKE1_LUMPS, BSP_VERSION_Q1);
    WriteEntitiesToString(bspMem);
    WriteBSP(bspFile, bspMem, BSP_VERSION_Q1);
    FreeClusters(bspMem, 0);
    tfree(bspMem);
    __close(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(failed_fileopen, hullfilename);

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

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

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

  for (i = 0; i < bspMem->shared.quake1.numclipnodes; i++) {
    d = &bspMem->shared.quake1.dclipnodes[i];
    p = &bspMem->shared.quake1.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(failed_fileopen, hullfilename);

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

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

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

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

  for (i = 0; i < n; i++) {
    if (bspMem->shared.quake1.numclipnodes == bspMem->shared.quake1.max_numclipnodes)
      ExpandClusters(bspMem, LUMP_CLIPNODES);
    d = &bspMem->shared.quake1.dclipnodes[bspMem->shared.quake1.numclipnodes];
    bspMem->shared.quake1.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->shared.quake1.nummodels = 0;
  bspMem->shared.quake1.numplanes = 0;
  bspMem->shared.quake1.numclipnodes = 0;
  hullnum = 2;
  CreateSingleHull(bspMem);

  bspMem->shared.quake1.nummodels = 0;
  bspMem->shared.quake1.numplanes = 0;
  bspMem->shared.quake1.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 {
    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;
}
