#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include <assert.h>

#include "plan.h"

unsigned long
hashfun (int ista, int idin, int fsta, int fdin)
{
  unsigned long key;

  if ( idin < 0 )
    idin = 15;
  if ( fdin < 0 )
    fdin = 15;
  /* 26-bit key */
  key = ((unsigned long)ista
	 |((unsigned long)idin<<9)
	 |((unsigned long)fsta<<13)
	 |((unsigned long)fdin<<22));
  key *= 108584422UL;
  key >>= 7;
  return (key&0x7ffffU);
}

#define SAMEMISSION 2
#define OTHERMISSION 11

struct blurb_s {
  int seen; /* Whether a solution has been found to the constraint. */
  int din;  /* Direction INdex of arrival at constraint (true value). */
  int prestation; /* Previous station (part of previous constraint). */
  int predin; /* Previous DIN (part of previous constraint), -1 if no DIN constraint. */
  int truepredin; /* True value of previous DIN (never -1). */
  int score; /* Best score satisfying this constraint. */
  int moc;  /* Mission or connection arriving at constraint. */
  int ind;  /* Index in mission of connection (of _previous_ station). */
};

struct hashentry_s {
  int ista;
  int idin;
  int fsta;
  int fdin;
  struct blurb_s data;
};

struct hashline_s {
  int nbentries;
  struct hashentry_s *entries;
} hashtable[0x80000UL];

void
OOM (void)
{
  fprintf (stderr, "Out of memory!\n");
  exit (EXIT_FAILURE);
}

void
init_hashtable (void)
{
  unsigned long l;

  for ( l=0 ; l<0x80000UL ; l++ )
    {
      hashtable[l].nbentries = 0;
      hashtable[l].entries = NULL;
    }
}

void
put_hashtable (int ista, int idin, int fsta, int fdin,
	       const struct blurb_s *data)
{
  unsigned long l;
  int li;

  l = hashfun (ista, idin, fsta, fdin);
  for ( li=0 ; li<hashtable[l].nbentries ; li++ )
    {
      if ( hashtable[l].entries[li].ista == ista
	   && hashtable[l].entries[li].idin == idin
	   && hashtable[l].entries[li].fsta == fsta
	   && hashtable[l].entries[li].fdin == fdin )
	{
	  hashtable[l].entries[li].data = *data;
	  return;
	}
    }
  li = hashtable[l].nbentries;
  if ( ( hashtable[l].entries
	 = realloc (hashtable[l].entries,
		    (++hashtable[l].nbentries)
		    *sizeof(struct hashentry_s)) )
       == NULL )
    OOM ();
  hashtable[l].entries[li].ista = ista;
  hashtable[l].entries[li].idin = idin;
  hashtable[l].entries[li].fsta = fsta;
  hashtable[l].entries[li].fdin = fdin;
  hashtable[l].entries[li].data = *data;
}

void
get_hashtable (int ista, int idin, int fsta, int fdin,
	       struct blurb_s *data)
{
  unsigned long l;
  int li;

  l = hashfun (ista, idin, fsta, fdin);
  for ( li=0 ; li<hashtable[l].nbentries ; li++ )
    {
      if ( hashtable[l].entries[li].ista == ista
	   && hashtable[l].entries[li].idin == idin
	   && hashtable[l].entries[li].fsta == fsta
	   && hashtable[l].entries[li].fdin == fdin )
	{
	  *data = hashtable[l].entries[li].data;
	  return;
	}
    }
  data->seen = 0;
}

void
fill_table (void)
{
  int istation;
  int idin;
  int iteration;
  int i, j;
  char change;
  struct blurb_s in0data;
  struct blurb_s indata;
  struct blurb_s outdata;

  init_hashtable ();
  for ( istation=0 ; istation<NBSTATIONS ; istation++ )
    for ( idin=-1 ; idin<directions[istation].dirsize ; idin++ )
      {
	fprintf (stderr, "IStation %s (idin=%d)...", stations[istation].name, idin);
	outdata.seen = 1;
	outdata.din = idin;
	outdata.prestation = -1;
	outdata.predin = -1;
	outdata.truepredin = -1;
	outdata.score = 0;
	outdata.moc = -1;
	outdata.ind = -1;
	put_hashtable (istation, idin, istation, idin, &outdata);
	put_hashtable (istation, idin, istation, -1, &outdata);
	iteration = 0;
	do
	  {
	    change = 0;
	    for ( i=0 ; i<NBSTATIONS ; i++ )
	      {
		get_hashtable (istation, idin, i, -1, &in0data);
		if ( ! in0data.seen )
		  continue;
		for ( j=0 ; j<directions[i].dirsize ; j++ )
		  {
		    int m;
		    int stat;
		    int din;
		    int temptscore;

		    m = directions[i].dir[j].moc;
		    if ( m < NBMISSIONS )
		      {
			if ( directions[i].dir[j].ind
			     == missions[m].misssize-1 )
			  continue;
			get_hashtable (istation, idin, i, j, &indata);
			if ( indata.seen )
			  {
			    stat = missions[m].miss[directions[i].dir[j].ind+1].sta;
			    din = missions[m].miss[directions[i].dir[j].ind+1].din;
			    temptscore = indata.score+SAMEMISSION;
			    get_hashtable (istation, idin, stat, din, &outdata);
			    if ( ! outdata.seen
				 || outdata.score > temptscore )
			      {
				change = 1;
				outdata.seen = 1;
				outdata.din = din;
				outdata.prestation = i;
				outdata.predin = j;
				outdata.truepredin = j;
				outdata.score = temptscore;
				outdata.moc = m;
				outdata.ind = directions[i].dir[j].ind;
				put_hashtable (istation, idin, stat, din, &outdata);
			      }
			    get_hashtable (istation, idin, stat, -1, &outdata);
			    if ( ! outdata.seen
				 || outdata.score > temptscore )
			      {
				change = 1;
				outdata.seen = 1;
				outdata.din = din;
				outdata.prestation = i;
				outdata.predin = j;
				outdata.truepredin = j;
				outdata.score = temptscore;
				outdata.moc = m;
				outdata.ind = directions[i].dir[j].ind;
				put_hashtable (istation, idin, stat, -1, &outdata);
			      }
			  }
			stat = missions[m].miss[directions[i].dir[j].ind+1].sta;
			din = missions[m].miss[directions[i].dir[j].ind+1].din;
			temptscore = in0data.score+OTHERMISSION;
			get_hashtable (istation, idin, stat, din, &outdata);
			if ( ! outdata.seen
			     || outdata.score > temptscore )
			  {
			    change = 1;
			    outdata.seen = 1;
			    outdata.din = din;
			    outdata.prestation = i;
			    outdata.predin = -1;
			    outdata.truepredin = j;
			    outdata.score = temptscore;
			    outdata.moc = m;
			    outdata.ind = directions[i].dir[j].ind;
			    put_hashtable (istation, idin, stat, din, &outdata);
			  }
			get_hashtable (istation, idin, stat, -1, &outdata);
			if ( ! outdata.seen
			     || outdata.score > temptscore )
			  {
			    change = 1;
			    outdata.seen = 1;
			    outdata.din = din;
			    outdata.prestation = i;
			    outdata.predin = -1;
			    outdata.truepredin = j;
			    outdata.score = temptscore;
			    outdata.moc = m;
			    outdata.ind = directions[i].dir[j].ind;
			    put_hashtable (istation, idin, stat, -1, &outdata);
			  }
		      }
		    else
		      {
			int k;

			for ( k=0 ; k<connections[m-NBMISSIONS].connsize ; k++ )
			  {
			    stat = connections[m-NBMISSIONS].conn[k].sta;
			    din = connections[m-NBMISSIONS].conn[k].din;
			    temptscore = in0data.score;
			    get_hashtable (istation, idin, stat, din, &outdata);
			    if ( ! outdata.seen
				 || outdata.score > temptscore )
			      {
				change = 1;
				outdata.seen = 1;
				outdata.din = din;
				outdata.prestation = i;
				outdata.predin = -1;
				outdata.truepredin = j;
				outdata.score = temptscore;
				outdata.moc = m;
				outdata.ind = directions[i].dir[j].ind;
				put_hashtable (istation, idin, stat, din, &outdata);
			      }
			    get_hashtable (istation, idin, stat, -1, &outdata);
			    if ( ! outdata.seen
				 || outdata.score > temptscore )
			      {
				change = 1;
				outdata.seen = 1;
				outdata.din = din;
				outdata.prestation = i;
				outdata.predin = -1;
				outdata.truepredin = j;
				outdata.score = temptscore;
				outdata.moc = m;
				outdata.ind = directions[i].dir[j].ind;
				put_hashtable (istation, idin, stat, -1, &outdata);
			      }
			  }
		      }
		  }
	      }
	    iteration++;
	  }
	while ( change );
	fprintf (stderr, " %d iterations\n", iteration);
      }
}

int nbintra;
int intra[NBSTATIONS];
int revintra[NBSTATIONS];

void
fill_intra (void)
{
  int i;

  nbintra = 0;
  for ( i=0 ; i<NBSTATIONS ; i++ )
    {
      if ( (stations[i].opts&5) == 0 )
	{
	  revintra[i] = nbintra;
	  intra[nbintra++] = i;
	}
      else
	revintra[i] = -1;
    }
}

int
score_perm (int perm[])
{
  int i;
  int score;
  int din;

  score = 0;
  din = -1;
  for ( i=1 ; i<nbintra ; i++ )
    {
      struct blurb_s data;

      get_hashtable (intra[perm[i-1]], din, intra[perm[i]], -1, &data);
      assert (data.seen);
      score += data.score;
      din = data.din;
    }
  return score;
}

void
random_perm (int perm[])
{
  int i, n;
  char done[NBSTATIONS];

  for ( i=0 ; i<nbintra ; i++ )
    done[i] = 0;
  for ( i=0 ; i<nbintra ; i++ )
    {
      do
	n = random()%nbintra;
      while ( done[n] );
      perm[i] = n;
      done[n] = 1;
    }
}

void
adhoc_perm (int perm[])
{
  int i;
  char done[NBSTATIONS];
  char misdone[NBMISSIONS];
  int ln;
  int mis;

  for ( i=0 ; i<nbintra ; i++ )
    done[i] = 0;
  for ( i=0 ; i<NBMISSIONS ; i++ )
    misdone[i] = 0;
  ln = 0;
  while ( ln < nbintra )
    {
      do
	mis = random()%NBMISSIONS;
      while ( misdone[mis] );
      misdone[mis] = 1;
      for ( i=0 ; i<missions[mis].misssize ; i++ )
	{
	  if ( revintra[missions[mis].miss[i].sta] >= 0
	       && ! done[revintra[missions[mis].miss[i].sta]] )
	    {
	      done[revintra[missions[mis].miss[i].sta]] = 1;
	      perm[ln++] = revintra[missions[mis].miss[i].sta];
	    }
	}
    }
}

void
single_displace_perm (int dstperm[], const int srcperm[])
{
  int i, j;
  int srcn, dstn;

  srcn = random()%nbintra;
  dstn = random()%nbintra;
  dstperm[dstn] = srcperm[srcn];
  j = 0;
  for ( i=0 ; i<nbintra ; i++ )
    {
      if ( j == dstn )
	j++;
      if ( i != srcn )
	dstperm[j++] = srcperm[i];
      if ( j == dstn )
	j++;
    }
  assert (j == nbintra);
}

void
invert_section_perm (int dstperm[], const int srcperm[])
{
  int i0, i1;
  int i;

  i0 = random()%nbintra;
  i1 = random()%nbintra;
  if ( i1 < i0 )
    {
      i = i1;
      i1 = i0;
      i0 = i;
    }
  for ( i=0 ; i<nbintra ; i++ )
    {
      if ( i>=i0 && i<=i1 )
	dstperm[i] = srcperm[i1-(i-i0)];
      else
	dstperm[i] = srcperm[i];
    }
}

void
cyclic_rotate_perm (int dstperm[], const int srcperm[])
{
  int i, k;

  k = random()%nbintra;
  for ( i=0 ; i<nbintra ; i++ )
    dstperm[i] = srcperm[(i+k)%nbintra];
}

void
block_swap_perm (int dstperm[], const int srcperm[])
{
  int i0, i1, im, ims;
  int i;

  i0 = random()%nbintra;
  i1 = random()%nbintra;
  if ( i1 < i0 )
    {
      i = i1;
      i1 = i0;
      i0 = i;
    }
  i1++;
  im = i0 + random()%(i1-i0);
  ims = i1 - (im-i0);
  for ( i=0 ; i<i0 ; i++ )
    dstperm[i] = srcperm[i];
  for ( i=i0 ; i<im ; i++ )
    dstperm[i] = srcperm[ims+(i-i0)];
  for ( i=im ; i<i1 ; i++ )
    dstperm[i] = srcperm[i0+(i-im)];
  for ( i=i1 ; i<nbintra ; i++ )
    dstperm[i] = srcperm[i];
}

#define POPSIZE 5

int fighters[POPSIZE][NBSTATIONS];
int scores[POPSIZE];
int crowned;

void
init_pop (void)
{
  crowned = 0;
  adhoc_perm (fighters[0]);
}

void
vary_pop (void)
{
  adhoc_perm (fighters[1]);
  single_displace_perm (fighters[2], fighters[0]);
  invert_section_perm (fighters[3], fighters[0]);
  block_swap_perm (fighters[4], fighters[0]);
}

void
score_pop (void)
{
  int i;

  for ( i=0 ; i<POPSIZE ; i++ )
    scores[i] = score_perm (fighters[i]);
}

void
crown_pop (void)
{
  int bestscore, besti;
  int i;

  bestscore = 0;
  for ( i=0 ; i<POPSIZE ; i++ )
    if ( i == 0 || scores[i] < bestscore )
      {
	bestscore = scores[i];
	besti = i;
      }
  if ( besti != 0 )
    {
      memcpy (fighters[0], fighters[besti], nbintra*sizeof(int));
      scores[0] = scores[besti];
    }
  crowned = besti;
}

struct step_s {
  int prestation;
  int predin;
  int moc;
  int ind;
  int poststation;
  int postdin;
};

void
make_perm_walk (int perm[], int maxsize, struct step_s *walk, int *size)
{
  int pi;
  int istation, idin, fstation, nextidin;
  int i0, i, i1;
  int station, din, prestation, predin;
  struct blurb_s data;

  i0 = 0;
  idin = -1;
  for ( pi=1 ; pi<nbintra ; pi++ )
    {
      istation = intra[perm[pi-1]];
      fstation = intra[perm[pi]];
      i1 = i0;
      station = fstation;
      din = -1;
      nextidin = -1;
      while ( 1 )
	{
	  get_hashtable (istation, idin, station, din, &data);
	  if ( nextidin < 0 )
	    nextidin = data.din;
	  prestation = data.prestation;
	  predin = data.predin;
	  if ( prestation < 0 )
	    break;
	  station = prestation;
	  din = predin;
	  i1++;
	}
      *size = i1;
      station = fstation;
      din = -1;
      for ( i=i1-1 ; i>=i0 ; i-- )
	{
	  get_hashtable (istation, idin, station, din, &data);
	  prestation = data.prestation;
	  predin = data.predin;
	  if ( i<maxsize )
	    {
	      walk[i].prestation = prestation;
	      walk[i].predin = data.truepredin;
	      walk[i].moc = data.moc;
	      walk[i].ind = data.ind;
	      walk[i].postdin = data.din;
	      walk[i].poststation = station;
	    }
	  station = prestation;
	  din = predin;
	}
      i0 = i1;
      idin = nextidin;
    }
}

int
score_walk (int size, const struct step_s *walk)
{
  int score;
  int i;

  assert (size>0);
  score = 0;
  for ( i=0 ; i<size ; i++ )
    {
      if ( i>0 )
	{
	  assert (walk[i].prestation == walk[i-1].poststation);
	  if ( walk[i].moc < NBMISSIONS )
	    {
	      if ( walk[i].predin == walk[i-1].postdin )
		score += SAMEMISSION;
	      else
		score += OTHERMISSION;
	    }
	}
      else
	{
	  if ( walk[i].moc < NBMISSIONS )
	    score += OTHERMISSION;
	}
      assert (walk[i].predin < directions[walk[i].prestation].dirsize);
      assert (walk[i].moc == directions[walk[i].prestation].dir[walk[i].predin].moc);
      assert (walk[i].ind == directions[walk[i].prestation].dir[walk[i].predin].ind);
      assert (walk[i].moc == directions[walk[i].poststation].dir[walk[i].postdin].moc);
      if ( walk[i].moc < NBMISSIONS )
	{
	  assert (walk[i].ind < missions[walk[i].moc].misssize);
	  assert (walk[i].prestation == missions[walk[i].moc].miss[walk[i].ind].sta);
	  assert (walk[i].predin == missions[walk[i].moc].miss[walk[i].ind].din);
	  assert (walk[i].poststation == missions[walk[i].moc].miss[walk[i].ind+1].sta);
	  assert (walk[i].postdin == missions[walk[i].moc].miss[walk[i].ind+1].din);
	  assert (walk[i].ind+1 == directions[walk[i].poststation].dir[walk[i].postdin].ind);
	}
      else
	{
	  assert (walk[i].ind < connections[walk[i].moc-NBMISSIONS].connsize);
	  assert (walk[i].prestation == connections[walk[i].moc-NBMISSIONS].conn[walk[i].ind].sta);
	  assert (walk[i].predin == connections[walk[i].moc-NBMISSIONS].conn[walk[i].ind].din);
	}
    }
  return score;
}

void
print_walk (int size, const struct step_s *walk)
{
  int i;
  int lastline;

  lastline = -1;
  for ( i=0 ; i<size ; i++ )
    {
      if ( walk[i].moc < NBMISSIONS
	   && missions[walk[i].moc].line != lastline )
	{
	  if ( lastline >= 0 )
	    printf ("\n");
	  printf ("%s: %s, %s",
		  lines[missions[walk[i].moc].line],
		  stations[walk[i].prestation].name,
		  stations[walk[i].poststation].name);
	  lastline = missions[walk[i].moc].line;
	}
      else if ( walk[i].moc < NBMISSIONS )
	{
	  printf (", %s", stations[walk[i].poststation].name);
	}
    }
  printf ("\n");
}

#define MAXWALKSIZE 5000

int
main (void)
{
  long int generation;
  long int last_print;
  long int last_change;
  char changed;
  int walk_size;
  struct step_s walk[MAXWALKSIZE];

  fill_intra ();
  fill_table ();
  srandom (time (NULL));
  generation = 0;
  last_print = 0;
  last_change = 0;
  changed = 1;
  init_pop ();
  while ( 1 )
    {
      vary_pop ();
      score_pop ();
      crown_pop ();
      if ( crowned )
	{
	  last_change = generation;
	  changed = 1;
	}
      if ( generation > 10000 ? crowned : generation%100 == 0 )
	{
	  printf ("generation %ld: SCORE = %d (crowned: %d)\n",
		  generation, scores[0], crowned);
	  fflush (stdout);
	}
      if ( generation-last_print >= 30000 && changed )
	{
	  make_perm_walk (fighters[0], MAXWALKSIZE, walk, &walk_size);
	  printf ("SCORE = %d\n", score_walk (walk_size, walk));
	  print_walk (walk_size, walk);
	  fflush (stdout);
	  last_print = generation;
	  changed = 0;
	}
      if ( generation >= 1000000L && generation-last_change >= 100000L )
	return 0;
      generation++;
    }
  return 0;
}
