/****************************************************************************
    Copyright (C) 1987-2005 by Jeffery P. Hansen

    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, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
****************************************************************************/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "gsim.h"

#define DEMUX_I	0
#define DEMUX_E	1
#define DEMUX_Z	2

#define DEMUX_DELAY_EZ	0
#define DEMUX_DELAY_IZ	1

static void Demux_processEvent(SGate*,EvQueue*,SEvent*);
static int Demux_checkGate(SGate*);

static SGateInfo demux_info = {
  0,
  "demux",0x0,
  3,{{"I",GIO_IN,0},
       {"E",GIO_IN,0},
       {"Z",GIO_OUT,PF_MULTI}},

  {{"E-Z",bit(0),2},
   {"I-Z",bit(1),2},
   {0}},

  Generic_copyGate,
  Demux_processEvent,
  Demux_checkGate,
  Nop_initGate,
  0,
  0,
  0,
  Generic_propFrwdDelay,
  Generic_propBackDelay,
  Generic_delay,
};

void init_demux()
{
  SGateInfo_register(&demux_info,0);
}

static int Demux_checkGate(SGate *g)
{
  SPort *I = g->g_ports.port[DEMUX_I];
  SPort *E = g->g_ports.port[DEMUX_E];
  int in = I->p_net->n_nbits;
  int zn = g->g_ports.num - DEMUX_Z;
  int i;

  if ( (1<<in) < zn) {
    errorGate(g->g_name,"Too few bits on input line.");
    return -1;
  }
  if ( (1<<(in-1)) >= zn) {
    errorGate(g->g_name,"Too many bits on input line.");
    return -1;
  }

  if (E->p_net->n_nbits != 1) {
    errorGate(g->g_name,"Demux enable must be single-bit.");
    return -1;
  }

  for (i = DEMUX_Z;i < g->g_ports.num;i++) {
    if (g->g_ports.port[i]->p_net->n_nbits != 1) {
      errorGate(g->g_name,"Demux outputs must be single-bit.");
      return -1;
    }
  }

  return 0;
}

static void Demux_processEvent(SGate *g,EvQueue *Q,SEvent *E)
{
  SState *I = SGate_allocPortState(g,DEMUX_I);
  SState *EN = SGate_allocPortState(g,DEMUX_E);
  SState *out = alloc_SState();
  unsigned mask = (1<<(I->nbits & SSBITMASK))-1;
  int delay;
  int i;

  SState_reinit(out,1);

  if (IsChangeOn(E,g,DEMUX_E))
    delay = g->g_delayParms[DEMUX_DELAY_EZ];
  else
    delay = g->g_delayParms[DEMUX_DELAY_IZ];


  if ((I->flt[0] & mask) && (EN->flt[0] & 1)) {
    SState_unknown(out);

    for (i = DEMUX_Z;i < g->g_ports.num;i++) {
      SPort *Z = g->g_ports.port[i];
      EvQueue_setPort(Q,Z,out,delay);
    }
  } else {
    int sel = I->one[0] & mask;

    if (!(EN->one[0] & 1)) sel = -1;	/* Nothing selected */

    for (i = DEMUX_Z;i < g->g_ports.num;i++) {
      SPort *Z = g->g_ports.port[i];

      if (i == sel+DEMUX_Z)
	SState_one(out);
      else
	SState_zero(out);

      EvQueue_setPort(Q,Z,out,delay);
    }
  }

  free_SState(I);
  free_SState(EN);
  free_SState(out);
}
