# include "dvomerge.h"

/* 

There are two modes for the measure table: sorted and unsorted. 

In sorted mode, all measures associated with a given average are in a single block.  The block
is pointed to by average->measureOffset and the range is average->Nmeasure

In unsorted mode, it is not possible to go directly from average to measure without scanning.
In this case, it is necessary to use the value measure->averef to find the corresponding
average entry.  

Note that average->measureOffset and measure->averef are only valid for a given load of the
data: they refer to the sequence number in the data blocks.

next_meas is a list of the equivalent sequence of the measure block as if it were sorted.

to find the sequence of measurements for a given average:
n_0 = average->measureOffset
n_1 = next_meas[n_0]
n_i = next_meas[n_i-1]

*/

/*** Measure ****************************************************************************************/

/* build the initial links assuming the table is sorted, 
   not partial, and has a correct set of average[].measureOffset,Nmeasure values */
off_t *init_measure_links (Average *average, off_t Naverage, Measure *measure, off_t Nmeasure) {

  off_t i, j, N;
  off_t *next_meas;

  if (!measure) return NULL;
  if (SKIP_MEASURE) return NULL;

  N = 0;

  ALLOCATE (next_meas, off_t, Nmeasure);
  for (i = 0; i < Naverage; i++) {
    if (!average[i].Nmeasure) continue;
    off_t m = average[i].measureOffset;
    myAssert (measure[m].averef == i, "not sorted");
    for (j = 0; j < average[i].Nmeasure - 1; j++, N++) {
      myAssert (measure[m+j+1].averef == i, "not sorted");
      next_meas[N] = N + 1;
      if (N >= Nmeasure) {
	fprintf (stderr, "WARNING: N out of bounds (1)\n");
      }
    }
    next_meas[N] = -1;
    if (N >= Nmeasure) {
      fprintf (stderr, "WARNING: N out of bounds (2)\n");
    }

    if (N >= Nmeasure) {
      fprintf (stderr, "overflow in init_measure_links\n");
      abort ();
    }
    N++;
  }
  return (next_meas);
}

/* construct measure links which are valid FOR THIS LOAD
 * - if we have a full load, we will get links which can
 *   be used by other programs (eg, relphot, etc)
 * - if we have a partial load, the links are only valid
 *   for that partial load
 */ 

off_t *build_measure_links (Average *average, off_t Naverage, Measure *measure, off_t Nmeasure) {

  off_t i, m, k, Nm, averef;
  off_t *next_meas;

  ALLOCATE (next_meas, off_t, Nmeasure);

  /* reset the Nm, offset values for average */
  for (i = 0; i < Naverage; i++) {
    average[i].measureOffset = -1;
    average[i].Nmeasure     =  0;
  }

  for (Nm = 0; Nm < Nmeasure; Nm++) {
    averef = measure[Nm].averef;
    m = average[averef].measureOffset;  
    next_meas[Nm] = -1;

    if (m == -1) { /* no links yet for source */
      average[averef].measureOffset = Nm;
      average[averef].Nmeasure     = 1;
      continue;
    }

    for (k = 0; next_meas[m] != -1; k++) {
      m = next_meas[m];
      if (m >= Nmeasure) {
	fprintf (stderr, "WARNING: m out of bounds (1)\n");
      }
    }

    average[averef].Nmeasure = k + 2;
    next_meas[m] = Nm;
    if (m >= Nmeasure) {
      fprintf (stderr, "WARNING: m out of bounds (2)\n");
    }
  }
  return (next_meas);
}

/* average[].measureOffset, average[].Nmeasure are valid within an addstar run */
int add_meas_link (Average *average, off_t *next_meas, off_t Nmeasure, off_t NMEASURE) {

  off_t k, m;

  /* if we have trouble, check validity of next_meas[m] : m < Nmeasure */
  m = average[0].measureOffset;  

  for (k = 0; k < average[0].Nmeasure - 1; k++)  {
    m = next_meas[m];
    if (m >= NMEASURE) {
      fprintf (stderr, "WARNING: m out of bounds (3)\n");
    }
  }

  /* set up references */
  next_meas[Nmeasure] = -1;
  if (Nmeasure >= NMEASURE) {
    fprintf (stderr, "WARNING: Nmeasure out of bounds (1)\n");
  }

  // if Nmeasure is 0, m may have been mis-set; add to the end
  if ((average[0].Nmeasure == 0) || (m == -1)) {
    average[0].measureOffset = Nmeasure;
  } else {
    next_meas[m] = Nmeasure;
    if (m >= NMEASURE) {
      fprintf (stderr, "WARNING: m out of bounds (4)\n");
    }
  }

  return (TRUE);
}

Measure *sort_measure (Average *average, off_t Naverage, Measure *measure, off_t Nmeasure, off_t *next_meas) {

  off_t i, k, n, np, N;
  Measure *tmpmeasure;

  /* fix order of Measure (memory intensive, but fast) */
  np = -1; // previous entry in case of errors
  N = 0; 
  ALLOCATE (tmpmeasure, Measure, Nmeasure);
  for (i = 0; i < Naverage; i++) {
    if (!average[i].Nmeasure) continue;
    n = average[i].measureOffset;
    average[i].measureOffset = N;
    int myObjID = average[i].objID;
    for (k = 0; k < average[i].Nmeasure; k++, N++) {
      if (n == -1) {
	fprintf (stderr, "entry after %d has a problem\n", (int) np);
	abort();
      }
      tmpmeasure[N] = measure[n]; 
      myAssert (measure[n].averef == i, "error in averef?");
      myAssert (measure[n].objID == myObjID, "error in objID?");
      tmpmeasure[N].averef = i;
      np = n;
      n = next_meas[n];
    }
  }
  free (measure);
  return (tmpmeasure);
}

/*** Missing ****************************************************************************************/

/* build the initial links assuming the table is sorted */
off_t *init_missing_links (Average *average, off_t Naverage, Missing *missing, off_t Nmissing) {
  OHANA_UNUSED_PARAM(missing);

  off_t i, j, N;
  off_t *next_miss;

  N = 0;

  ALLOCATE (next_miss, off_t, Nmissing);
  for (i = 0; i < Naverage; i++) {
    for (j = 0; j < average[i].Nmissing - 1; j++, N++) {
      next_miss[N] = N + 1;
    }
    if (average[i].Nmissing > 0) {
      next_miss[N] = -1;
      if (N >= Nmissing) {
	fprintf (stderr, "overflow in init_missing_links");
	abort ();
      }
      N++;
    }

  }
  return (next_miss);
}

int add_miss_link (Average *average, off_t *next_miss, off_t Nmissing) {

  off_t k, m;

  /* there may be 0 Nmiss; this is not true for Nmeas */
  if (average[0].Nmissing < 1) {
    average[0].missingOffset = Nmissing;
    next_miss[Nmissing] = -1;
    return (TRUE);
  }

  m = average[0].missingOffset;  
  for (k = 0; k < average[0].Nmissing - 1; k++) m = next_miss[m];
  /* set up references */
  next_miss[Nmissing] = -1;
  next_miss[m] = Nmissing;
  return (TRUE);
}

/* Missing does not carry enough information to reconstruct the links
   we must always save the missing table, if it exists */

Missing *sort_missing (Average *average, off_t Naverage, Missing *missing, off_t Nmissing, off_t *next_miss) {

  off_t i, k, n, N;
  Missing *tmpmissing;

  /* fix order of Missing (memory intensive, but fast) */
  N = 0; 
  ALLOCATE (tmpmissing, Missing, Nmissing);
  for (i = 0; i < Naverage; i++) {
    n = average[i].missingOffset;
    average[i].missingOffset = N;
    for (k = 0; k < average[i].Nmissing; k++, N++) {
      tmpmissing[N] = missing[n]; 
      n = next_miss[n];
    }
  }
  free (missing);
  return (tmpmissing);
}

/*** Lensing ****************************************************************************************/

/* build the initial links assuming the table is sorted, 
   not partial, and has a correct set of average[].lensingOffset,Nlensing values */
off_t *init_lensing_links (Average *average, off_t Naverage, Lensing *lensing, off_t Nlensing) {

  off_t i, j, N;
  off_t *next_lens;

  if (!lensing) return NULL;
  if (SKIP_LENSING) return NULL;

  N = 0;

  ALLOCATE (next_lens, off_t, Nlensing);
  for (i = 0; i < Naverage; i++) {
    if (!average[i].Nlensing) continue;
    off_t m = average[i].lensingOffset;
    myAssert (lensing[m].averef == i, "not sorted");
    for (j = 0; j < average[i].Nlensing - 1; j++, N++) {
      myAssert (lensing[m+j+1].averef == i, "not sorted");
      next_lens[N] = N + 1;
      if (N >= Nlensing) {
	fprintf (stderr, "WARNING: N out of bounds (1)\n");
      }
    }
    next_lens[N] = -1;
    if (N >= Nlensing) {
      fprintf (stderr, "WARNING: N out of bounds (2)\n");
    }

    if (N >= Nlensing) {
      fprintf (stderr, "overflow in init_lensing_links\n");
      abort ();
    }
    N++;
  }
  return (next_lens);
}

/* construct lensing links which are valid FOR THIS LOAD
 * - if we have a full load, we will get links which can
 *   be used by other programs (eg, relphot, etc)
 * - if we have a partial load, the links are only valid
 *   for that partial load
 */ 

off_t *build_lensing_links (Average *average, off_t Naverage, Lensing *lensing, off_t Nlensing) {

  off_t i, m, k, Nm, averef;
  off_t *next_lens;

  ALLOCATE (next_lens, off_t, Nlensing);

  /* reset the Nm, offset values for average */
  for (i = 0; i < Naverage; i++) {
    average[i].lensingOffset = -1;
    average[i].Nlensing     =  0;
  }

  for (Nm = 0; Nm < Nlensing; Nm++) {
    averef = lensing[Nm].averef;
    m = average[averef].lensingOffset;  
    next_lens[Nm] = -1;

    if (m == -1) { /* no links yet for source */
      average[averef].lensingOffset = Nm;
      average[averef].Nlensing     = 1;
      continue;
    }

    for (k = 0; next_lens[m] != -1; k++) {
      m = next_lens[m];
      if (m >= Nlensing) {
	fprintf (stderr, "WARNING: m out of bounds (1)\n");
      }
    }

    average[averef].Nlensing = k + 2;
    next_lens[m] = Nm;
    if (m >= Nlensing) {
      fprintf (stderr, "WARNING: m out of bounds (2)\n");
    }
  }
  return (next_lens);
}

/* average[].lensingOffset, average[].Nlensing are valid within an addstar run */
int add_lens_link (Average *average, off_t *next_lens, off_t Nlensing, off_t NLENSING) {

  off_t k, m;

  /* if we have trouble, check validity of next_lens[m] : m < Nlensing */
  m = average[0].lensingOffset;  

  for (k = 0; k < average[0].Nlensing - 1; k++)  {
    m = next_lens[m];
    if (m >= NLENSING) {
      fprintf (stderr, "WARNING: m out of bounds (3)\n");
    }
  }

  /* set up references */
  next_lens[Nlensing] = -1;
  if (Nlensing >= NLENSING) {
    fprintf (stderr, "WARNING: Nlensing out of bounds (1)\n");
  }

  // if Nlensing is 0, m may have been mis-set; add to the end
  if ((average[0].Nlensing == 0) || (m == -1)) {
    average[0].lensingOffset = Nlensing;
  } else {
    next_lens[m] = Nlensing;
    if (m >= NLENSING) {
      fprintf (stderr, "WARNING: m out of bounds (4)\n");
    }
  }

  return (TRUE);
}

Lensing *sort_lensing (Average *average, off_t Naverage, Lensing *lensing, off_t Nlensing, off_t *next_lens) {

  off_t i, k, n, np, N;
  Lensing *tmplensing;

  /* fix order of Lensing (memory intensive, but fast) */
  np = -1;
  N = 0; 
  ALLOCATE (tmplensing, Lensing, Nlensing);
  for (i = 0; i < Naverage; i++) {
    if (!average[i].Nlensing) continue;
    n = average[i].lensingOffset;
    average[i].lensingOffset = N;
    int myObjID = average[i].objID;
    for (k = 0; k < average[i].Nlensing; k++, N++) {
      if (n == -1) {
	fprintf (stderr, "entry after %d has a problem\n", (int) np);
	abort();
      }
      tmplensing[N] = lensing[n]; 
      myAssert (lensing[n].averef == i, "error in averef");
      myAssert (lensing[n].objID == myObjID, "error in objID?");
      tmplensing[N].averef = i;
      np = n;
      n = next_lens[n];
    }
  }
  free (lensing);
  return (tmplensing);
}

/*** StarPar ********************************************************************************/

/* build the initial links assuming the table is sorted, 
   not partial, and has a correct set of average[].starparOffset,Nstarpar values */
off_t *init_starpar_links (Average *average, off_t Naverage, StarPar *starpar, off_t Nstarpar) {

  off_t i, j, N;
  off_t *next_star;

  if (!starpar) return NULL;
  if (SKIP_STARPAR) return NULL;

  N = 0;

  // NOTE that is we choose DVO_SKIP_STARPAR, catalog.starpar is NULL.
  // this code will let merge_catalogs_old.c do nothing for starpar
  ALLOCATE (next_star, off_t, Nstarpar);
  if (!starpar) return next_star;

  for (i = 0; i < Naverage; i++) {
    if (!average[i].Nstarpar) continue;
    off_t m = average[i].starparOffset;
    myAssert (starpar[m].averef == i, "not sorted");
    for (j = 0; j < average[i].Nstarpar - 1; j++, N++) {
      myAssert (starpar[m+j+1].averef == i, "not sorted");
      next_star[N] = N + 1;
      if (N >= Nstarpar) {
	fprintf (stderr, "WARNING: N out of bounds (1)\n");
      }
    }
    next_star[N] = -1;
    if (N >= Nstarpar) {
      fprintf (stderr, "WARNING: N out of bounds (2)\n");
    }

    if (N >= Nstarpar) {
      fprintf (stderr, "overflow in init_starpar_links\n");
      abort ();
    }
    N++;
  }
  return (next_star);
}

/* construct starpar links which are valid FOR THIS LOAD
 * - if we have a full load, we will get links which can
 *   be used by other programs (eg, relphot, etc)
 * - if we have a partial load, the links are only valid
 *   for that partial load
 */ 

off_t *build_starpar_links (Average *average, off_t Naverage, StarPar *starpar, off_t Nstarpar) {

  off_t i, m, k, Nm, averef;
  off_t *next_star;

  ALLOCATE (next_star, off_t, Nstarpar);
  if (!starpar) return next_star;

  /* reset the Nm, offset values for average */
  for (i = 0; i < Naverage; i++) {
    average[i].starparOffset = -1;
    average[i].Nstarpar     =  0;
  }

  for (Nm = 0; Nm < Nstarpar; Nm++) {
    averef = starpar[Nm].averef;
    m = average[averef].starparOffset;  
    next_star[Nm] = -1;

    if (m == -1) { /* no links yet for source */
      average[averef].starparOffset = Nm;
      average[averef].Nstarpar     = 1;
      continue;
    }

    for (k = 0; next_star[m] != -1; k++) {
      m = next_star[m];
      if (m >= Nstarpar) {
	fprintf (stderr, "WARNING: m out of bounds (1)\n");
      }
    }

    average[averef].Nstarpar = k + 2;
    next_star[m] = Nm;
    if (m >= Nstarpar) {
      fprintf (stderr, "WARNING: m out of bounds (2)\n");
    }
  }
  return (next_star);
}

/* average[].starparOffset, average[].Nstarpar are valid within an addstar run */
int add_star_link (Average *average, off_t *next_star, off_t Nstarpar, off_t NSTARPAR) {

  off_t k, m;

  /* if we have trouble, check validity of next_star[m] : m < Nstarpar */
  m = average[0].starparOffset;  

  for (k = 0; k < average[0].Nstarpar - 1; k++)  {
    m = next_star[m];
    if (m >= NSTARPAR) {
      fprintf (stderr, "WARNING: m out of bounds (3)\n");
    }
  }

  /* set up references */
  next_star[Nstarpar] = -1;
  if (Nstarpar >= NSTARPAR) {
    fprintf (stderr, "WARNING: Nstarpar out of bounds (1)\n");
  }

  // if Nmeasure is 0, m may have been mis-set; add to the end
  if ((average[0].Nstarpar == 0) || (m == -1)) {
    average[0].starparOffset = Nstarpar;
  } else {
    next_star[m] = Nstarpar;
    if (m >= NSTARPAR) {
      fprintf (stderr, "WARNING: m out of bounds (4)\n");
    }
  }

  return (TRUE);
}

StarPar *sort_starpar (Average *average, off_t Naverage, StarPar *starpar, off_t Nstarpar, off_t *next_star) {

  off_t i, k, n, np, N;
  StarPar *tmpstarpar;

  if (!starpar) return NULL;
  if (SKIP_STARPAR) return NULL;

  /* fix order of StarPar (memory intensive, but fast) */
  np = -1; // previous entry in case of errors
  N = 0; 
  ALLOCATE (tmpstarpar, StarPar, Nstarpar);
  for (i = 0; i < Naverage; i++) {
    if (!average[i].Nstarpar) continue;
    n = average[i].starparOffset;
    average[i].starparOffset = N;
    int myObjID = average[i].objID;
    for (k = 0; k < average[i].Nstarpar; k++, N++) {
      if (n == -1) {
	fprintf (stderr, "entry after %d has a problem\n", (int) np);
	abort();
      }
      tmpstarpar[N] = starpar[n]; 
      myAssert (starpar[n].averef == i, "error in averef?");
      myAssert (starpar[n].objID == myObjID, "error in objID?");
      tmpstarpar[N].averef = i;
      np = n;
      n = next_star[n];
    }
  }
  free (starpar);
  return (tmpstarpar);
}

/*** GalPhot ****************************************************************************************/

/* build the initial links assuming the table is sorted, 
   not partial, and has a correct set of average[].galphotOffset,Ngalphot values */
off_t *init_galphot_links (Average *average, off_t Naverage, GalPhot *galphot, off_t Ngalphot) {

  off_t i, j, N;
  off_t *next_galp;

  if (galphot) return NULL;
  if (SKIP_GALPHOT) return NULL;

  N = 0;

  ALLOCATE (next_galp, off_t, Ngalphot);
  for (i = 0; i < Naverage; i++) {
    if (!average[i].Ngalphot) continue;
    off_t m = average[i].galphotOffset;
    myAssert (galphot[m].averef == i, "not sorted");
    for (j = 0; j < average[i].Ngalphot - 1; j++, N++) {
      myAssert (galphot[m+j+1].averef == i, "not sorted");
      next_galp[N] = N + 1;
      if (N >= Ngalphot) {
	fprintf (stderr, "WARNING: N out of bounds (1)\n");
      }
    }
    next_galp[N] = -1;
    if (N >= Ngalphot) {
      fprintf (stderr, "WARNING: N out of bounds (2)\n");
    }

    if (N >= Ngalphot) {
      fprintf (stderr, "overflow in init_galphot_links\n");
      abort ();
    }
    N++;
  }
  return (next_galp);
}

/* construct galphot links which are valid FOR THIS LOAD
 * - if we have a full load, we will get links which can
 *   be used by other programs (eg, relphot, etc)
 * - if we have a partial load, the links are only valid
 *   for that partial load
 */ 

off_t *build_galphot_links (Average *average, off_t Naverage, GalPhot *galphot, off_t Ngalphot) {

  off_t i, m, k, Nm, averef;
  off_t *next_galp;

  ALLOCATE (next_galp, off_t, Ngalphot);

  /* reset the Nm, offset values for average */
  for (i = 0; i < Naverage; i++) {
    average[i].galphotOffset = -1;
    average[i].Ngalphot     =  0;
  }

  for (Nm = 0; Nm < Ngalphot; Nm++) {
    averef = galphot[Nm].averef;
    m = average[averef].galphotOffset;  
    next_galp[Nm] = -1;

    if (m == -1) { /* no links yet for source */
      average[averef].galphotOffset = Nm;
      average[averef].Ngalphot     = 1;
      continue;
    }

    for (k = 0; next_galp[m] != -1; k++) {
      m = next_galp[m];
      if (m >= Ngalphot) {
	fprintf (stderr, "WARNING: m out of bounds (1)\n");
      }
    }

    average[averef].Ngalphot = k + 2;
    next_galp[m] = Nm;
    if (m >= Ngalphot) {
      fprintf (stderr, "WARNING: m out of bounds (2)\n");
    }
  }
  return (next_galp);
}

/* average[].galphotOffset, average[].Ngalphot are valid within an addstar run */
int add_galp_link (Average *average, off_t *next_galp, off_t Ngalphot, off_t NGALPHOT) {

  off_t k, m;

  /* if we have trouble, check validity of next_galp[m] : m < Ngalphot */
  m = average[0].galphotOffset;  

  for (k = 0; k < average[0].Ngalphot - 1; k++)  {
    m = next_galp[m];
    if (m >= NGALPHOT) {
      fprintf (stderr, "WARNING: m out of bounds (3)\n");
    }
  }

  /* set up references */
  next_galp[Ngalphot] = -1;
  if (Ngalphot >= NGALPHOT) {
    fprintf (stderr, "WARNING: Ngalphot out of bounds (1)\n");
  }

  // if Ngalphot is 0, m may have been mis-set; add to the end
  if ((average[0].Ngalphot == 0) || (m == -1)) {
    average[0].galphotOffset = Ngalphot;
  } else {
    next_galp[m] = Ngalphot;
    if (m >= NGALPHOT) {
      fprintf (stderr, "WARNING: m out of bounds (4)\n");
    }
  }

  return (TRUE);
}

GalPhot *sort_galphot (Average *average, off_t Naverage, GalPhot *galphot, off_t Ngalphot, off_t *next_galp) {

  off_t i, k, n, np, N;
  GalPhot *tmpgalphot;

  /* fix order of GalPhot (memory intensive, but fast) */
  np = -1;
  N = 0; 
  ALLOCATE (tmpgalphot, GalPhot, Ngalphot);
  for (i = 0; i < Naverage; i++) {
    if (!average[i].Ngalphot) continue;
    n = average[i].galphotOffset;
    average[i].galphotOffset = N;
    int myObjID = average[i].objID;
    for (k = 0; k < average[i].Ngalphot; k++, N++) {
      if (n == -1) {
	fprintf (stderr, "entry after %d has a problem\n", (int) np);
	abort();
      }
      tmpgalphot[N] = galphot[n]; 
      myAssert (galphot[n].averef == i, "error in averef?");
      myAssert (galphot[n].objID == myObjID, "error in objID?");
      tmpgalphot[N].averef = i;
      np = n;
      n = next_galp[n];
    }
  }
  free (galphot);
  return (tmpgalphot);
}

