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

struct node outside_node;					/* 76                                          // portals outside the world face this */

FILE *pf;							/* 4 */
int num_visportals;						/* 4 */
int num_visleafs;						/* 4                                           // leafs the player can be in */
struct visportal *portals;
struct visleaf **leafs;

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

/*
 * =============
 * AddPortalToNodes
 * =============
 */
void AddPortalToNodes(register struct portal *p, register struct node *front, register struct node *back)
{
  if (p->nodes[0] || p->nodes[1])
    Error("AddPortalToNode: allready included");

  p->nodes[0] = front;
  p->next[0] = front->portals;
  front->portals = p;

  p->nodes[1] = back;
  p->next[1] = back->portals;
  back->portals = p;
}

/*
 * =============
 * RemovePortalFromNode
 * =============
 */
void RemovePortalFromNode(register struct portal *portal, register struct node *l)
{
  struct portal **pp, *t;

  /* remove reference to the current portal */
  pp = &l->portals;
  while (1) {
    t = *pp;
    if (!t)
      Error("RemovePortalFromNode: portal not in leaf");

    if (t == portal)
      break;

    if (t->nodes[0] == l)
      pp = &t->next[0];
    else if (t->nodes[1] == l)
      pp = &t->next[1];
    else
      Error("RemovePortalFromNode: portal not bounding leaf");
  }

  if (portal->nodes[0] == l) {
    *pp = portal->next[0];
    portal->nodes[0] = NULL;
  }
  else if (portal->nodes[1] == l) {
    *pp = portal->next[1];
    portal->nodes[1] = NULL;
  }
}

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

void PrintPortal(register struct portal *p)
{
  int i;
  struct winding *w;

  w = p->winding;
  for (i = 0; i < w->numpoints; i++)
    mprintf("( %g %g %g )\n", w->points[i][0], w->points[i][1], w->points[i][2]);
}

/*
 * ================
 * MakeHeadnodePortals
 * 
 * The created portals will face the global outside_node
 * ================
 */
void MakeHeadnodePortals(__memBase, register struct node *node)
{
  vec3_t bounds[2];
  short int i, j, n;
  struct portal *p, *portals[6];
  struct plane bplanes[6], *pl;
  int side;

  Draw_ClearWindow();

  /* pad with some space so there will never be null volume leafs */
  for (i = 0; i < 3; i++) {
    bounds[0][i] = brushset->mins[i] - SIDESPACE;
    bounds[1][i] = brushset->maxs[i] + SIDESPACE;
  }

  outside_node.contents = CONTENTS_SOLID;
  outside_node.portals = NULL;

  for (i = 0; i < 3; i++)
    for (j = 0; j < 2; j++) {
      n = j * 3 + i;

      p = AllocPortal();
      portals[n] = p;

      pl = &bplanes[n];
      __bzero(pl, sizeof(*pl));
      if (j) {
	pl->normal[i] = -1;
	pl->dist = -bounds[j][i];
      }
      else {
	pl->normal[i] = 1;
	pl->dist = bounds[j][i];
      }
      p->planenum = FindPlane(bspMem, pl, &side);

      p->winding = BaseWindingForPlane(pl);
      if (side)
	AddPortalToNodes(p, &outside_node, node);
      else
	AddPortalToNodes(p, node, &outside_node);
    }

  /* clip the basewindings by all the other planes */
  for (i = 0; i < 6; i++) {
    for (j = 0; j < 6; j++) {
      if (j == i)
	continue;
      portals[i]->winding = ClipWinding(portals[i]->winding, &bplanes[j], TRUE);
    }
  }
}

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

void CheckLeafPortalConsistancy(__memBase, register struct node *node)
{
  int side, side2;
  struct portal *p, *p2;
  struct plane plane, plane2;
  int i;
  struct winding *w;
  float dist;

  side = side2 = 0;						/* quiet compiler warning */

  for (p = node->portals; p; p = p->next[side]) {
    if (p->nodes[0] == node)
      side = 0;
    else if (p->nodes[1] == node)
      side = 1;
    else
      Error("CutNodePortals_r: mislinked portal");
    CheckWindingInNode(p->winding, node);
    CheckWindingArea(p->winding);

    /* check that the side orders are correct */
#ifdef EXHAUSIVE_CHECK
    if (p->planenum >= bspMem->numbrushplanes || p->planenum < 0)
      Error("looking for nonexisting plane %d\n", p->planenum);
#endif
    plane = bspMem->brushplanes[p->planenum];
    PlaneFromWinding(p->winding, &plane2);

    for (p2 = node->portals; p2; p2 = p2->next[side2]) {
      if (p2->nodes[0] == node)
	side2 = 0;
      else if (p2->nodes[1] == node)
	side2 = 1;
      else
	Error("CutNodePortals_r: mislinked portal");
      w = p2->winding;
      for (i = 0; i < w->numpoints; i++) {
	dist = DotProduct(w->points[i], plane.normal) - plane.dist;
	if ((side == 0 && dist < -1) || (side == 1 && dist > 1)) {
	  eprintf("portal siding direction is wrong\n");
	  return;
	}
      }
    }
  }
}

/*
 * ================
 * CutNodePortals_r
 * 
 * ================
 */
void CutNodePortals_r(__memBase, register struct node *node)
{
  struct plane *plane, clipplane;
  struct node *f, *b, *other_node;
  struct portal *p, *new_portal, *next_portal;
  struct winding *w, *frontwinding, *backwinding;
  int side;

  /* CheckLeafPortalConsistancy (node); */

  /* seperate the portals on node into it's children */
  if (node->contents)
    return;							/* at a leaf, no more dividing */

#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];

  f = node->children[0];
  b = node->children[1];

  /*
   * create the new portal by taking the full plane winding for the cutting plane
   * and clipping it by all of the planes from the other portals
   */
  new_portal = AllocPortal();
  new_portal->planenum = node->planenum;

  w = BaseWindingForPlane(&bspMem->brushplanes[node->planenum]);
  side = 0;							/* shut up compiler warning */

  for (p = node->portals; p; p = p->next[side]) {
    clipplane = bspMem->brushplanes[p->planenum];
    if (p->nodes[0] == node)
      side = 0;
    else if (p->nodes[1] == node) {
      clipplane.dist = -clipplane.dist;
      VectorNegate(clipplane.normal);
      side = 1;
    }
    else
      Error("CutNodePortals_r: mislinked portal");

    w = ClipWinding(w, &clipplane, TRUE);
    if (!w) {
      eprintf("CutNodePortals_r: new portal was clipped away\n");
      break;
    }
  }

  if (w) {
    /* if the plane was not clipped on all sides, there was an error */
    new_portal->winding = w;
    AddPortalToNodes(new_portal, f, b);
  }

  /* partition the portals */
  for (p = node->portals; p; p = next_portal) {
    if (p->nodes[0] == node)
      side = 0;
    else if (p->nodes[1] == node)
      side = 1;
    else
      Error("CutNodePortals_r: mislinked portal");
    next_portal = p->next[side];

    other_node = p->nodes[!side];
    RemovePortalFromNode(p, p->nodes[0]);
    RemovePortalFromNode(p, p->nodes[1]);

    /* cut the portal into two portals, one on each side of the cut plane */
    DivideWinding(p->winding, plane, &frontwinding, &backwinding);

    if (!frontwinding) {
      if (side == 0)
	AddPortalToNodes(p, b, other_node);
      else
	AddPortalToNodes(p, other_node, b);
      continue;
    }
    if (!backwinding) {
      if (side == 0)
	AddPortalToNodes(p, f, other_node);
      else
	AddPortalToNodes(p, other_node, f);
      continue;
    }

    /* the winding is split */
    new_portal = AllocPortal();
    *new_portal = *p;
    new_portal->winding = backwinding;
    FreeWinding(p->winding);
    p->winding = frontwinding;

    if (side == 0) {
      AddPortalToNodes(p, f, other_node);
      AddPortalToNodes(new_portal, b, other_node);
    }
    else {
      AddPortalToNodes(p, other_node, f);
      AddPortalToNodes(new_portal, other_node, b);
    }
  }

  DrawLeaf(f, 1);
  DrawLeaf(b, 2);

  CutNodePortals_r(bspMem, f);
  CutNodePortals_r(bspMem, b);

}

/*
 * ==================
 * PortalizeWorld
 * 
 * Builds the exact polyhedrons for the nodes and leafs
 * ==================
 */
void PortalizeWorld(__memBase, struct node *headnode)
{
  mprintf("----- Portalize ---------\n");

  MakeHeadnodePortals(bspMem, headnode);
  CutNodePortals_r(bspMem, headnode);
}

/*
 * ==================
 * FreeAllPortals
 * 
 * ==================
 */
void FreeAllPortals(struct node *node)
{
  struct portal *p, *nextp;

  if (!node->contents) {
    FreeAllPortals(node->children[0]);
    FreeAllPortals(node->children[1]);
  }

  for (p = node->portals; p; p = nextp) {
    if (p->nodes[0] == node)
      nextp = p->next[0];
    else
      nextp = p->next[1];
    RemovePortalFromNode(p, p->nodes[0]);
    RemovePortalFromNode(p, p->nodes[1]);
    FreeWinding(p->winding);
    FreePortal(p);
  }
}

/*
 * ==============================================================================
 * 
 * PORTAL FILE GENERATION
 * 
 * ==============================================================================
 */

void WritePortalFile_r(__memBase, register struct node *node)
{
  int i;
  struct portal *p;
  struct winding *w;
  struct plane *pl, plane2;

  if (!node->contents) {
    WritePortalFile_r(bspMem, node->children[0]);
    WritePortalFile_r(bspMem, node->children[1]);
    return;
  }

  if (node->contents == CONTENTS_SOLID)
    return;

  for (p = node->portals; p;) {
    w = p->winding;
    if (w && p->nodes[0] == node) {
      if (((bspMem->bspOptions & QBSP_WATERVIS) &&
	   ((p->nodes[0]->contents == CONTENTS_WATER && p->nodes[1]->contents == CONTENTS_EMPTY) ||
	    (p->nodes[0]->contents == CONTENTS_EMPTY && p->nodes[1]->contents == CONTENTS_WATER)))
	  || ((bspMem->bspOptions & QBSP_SLIMEVIS) &&
	      ((p->nodes[0]->contents == CONTENTS_SLIME && p->nodes[1]->contents == CONTENTS_EMPTY) ||
	       (p->nodes[0]->contents == CONTENTS_EMPTY && p->nodes[1]->contents == CONTENTS_SLIME)))
	  || ((bspMem->bspOptions & QBSP_WATERVIS) && (bspMem->bspOptions & QBSP_SLIMEVIS) &&
	      ((p->nodes[0]->contents == CONTENTS_WATER && p->nodes[1]->contents == CONTENTS_SLIME) ||
	       (p->nodes[0]->contents == CONTENTS_SLIME && p->nodes[1]->contents == CONTENTS_WATER)))
	  || (p->nodes[0]->contents == p->nodes[1]->contents)) {
	/*
	 * write out to the file
	 *
	 * sometimes planes get turned around when they are very near
	 * the changeover point between different axis.  interpret the
	 * plane the same way vis will, and flip the side orders if needed
	 */
#ifdef EXHAUSIVE_CHECK
	if (p->planenum >= bspMem->numbrushplanes || p->planenum < 0)
	  Error("looking for nonexisting plane %d\n", p->planenum);
#endif
	pl = &bspMem->brushplanes[p->planenum];
	PlaneFromWinding(w, &plane2);

	if (DotProduct(pl->normal, plane2.normal) < 0.99)	/* backwards... */
	  fprintf(pf, "%i %i %i ", w->numpoints, p->nodes[1]->visleafnum, p->nodes[0]->visleafnum);
	else
	  fprintf(pf, "%i %i %i ", w->numpoints, p->nodes[0]->visleafnum, p->nodes[1]->visleafnum);

	for (i = 0; i < w->numpoints; i++)
	  fprintf(pf, "( %g %g %g ) ", w->points[i][0], w->points[i][1], w->points[i][2]);
	fprintf(pf, "\n");
      }
    }

    if (p->nodes[0] == node)
      p = p->next[0];
    else
      p = p->next[1];
  }

}

/*
 * ================
 * NumberLeafs_r
 * ================
 */
void NumberLeafs_r(__memBase, register struct node *node)
{
  struct portal *p;

  if (!node->contents) {					/* decision node */
    node->visleafnum = -99;
    NumberLeafs_r(bspMem, node->children[0]);
    NumberLeafs_r(bspMem, node->children[1]);
    return;
  }

  Draw_ClearWindow();
  DrawLeaf(node, 1);

  if (node->contents == CONTENTS_SOLID) {			/* solid block, viewpoint never inside */
    node->visleafnum = -1;
    return;
  }

  node->visleafnum = num_visleafs++;

  for (p = node->portals; p;) {
    if (p->nodes[0] == node) {					/* only write out from first leaf */
      if (((bspMem->bspOptions & QBSP_WATERVIS) &&
	   ((p->nodes[0]->contents == CONTENTS_WATER && p->nodes[1]->contents == CONTENTS_EMPTY) ||
	    (p->nodes[0]->contents == CONTENTS_EMPTY && p->nodes[1]->contents == CONTENTS_WATER)))
	  || ((bspMem->bspOptions & QBSP_SLIMEVIS) &&
	      ((p->nodes[0]->contents == CONTENTS_SLIME && p->nodes[1]->contents == CONTENTS_EMPTY) ||
	       (p->nodes[0]->contents == CONTENTS_EMPTY && p->nodes[1]->contents == CONTENTS_SLIME)))
	  || ((bspMem->bspOptions & QBSP_WATERVIS) && (bspMem->bspOptions & QBSP_SLIMEVIS) &&
	      ((p->nodes[0]->contents == CONTENTS_WATER && p->nodes[1]->contents == CONTENTS_SLIME) ||
	       (p->nodes[0]->contents == CONTENTS_SLIME && p->nodes[1]->contents == CONTENTS_WATER)))
	  || (p->nodes[0]->contents == p->nodes[1]->contents)) {
	num_visportals++;
      }
      p = p->next[0];
    }
    else
      p = p->next[1];
  }
}

/*
 * ================
 * WritePortalfile
 * ================
 */
void WritePortalfile(__memBase, struct node *headnode, char *portfilename)
{
  /* set the visleafnum field in every leaf and count the total number of portals */
  num_visleafs = 0;
  num_visportals = 0;
  NumberLeafs_r(bspMem, headnode);

  /* write the file */
  mprintf("    - writing %s\n", portfilename);
  pf = __fopen(portfilename, "w");
  if (!pf)
    Error(failed_fileopen, portfilename);

  fprintf(pf, "%s\n", PORTALFILE);
  fprintf(pf, "%i\n", num_visleafs);
  fprintf(pf, "%i\n", num_visportals);

  WritePortalFile_r(bspMem, headnode);

  __fclose(pf);
}

/*
 * ============
 * LoadPortals
 * ============
 */
void LoadPortals(char *prtBuf)
{
  int i, j;
  int read;
  struct visportal *p;
  struct visleaf *l;
  char magic[80];
  int numpoints;
  struct winding *w;
  int leafnums[2];
  struct plane plane;

  mprintf("----- LoadPortals -------\n");

  if (sscanf(prtBuf, "%79s\n%i\n%i\n%n", magic, &num_visleafs, &num_visportals, &read) != 3)
    Error("LoadPortals: failed to read header");
  prtBuf += read;
  if (strcmp(magic, PORTALFILE))
    Error("LoadPortals: not a portal file");

  /* each file portal is split into two memory portals */
  if (!(portals = (struct visportal *)tmalloc(2 * num_visportals * sizeof(struct visportal))))
      Error(failed_memoryunsize, "visportal");

  /*leafs = (struct visleaf *)tmalloc(num_visleafs * sizeof(struct visleaf)); */
  if (!(leafs = (struct visleaf **)tmalloc(num_visleafs * sizeof(struct visleaf *))))
      Error(failed_memoryunsize, "visleaf");

  for (i = 0, p = portals; i < num_visportals; i++) {
    if (sscanf(prtBuf, "%i %i %i %n", &numpoints, &leafnums[0], &leafnums[1], &read) != 3)
      Error("LoadPortals: reading portal %i\n", i);
    if (numpoints > MAX_POINTS_ON_WINDING)
      Error("LoadPortals: portal %i has too many points\n", i);
    if ((unsigned)leafnums[0] > num_visleafs || (unsigned)leafnums[1] > num_visleafs)
      Error("LoadPortals: reading portal %i\n", i);
    prtBuf += read;

    w = p->winding = NewWinding(numpoints);
    w->original = TRUE;
    w->numpoints = numpoints;

    for (j = 0; j < numpoints; j++) {
      int k;

      /* scanf into double, then assign to vec_t */
      if ((k = sscanf(prtBuf, "( %g %g %g ) %n", &w->points[j][0], &w->points[j][1], &w->points[j][2], &read)) != 3)
	Error("LoadPortals: reading portal %i (%i elements)\n", i, k);
      prtBuf += read;
    }
    sscanf(prtBuf, "\n%n", &read);
    prtBuf += read;

    /* calc plane */
    PlaneFromWinding(w, &plane);

    /* create forward portal */
    /*l = &leafs[leafnums[0]]; */
    if (!(l = leafs[leafnums[0]]))
      l = leafs[leafnums[0]] = AllocLeaf(MAX_PORTALS_ON_LEAF);
    else if (l->numportals == MAX_PORTALS_ON_LEAF)
      Error("Leaf with too many portals");
    l->portals[l->numportals] = p;
    l->numportals++;

    p->winding = w;
    VectorNegateTo(plane.normal, p->plane.normal);
    p->plane.dist = -plane.dist;
    p->leaf = leafnums[1];
    p++;

    /* create backwards portal */
    /*l = &leafs[leafnums[1]]; */
    if (!(l = leafs[leafnums[1]]))
      l = leafs[leafnums[1]] = AllocLeaf(MAX_PORTALS_ON_LEAF);
    else if (l->numportals == MAX_PORTALS_ON_LEAF)
      Error("Leaf with too many portals");
    l->portals[l->numportals] = p;
    l->numportals++;

    p->winding = w;
    p->plane = plane;
    p->leaf = leafnums[0];
    p++;

    mprogress(num_visportals, i + 1);
  }

  mprintf("%5i num_visportals\n", num_visportals);
  mprintf("%5i num_visleafs\n", num_visleafs);

  for (i = 0; i < num_visleafs; i++) {
    if (leafs[i])
      RecalcLeaf(leafs[i]);
    else
      leafs[i] = AllocLeaf(MAX_PORTALS_ON_LEAF);
  }
}
