/* tierra.c   14-12-93  main module of Tierra Simulator */
/* Tierra Simulator V4.1: Copyright (c) 1991, 1992, 1993 */
/* Tom Ray & Virtual Life */

#ifndef lint
static char     sccsid[] = "@(#)tierra.c        1.5 7/21/92";
#endif

#include "license.h"
#include "tierra.h"
#include "declare.h"
#include "soup_in.h"
#include <sys/types.h>
#include <fcntl.h>
#ifdef unix
#include <unistd.h>
#endif

#ifdef MEM_CHK
#include <memcheck.h>
#endif

#ifdef ALCOMM

#include "tmonitor.h" 
#include "trequest.h"
#include <mlayer.h>
#include <alcomm.h>

#ifdef CM5
char *malloc();
#endif


#define                                                 _MFnCount       2
static MtDefaultRoutines        _message_fns[] = {
  { TrtPauseSim,        TSimRuncontrol },
  { TrtResumeSim,       TSimRuncontrol }
};

#define                                                 _QFnCount       2
static MtDefaultRoutines        _query_fns[] = {
  { TrtGeneralStats,    TQueryGeneralStats },
  { TrtQueryOrg,        TQueryOrganism }
};

#define                                                 _DIFnCount      4
static MtDefaultRoutines        _dfinit_fns[] = {
  { TrtOrgLifeEvent,    TInitOrgLifeEvents},
  { TrtIPEvent,         TMoveIP},
  { 1,         TMoveD} ,
  { TrtPlanEvent,       TPlan}
};

#endif         /*ALCOMM */

I32s   FindCell;
I32s   itime, mtime;
Event  FindTime;

int t = 432432423 ;

#ifdef CM5

int   NumGBPNBits ;
int   GBPNMask ;
int   NumGBPN ;

HostTNdata TNdata ;
HostGBdata GBdata ;

run_genebank_on_nodes(argc,argv)
int   argc;
char  *argv[];
{
  char   buf[5000] ;
  I32u   rec_tag ;

  I32u	 sourcePN ;
  I32u	 pass_back_parent ;
  I32u	 pending ;

  I32u   num_gb_events = 0 ;

  extern int message_flag ;

  GetSoup(argc,argv); 

  NumCells = 999999999 ;

  message_flag = 1 ;

  while(1) 
    {
      CMMD_receive(CMMD_ANY_NODE,
		   CMMD_ANY_TAG,
		   buf,
		   5000) ;

      rec_tag = CMMD_msg_tag() ;
      
      sourcePN = CMMD_msg_sender() ;

      num_gb_events++ ;

      /* printf("GB %d: received message from PN %d with tag %d\n",CMMD_self_address(),sourcePN,rec_tag>>29) ; */

      switch(GB_TAG(rec_tag))
	{
	case CMMD_CheckGenotype_TAG:

	  /* printf("GB %d: received a checkgenotype tag\n",CMMD_self_address()) ;  */

	  /* need to put addr of genome into buffer structure */
	  ((Dem *)buf)->genome = ((FpInst)&buf[ALLIGN(Dem)]) ;

          pass_back_parent = ((rec_tag >> 10) & 1) ;

	  pending = ((rec_tag >> 11) & 1) ;

	  /* printf("GB %d: checkgenotype with incoming hash value %d and passback = %d\n",
	            CMMD_self_address(),Hash(((Dem *)buf)->gen.size,((Dem *)buf)->genome),pass_back_parent) ; */

          CheckGenotype(*(Dem *)buf,
			rec_tag & 31,
			sourcePN,
			pass_back_parent,
			pending) ;
	  /* printf("GB %d: checkgenotype DONE\n",CMMD_self_address()) ;  */
	  break ;

	case CMMD_ReapGenBook_TAG:

	  /* printf("GB %d: received a reapgenbook tag\n",CMMD_self_address()) ;  */
	  ReapGenBook((Pcells)buf) ;
	  /* printf("GB %d: reapgenbook DONE\n",CMMD_self_address()) ;  */
	  break ;

	case CMMD_DivGenBook_CE_TAG:

	  /* printf("GB %d: received a divgenbook CE tag\n",CMMD_self_address()) ;  */
	  DivGenBook((Pcells)buf,                      /* cell    */
		     0,                                /* InstExe */  /* unused in a mother */
		     0,                                /* reaped  */  /* unused in a mother */
		     1,                                /* mom     */  /* always 1 in a mother */
		     rec_tag & 1,                      /* same    */  
		     0) ;                              /* disk    */  /* unused in a mother */
	  /* printf("GB %d: divgenbook ce DONE\n",CMMD_self_address()) ;  */
	  break ;

	case CMMD_DivGenBook_NC_TAG:

	  /* printf("GB %d: received a divgenbook NC tag\n",CMMD_self_address()) ; */
	  DivGenBook((Pcells)buf,    	                	/* cell    */
		     *(Event *)(&buf[ALLIGN(struct cell)]),    	/* InstExe */
		     rec_tag & 1,                       	/* reaped  */
		     0,                                 	/* mom     */  /* always 0 in a child */ 
		     (rec_tag >> 10) & 1,                      	/* same    */  
		     rec_tag & 2) ;                     	/* disk    */
	  /* printf("GB %d: divgenbook nc DONE\n",CMMD_self_address()) ;  */
	  break ;
		     
	}

      if ((num_gb_events % 2000) == 0)
	SendGBdataToHost() ;
    }
}

void SendGBdataToHost() 
{

/* printf("stats to host from GB pn %d\n",CMMD_self_address()) ; fflush(stdout) ;  */

  CalcGBStats(sl, siz_sl) ; 

  GBdata.AvgSize = AverageSize ;
  GBdata.MaxPop = MaxPop ;
  GBdata.MaxMem = MaxMem ;

  GBdata.MaxGenPop.size =  MaxGenPop.size ;
  GBdata.MaxGenPop.label0 =  MaxGenPop.label[0] ;
  GBdata.MaxGenPop.label1 =  MaxGenPop.label[1] ;
  GBdata.MaxGenPop.label2 =  MaxGenPop.label[2] ;
  GBdata.MaxGenPop.label3 =  MaxGenPop.label[3] ;

  GBdata.MaxGenMem.size =  MaxGenMem.size ;
  GBdata.MaxGenMem.label0 =  MaxGenMem.label[0] ;
  GBdata.MaxGenMem.label1 =  MaxGenMem.label[1] ;
  GBdata.MaxGenMem.label2 =  MaxGenMem.label[2] ;
  GBdata.MaxGenMem.label3 =  MaxGenMem.label[3] ;

  GBdata.NumGenotypes = NumGenotypes ;
  GBdata.NumSizes = NumSizes ;
  GBdata.NumGenDG = NumGenDG ;
  GBdata.NumGenRQ = NumGenRQ ;

/*  CMMD_send_noblock(CMMD_host_node(),
		    GB_data_tag,
		    &GBdata,
		    sizeof(HostGBdata)) ; */

  CMMD_send_and_receive(CMMD_host_node(),
			GB_data_tag,
			&NumCells,
			sizeof(NumCells),
			CMMD_host_node(),
			GB_data_tag,
			&GBdata,
			sizeof(HostGBdata)) ;

    TimePop = 0.; /* these are used in CalcSoupStats() and FEPlan() */
    TimeBirth = TimeDeath = 0L;
    TimeStats = InstExe;
}
#endif /* CM5 */


#ifndef CM5
int main(argc,argv)
#else
int tierra_main(argc,argv)
#endif

    int   argc;
    char  *argv[];
{   

#ifdef CM5
  extern int message_flag ;
#else
  FEStartup();         
#endif /* CM5 */

#ifdef MEM_CHK
    mc_startcheck(FEMemCheck); /* set memcheck=on */
#endif

#ifdef ALCOMM
    _t_init_alcomm();
#endif /* ALCOMM */

    GetSoup(argc,argv); 

#ifdef CM5
  message_flag = 1 ;
#endif /* CM5 */

#ifndef CM5
  if(argc > 2) FEMenu();
#endif /* CM5 */

    life();

#ifndef CM5
    WriteSoup(1);
#endif /* CM5 */

#ifdef MEM_CHK
  mc_endcheck();
#endif

    if(Log)
        fclose(tfp_log);
    FEExit(0);
}

void life() /* doles out time slices and death */
{
#ifdef CM5
I8s  *ReceiveBuf ;
I32u sending_pn ;
I32u  received_len ;

  ReceiveBuf = (I8s *)malloc(SoupSize) ;
#endif /* CM5 */

/*  while(InstExe.m < alive) */
    while(Generations < alive)
    {

#ifndef CM5
      if (KEYHIT()) FEMenu();
#endif /* CM5 */

#ifdef __TURBOC__
    if (GoDown)
        FEError(-1000,EXIT,WRITE,
      "Tierra life() memory fragmented & running low, saving system to disk");
#endif /* __TURBOC__ */

#ifdef ALCOMM
      if ( AL_run_flag == 1 )
        {   (*slicer)();
            ReapCheck();
        }
      _t_life_bookeep();
#else /* ALCOMM */
        (*slicer)();
        ReapCheck();
#endif /* ALCOMM */

#ifdef CM5
    if (CMMD_msg_pending(CMMD_ANY_NODE, 
			 EJECT_TAG))
      {   
	CMMD_receive_block(CMMD_ANY_NODE, 
			   EJECT_TAG,
			   ReceiveBuf, 
			   SoupSize) ;

	sending_pn = CMMD_msg_sender() ;
	received_len = CMMD_bytes_received() ;
	
/* printf("  INJECT from pn %3d to %3d a total of %4d bytes\n",sending_pn,CMMD_self_address(),received_len) ;  */

	Inject(ReceiveBuf, received_len, -1, 0, 0, &ReapRndProp) ;

      }
#endif /* CM5 */
    }
}

void TimeSlice(ce, size_slice)
Pcells  ce;
I32s    size_slice;
{   I8s     a = 0, b = 0;
    I16s    c, di;  /* decoded instruction */
    
    debug = 1; FindTime.m = 0; FindTime.i = 904455;

    ce->c.ib += size_slice;
    for(is.ts = ce->c.ib; is.ts > 0; )
    {   for (c = ce->c.n - 1; c >= 0; c--)
        {   ce->c.a = &(ce->c.c[c]);
            ce->c.ac = c;
#ifdef EXECPROT
            if(PrivExec(ce, ce->c.a->ip, ce->c.a->tr))
            {
#endif /* EXECPROT */
            if (ce->c.sync && ce->c.a->sync)
                continue;
            di = FetchDecode(ce);
            (*id[di].execute)(ce);
            if (Wait)
                continue;
            IncrementIp(ce);
/*                       FOR DEBUGGING PURPOSES
            if (debug && InstExe.m >= FindTime.m && InstExe.i >= FindTime.i) 
            {   
	            a = b;
            }
 */
            SystemWork(ce);
#ifdef EXECPROT
            }
            else
                IncrNPrIp(ce);
#endif /* EXECPROT */
        }
        ce->c.ib -= is.dib;
        is.ts -= is.dib;
    }
}

I16s FetchDecode(ce)
Pcells  ce;
{   I16s    di;

#if PLOIDY == 1
    is.eins = &soup[ce->c.a->ip];
#else /* PLOIDY > 1 */
    is.eins = &soup[ce->c.a->ip][ce->c.a->ex];
#endif /* PLOIDY > 1 */
    di = *(is.eins);
    is.oip = ce->c.a->ip;
    ce->c.p = id + di;
    (*id[di].decode)(ce);

#ifdef MICRO
if( MC_step  > -1L) Micro_Spy(ce);
#endif
    Wait = 0;
    return di;
}

void IncrementIp(ce)
Pcells  ce;
{   I8s  ttr;

    ce->c.a->ip += is.iip;
    ce->c.a->ip = ad(ce->c.a->ip);
#if PLOIDY > 1 /* if there is an error, switch tracks & repeat instruction */
    if (ce->c.a->fl && !flaw(ce))
    {   if (ce->c.a->wc < PLOIDY - 1)
        {   ce->c.a->ip -= is.iip;
            ce->c.a->ip = ad(ce->c.a->ip);
            (ce->c.a->wc)++;
        }
        else
            ce->c.a->wc = 0;
#if PLOIDY == 2
        ce->c.a->ex = !(ce->c.a->ex);
#else /* PLOIDY == 2 */
        ttr = ce->c.a->ex;
        do
        {   ce->c.a->ex = (I8s) tcrand() % PLOIDY;
        }   while (ce->c.a->ex == ttr);
#endif /* PLOIDY == 2 */
    }
#endif /* PLOIDY > 1 */
    if (WatchExe)
        GenExExe(ce, is.oip);
}

void IncrNPrIp(ce)
Pcells  ce;
{   ce->c.a->ip += 1;
    ce->c.a->ip = ad(ce->c.a->ip);
}

void SystemWork(ce)
Pcells  ce;
{
#ifdef KURT
    if (!(ce->c.ac))
#endif /* KURT */
        ce->d.inst += is.dib;
    if(ce->c.a->fl)
    {   ce->d.flags++;
        if(!ce->d.dm)
            UpRprIf(ce);
    }
    CountMutRate++;
    if(CountMutRate >= RateMut && RateMut)
    {   mutate();
        TotMut++;
        CountMutRate = tlrand() % RateMut;
    }
    if(isolate) extract(&cells[extr.a][extr.i]);
    InstExe.i++;
    if(InstExe.i > 1000000L)
    {   InstExe.i %= 1000000L; InstExe.m++;
        if(DropDead && (InstExe.m > LastDiv.m + DropDead))
        {   FEError(-1001,EXIT,WRITE,
                "Tierra SystemWork() soup has died, saving system to disk");
        }
#ifndef CM5
        if(SaveFreq && !(InstExe.m % SaveFreq))
            WriteSoup(0);
#endif /* CM5 */

	stats() ;

#ifdef CM5
	SendTNdataToHost() ;
#endif /* CM5 */

        plan();
      }
}

#ifdef CM5 
void SendTNdataToHost() 
{
  Event  TimeSince;

  TNdata.InstExeM = InstExe.m ;
  TNdata.NumCells = NumCells ;
  TNdata.RateMut = RateMut ;
  TNdata.RateMovMut = RateMovMut ;
  TNdata.RateFlaw = RateFlaw ;

  CMMD_send_noblock(CMMD_host_node(),
		    TN_data_tag,
		    &TNdata,
		    sizeof(HostTNdata)) ;
}
#endif /* CM5 */

void mutate()
{   I32s  i;

    i = tlrand() % SoupSize;
#if PLOIDY == 1
    mut_site(soup + i, tcrand());
#else /* PLOIDY > 1 */
    mut_site(soup + i, tcrand() % PLOIDY);
#endif /* PLOIDY > 1 */
    MutBookeep(i);
}

void mut_site(s, t)
HpInst  s;
I32s     t;
{   
#if PLOIDY == 1
s[0] ^= (1 << (tirand() % (I16s) InstBitNum)); 
#else /* PLOIDY > 1 */
s[0][t] ^= (1 << (tirand() % (I16s) InstBitNum)); 
#endif /* PLOIDY > 1 */
}


void ReapCheck() /* kill some cells if necessary */
{   I32s  i, t, dtime;
    Event  result;

    if(DistFreq < -.00001 || !reaped || (!DistNext.m && !DistNext.i))
        return;
    dtime = SubEvent(&InstExe, &DistNext, &result);
    if(dtime > 0)
    {   Disturb = InstExe;
        DistNext.m = DistNext.i = 0L;
        t = (I32s) (DistProp * (float) NumCells);
        if(t == NumCells)
            t--;
        for(i = 0; i < t; i++)
            reaper(0,-1);
    }
}

void reaper(ex, sad)
I32s  ex;  /* is a creature executing now ? */
I32s  sad; /* suggested address for reaping */
{   Pcells  ce; /* cell to be reaped */
    Pcells  nc; /* daughter of cell to be reaped */
    Event   result;
    I32s    reap_range,i, j, ll, ul, goon, rtime, found = 0;

    if (MalReapTol && (sad >= 0 && sad < SoupSize))
    {   ce = TopReap; i = 1; goon = 1;
        ll = sad - MalLimit;
        ul = sad + MalLimit + 1;
        while (goon)
        {   goon = 0;
            if (ex && ce == ThisSlice)
                goon = 1;
            if (ce->md.s)
            {   if ((((ce->mm.p + ce->mm.s) < ll) || (ce->mm.p > ul))
                    && (((ce->md.p + ce->md.s) < ll) || (ce->md.p > ul)))
                goon = 1;
            }
            else
            {   if (((ce->mm.p + ce->mm.s) < ll)
                    || (ce->mm.p > ul))
                goon = 1;
            }
            if (goon)
            {   ce = &cells[ce->q.n_reap.a][ce->q.n_reap.i];
                i++;
            }
            if (i > NumCells || (!ce)
                || ((ce->q.this.a == 0) && (ce->q.this.i < 2))) 
                break;
            if (!goon)
            {   found = 1;
                break;
            }
        }
    }
    if (!found)
    {   reap_range =  ReapRndProp * NumCells;
        if(reap_range < 2)
            ce = TopReap;
        else        /* pick rnd cell in top reap_range */
        {   j = tlrand() % reap_range;
            for(ce = TopReap, i = 0; i < j; i++)
            {   ce = &cells[ce->q.n_reap.a][ce->q.n_reap.i];
#ifdef ERROR
                if ((!ce) || ((ce->q.this.a ==0) && (ce->q.this.i < 2))) 
        FEError(-1002,EXIT,WRITE,"Tierra reaper() error, queues corrupted!");
#endif
            }
        }
    }
    if(ex && ce == ThisSlice)
    {   if(ce == TopReap)
            ce = &cells[ce->q.n_reap.a][ce->q.n_reap.i];
        else
            ce = &cells[ce->q.p_reap.a][ce->q.p_reap.i];
    }
    if(ex && DistFreq > -.00001 && !DistNext.m && !DistNext.i)
    {   rtime = SubEvent(&InstExe, &Disturb, &result);
        rtime = (I32s) (DistFreq * (float) rtime);
        DistNext = Disturb = InstExe;
        DistNext.m += rtime / 1000000L;
        DistNext.i += rtime % 1000000L;
        DistNext.m += DistNext.i / 1000000L;
        DistNext.i %= 1000000L;
    }
    if(NumCells == 1)
    {   FEError(-1003,EXIT,WRITE,
            "Tierra reaper() error 0, attempt to reap last creature");
    }
    /* DAN old ce = TopReap; l_top = TopReap; */
#ifdef ERROR
    if(!ce->ld || !NumCells || (!ce->mm.s && !ce->md.s))
    {   FEError(-1004,EXIT,WRITE,
            "Tierra reaper() error 1, attempt to reap non-existant cell");
    }
#endif
    if(ce->mm.s)
    {
#ifdef ERROR
        if(ce->mm.p < 0 || ce->mm.p >= SoupSize)
        {   FEError(-1005,EXIT,WRITE,
   "Tierra reaper() error 2: attemp to deallocate mother memory not in soup");
        }
#endif
        MemDealloc(ce->mm.p,ce->mm.s);
    }
    if(ce->md.s && (ce->md.p > -1))
    {
#ifdef ERROR
        if(ce->md.p < 0 || ce->md.p >= SoupSize)
        {   FEError(-1006,EXIT,WRITE,
"Tierra reaper() error 3: attemp to deallocate daughter memory not in soup");
        }
#endif
        if(ce->d.ne.a != ce->q.this.a || ce->d.ne.i != ce->q.this.i)
            /* cleanup daughter cpu */
        {   nc = &cells[ce->d.ne.a][ce->d.ne.i];
            if(nc->d.is) /* cleanup daughter instruction pointer */
                RmvFrmSlicer(nc);
            NumCells--;
            InitCell(nc->q.this.a, nc->q.this.i, nc);
        }
        MemDealloc(ce->md.p,ce->md.s);
    }
    if(ce->md.s && ce->md.p == -1)
    {    if (ce->d.genome)
         {   tfree(ce->d.genome);
             ce->d.genome = soup + ce->mm.p;
         }
    }
    RmvFrmSlicer(ce);
    RmvFrmReaper(ce);
    ReapBookeep(ce);
/*  InitCell(ci); done in ReapBookeep(ci); */
}

I32s SubEvent(event1, event2, result) /* subtract e2 from e1 */
Event  *event1, *event2, *result;
{   result->m =  event1->m - event2->m;
    result->m += (event1->i - event2->i) / 1000000L;
    result->i =  (event1->i - event2->i) % 1000000L;
    if(result->m <= 0)
        return result->i + (result->m * 1000000L);
    if(result->i < 0)
    {   --result->m;
        result->i += 1000000L;
    }
    return result->i + (result->m * 1000000L);
}

/* --------------------------------------------------------------------- */
#ifdef ALCOMM
void _t_init_alcomm()
{
  MtStatus      iRet;
  AL_run_flag = 1;

    VPORT = 7001;
    if (( iRet = MInitialise( 0, 0,
                              _message_fns, _MFnCount,
                              _query_fns, _QFnCount,
                              M_NoFns, 0,
                              _dfinit_fns, _DIFnCount )
        ) != MsOK )
      {FEError(-1007,NOEXIT,NOWRITE,
          "Tierra _t_init_alcomm() main MInitialise error (%d)\n", iRet );}

    for(VPORT=7001;VPORT < 8000;VPORT++)
       {
       if (( iRet = MOpenPublicPort( VPORT )) != MsOK )
         {FEError(-1008,NOEXIT,NOWRITE,
             "Tierra _t_init_alcom() main MOpenPublicPort error (%d) on # %d",
             iRet, VPORT);
         }
       else break;
       }
}

void _t_life_bookeep()
{
I32s         acount=50;
MtStatus     iRet;
      if ( AL_run_flag == 1 )
        {
         if ( MIsDFEnabled( TrtIPEvent ) )
              {
              TMoveIP( ThisSlice->mm.p,ThisSlice->c.a->ip);
              }
        }
        else
        {
        if(acount++ > 30)
          {
          FEPrintf(0,0,1,"ALMOND: holding\n");
          acount = 0;
          }
        sleep( 1 );
        }
      if (( iRet = MServiceRequests( M_NoWait )) != MsOK )
      {sprintf(mes[0], "life MServiceRequests error (%d)\n", iRet );
      FEMessage(1);}
}
#endif /* ALCOMM */

/* ----------------------- END OF TIERRA.C ----------------------------- */
