/* validate.c
 *
 *   Checks that SURVEX's data structures are valid and consistent
 *
 *   NB The checks currently done aren't very comprehensive - more will be
 *    added if bugs require them
 *
 *   Copyright (C) 1993,1994,1996,2000,2001,2024 Olly Betts
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see
 * <https://www.gnu.org/licenses/>.
 */

#include <config.h>

#include "cavern.h"
#include "filename.h"
#include "message.h"
#include "netbits.h"
#include "validate.h"

/* Maximum absolute value of a coordinate of a fixed station for validate()
 * to allow, in metres.
 *
 * The Northing of the equator in southern hemisphere UTM zones is 10000000m,
 * which seems to be the largest coordinate in any common CRS.  We add an
 * extra 100km to allow for extending such a zone North to cover a cave system
 * which straddles the equator.
 */
#define MAX_POS 10100000.0

static bool validate_prefix_tree(void);
static bool validate_prefix_subtree(prefix *pfx);

static bool validate_station_list(void);

#undef validate
extern bool
validate(void)
{
   bool fOk = true;
   if (!validate_prefix_tree()) fOk = false;
   if (!validate_station_list()) fOk = false;
   if (fOk) puts("*** Data structures passed consistency checks");
   else puts("*** Data structures FAILED consistency checks");
   return fOk;
}

static bool
validate_prefix_tree(void)
{
   bool fOk = true;
   if (root->up != NULL) {
      printf("*** root->up == %p\n", root->up);
      fOk = false;
   }
   if (root->right != NULL) {
      printf("*** root->right == %p\n", root->right);
      fOk = false;
   }
   if (root->stn != NULL) {
      printf("*** root->stn == %p\n", root->stn);
      fOk = false;
   }
   if (root->pos != NULL) {
      printf("*** root->pos == %p\n", root->pos);
      fOk = false;
   }
   fOk &= validate_prefix_subtree(root);
   return fOk;
}

static bool
validate_prefix_subtree(prefix *pfx)
{
   bool fOk = true;
   prefix *pfx2;
   pfx2 = pfx->down;
   /* this happens now, as nodes are freed after solving */
#if 0
   if (pfx2 == NULL) {
      if (pfx->stn == NULL) {
	 printf("*** Leaf prefix '");
	 print_prefix(pfx);
	 printf("' has no station attached\n");
	 fOk = false;
      }
      return fOk;
   }
#endif

   while (pfx2 != NULL) {
      if (pfx2->stn != NULL && pfx2->stn->name != pfx2) {
	 printf("*** Prefix '");
	 print_prefix(pfx2);
	 printf("' ->stn->name is '");
	 print_prefix(pfx2->stn->name);
	 printf("'\n");
	 fOk = false;
      }
      if (pfx2->up != pfx) {
	 printf("*** Prefix '");
	 print_prefix(pfx2);
	 printf("' ->up is '");
	 print_prefix(pfx);
	 printf("'\n");
	 fOk = false;
      }
      fOk &= validate_prefix_subtree(pfx2);
      pfx2 = pfx2->right;
   }
   return fOk;
}

static bool
validate_station_list(void)
{
   bool fOk = true;
   node *stn2;
   int d, d2;

   SVX_ASSERT(!stnlist || !stnlist->prev);
   /* NB: don't use FOR_EACH_STN as it isn't reentrant at present */
   for (int fixed = 0; fixed < 2; ++fixed) {
      for (node *stn = fixed ? fixedlist : stnlist; stn; stn = stn->next) {
	 if (fixed(stn) != fixed) {
	     printf("*** Station '");
	     print_prefix(stn->name);
	     printf("' is %sfixed but on %sfixed list\n",
		    fixed ? "un" : "", fixed ? "" : "un");
	     fOk = false;
	 }

	 bool fGap = false;
#if 0
	 printf("V [%p]<-[%p]->[%p] ", stn->prev, stn, stn->next); print_prefix(stn->name); putnl();
#endif
	 SVX_ASSERT(stn->prev == NULL || stn->prev->next == stn);
	 SVX_ASSERT(stn->next == NULL || stn->next->prev == stn);
	 for (d = 0; d <= 2; d++) {
	    if (!stn->leg[d]) {
	       fGap = true;
	    } else {
	       if (fGap) {
		  printf("*** Station '");
		  print_prefix(stn->name);
		  printf("', leg %d is used, but an earlier leg isn't\n", d);
		  fOk = false;
	       }
	       stn2 = stn->leg[d]->l.to;
	       SVX_ASSERT(stn2);
	       d2 = reverse_leg_dirn(stn->leg[d]);
	       if (stn2->leg[d2] == NULL) {
		  /* fine iff stn is at the disconnected end of a fragment */
		  node *s;
		  /* NB: don't use FOR_EACH_STN as it isn't reentrant at present */
		  for (s = stnlist; s; s = s->next) if (s == stn) break;
		  if (!s) for (s = fixedlist; s; s = s->next) if (s == stn) break;
		  if (s) {
		     printf("*** Station '");
		     print_prefix(stn->name);
		     printf("', leg %d doesn't reciprocate from station '", d);
		     print_prefix(stn2->name);
		     printf("'\n");
		     fOk = false;
		  }
	       } else if (stn2->leg[d2]->l.to == NULL) {
		  printf("*** Station '");
		  print_prefix(stn2->name);
		  printf("' [%p], leg %d points to NULL\n", stn2, d2);
		  fOk = false;
	       } else if (stn2->leg[d2]->l.to!=stn) {
		  /* fine iff stn is at the disconnected end of a fragment */
		  node *s;
		  /* NB: don't use FOR_EACH_STN as it isn't reentrant at present */
		  for (s = stnlist; s; s = s->next) if (s == stn) break;
		  if (!s) for (s = fixedlist; s; s = s->next) if (s == stn) break;
		  if (s) {
		     printf("*** Station '");
		     print_prefix(stn->name);
		     printf("' [%p], leg %d reciprocates via station '", stn, d);
		     print_prefix(stn2->name);
		     printf("' to station '");
		     print_prefix(stn2->leg[d2]->l.to->name);
		     printf("'\n");
		     fOk = false;
		  }
	       } else if ((data_here(stn->leg[d]) != 0) ^
			  (data_here(stn2->leg[d2]) == 0)) {
		  printf("*** Station '");
		  print_prefix(stn->name);
		  printf("' [%p], leg %d reciprocates via station '", stn, d);
		  print_prefix(stn2->name);
		  if (data_here(stn->leg[d]))
		     printf("' - data on both legs\n");
		  else
		     printf("' - data on neither leg\n");
		  fOk = false;
	       }
	       if (data_here(stn->leg[d])) {
		  int i;
		  for (i = 0; i < 3; i++)
		     if (fabs(stn->leg[d]->d[i]) > MAX_POS) {
			printf("*** Station '");
			print_prefix(stn->name);
			printf("', leg %d, d[%d] = %g\n",
			       d, i, (double)(stn->leg[d]->d[i]));
			fOk = false;
		     }
	       }
	    }

	    if (fixed(stn)) {
	       if (fabs(POS(stn, 0)) > MAX_POS ||
		   fabs(POS(stn, 1)) > MAX_POS ||
		   fabs(POS(stn, 2)) > MAX_POS) {
		  printf("*** Station '");
		  print_prefix(stn->name);
		  printf("' fixed at coords (%f,%f,%f)\n",
			 POS(stn, 0), POS(stn, 1), POS(stn, 2) );
		  fOk = false;
	       }
	    }
	 }
      }
   }
   return fOk;
}

#undef dump_node
extern void
dump_node(node *stn)
{
    printf("stn [%p]", stn);
    if (stn->name) {
	if (stn->name->stn == stn) {
	    printf("<->");
	} else {
	    printf("-->");
	}
	printf("name ");
	print_prefix(stn->name);
	printf(" (%p)", stn->name);
    } else {
	printf("-->NULL");
    }

    printf(" colour %ld", stn->colour);
    if (fixed(stn)) {
	printf(" FIXED\n");
    } else {
	putnl();
    }

    for (int d = 0; d <= 2; d++) {
	linkfor *leg = stn->leg[d];
	if (leg) {
	    const char* type;
	    if (data_here(leg)) {
		type = fZeros(&leg->v) ? "=>" : "->";
	    } else {
		type = fZeros(&reverse_leg(leg)->v) ? "<=" : "<-";
	    }
	    printf("  leg #%d %s stn [%p] ", d, type, leg->l.to);
	    print_prefix(leg->l.to->name);
	    printf(" | rev %d\n", reverse_leg_dirn(leg));
	}
    }
}

/* This doesn't cover removed stations - might be nice to have
 * dump_entire_network() which iterates prefix tree */
#undef dump_network
extern void
dump_network(void)
{
   node *stn;
   /* NB: don't use FOR_EACH_STN as it isn't reentrant at present */
   for (stn = fixedlist; stn; stn = stn->next) dump_node(stn);
   for (stn = stnlist; stn; stn = stn->next) dump_node(stn);
}
