#define TABLE_SIZE_MAX 0x10000

I64 output_found,passes,table_size;

U8 *gate_type_table =NULL,
   *displayed_design=NULL,
   *added_this_pass =NULL;

U16 *input1_table=NULL,
    *input2_table=NULL,
    *input3_table=NULL;

#define CONNECT_WIDTH   16
#define GATE_WIDTH      37

      
      /* Graphics Not Rendered in HTML */
      
      
      /* Graphics Not Rendered in HTML */
      
      
      /* Graphics Not Rendered in HTML */
      
      
      /* Graphics Not Rendered in HTML */
      
      
      /* Graphics Not Rendered in HTML */
      
      
      /* Graphics Not Rendered in HTML */
      
      
      /* Graphics Not Rendered in HTML */
      
      
      
      /* Graphics Not Rendered in HTML */
      
      
      
      /* Graphics Not Rendered in HTML */
      
      
      
      /* Graphics Not Rendered in HTML */
      

U8 *gate_type_lst="NULL\0OUTPUT\0INPUT\0"
     "NOT\0AND\0OR\0NAND\0NOR\0XOR\0AND3\0OR3\0NAND3\0NOR3\0";

#define GT_NULL   0  //Specifies that table entry has not been filled-in
#define GT_OUTPUT 1  //Specifies the table entry is a desired output
#define GT_INPUT  2  //Specifies that table entry comes from an input signal

#define GT_FIRST_REAL_GATE 3
#define GT_NOT          3
#define GT_AND          4
#define GT_OR           5
#define GT_NAND         6
#define GT_NOR          7
#define GT_XOR          8
#define GT_AND3         9
#define GT_OR3          10
#define GT_NAND3        11
#define GT_NOR3         12
#define GT_ENTRIES_NUM  13

#define SEL_GATES_NUM   128

U8 *imgs[GT_ENTRIES_NUM]={NULL,NULL,NULL,
<NOT>,<AND>,<OR>,<NAND>,<NOR>,<XOR>,<AND3>,<OR3>,<NAND3>,<NOR3>};

I64 num_inputs_entered,num_outputs_entered;
I64 num_sel_gates,
    sel_gates[SEL_GATES_NUM];

U0 GetGates()
{
  I64 i;
  U8 *st;

  "\nEnter the available gate types in the order you prefer them to be used.\n"
        "Your choices are:\n";
  for (i=GT_FIRST_REAL_GATE;i<GT_ENTRIES_NUM;i++)
    "%z ",i,gate_type_lst;
  '\n';

  num_sel_gates=0;
  while (num_sel_gates<GT_ENTRIES_NUM) {
    "%d",num_sel_gates;
    st=GetStr(" Gate: ");
    if (!*st) {
      Free(st);
      return;
    }
    i=LstMatch(st,gate_type_lst,LMF_IGNORE_CASE);
    Free(st);
    if (i<GT_FIRST_REAL_GATE)
      "Invalid response\n";
    else
      sel_gates[num_sel_gates++]=i;
  }
}

U0 Init()
{
  I64 i;

  do {
    table_size=GetI64("\nTable size in hex (3 input=0x100,4=0x10000): ",0);
    if (table_size>TABLE_SIZE_MAX)
      "Too large\n";
    else if (table_size<1) {
      "No table specified, aborting.\n";
      throw;
    }
  } while (table_size>TABLE_SIZE_MAX);

  gate_type_table =CAlloc(table_size*sizeof(U8));
  displayed_design=MAlloc((table_size+7)/8);
  added_this_pass =MAlloc((table_size+7)/8);
  input1_table    =MAlloc(table_size*sizeof(U16));
  input2_table    =MAlloc(table_size*sizeof(U16));
  input3_table    =MAlloc(table_size*sizeof(U16));

  "\nEnter the hex truth table column values of inputs.\n";
  if (table_size<=0x100)
    "For example, enter A=0xF0, B=0xCC and C=0xAA.\n";
  else
    "For example, enter A=0xFF00, B=0xF0F0, C=0xCCCC and D=0xAAAA.\n";
  num_inputs_entered=0;
  while (TRUE) {
    "Input %C: ",'A'+num_inputs_entered;
    i=GetI64("",-1);
    if (i<0) break;
    if (i>table_size)
      "Too large\n";
    else {
      if (gate_type_table[i])
        "Duplicate\n";
      else {
        gate_type_table[i]=GT_INPUT;
        input1_table[i]=num_inputs_entered++;
      }
    }
  }
  if (!num_inputs_entered) {
    "No inputs specified, aborting.\n";
    throw;
  }

  "\nEnter the hex truth table columns values of the outputs.\n";
  num_outputs_entered=0;
  while (TRUE) {
    "Output %C: ",'A'+num_outputs_entered;
    i=GetI64("",-1);
    if (i<0) break;
    if (i>table_size)
      "Too large\n";
    else {
      if (gate_type_table[i]==GT_INPUT)
        "To produce this output, connect to input %C\n",
              'A'+input1_table[i];
      else if (gate_type_table[i]==GT_OUTPUT)
        "Duplicate\n";
      else {
        gate_type_table[i]=GT_OUTPUT;
        input1_table[i]=num_outputs_entered++;
      }
    }
  }

  if (!num_outputs_entered) {
    "No output specified, aborting.\n";
    throw;
  }
}

U0 DrawDesign(CDC *dc,I64 *_y,I64 output,I64 depth,I64 *_x_out,I64 *_y_out)
{
  I64 y=*_y,type=gate_type_table[output],
        xx=(passes-depth)*(GATE_WIDTH+CONNECT_WIDTH),yy=y,
        x1,y1,x2,y2,x3,y3;
  if (_x_out) *_x_out=xx;
  if (_y_out) *_y_out=yy;
  if (Bt(displayed_design,output) && type!=GT_INPUT) {
    dc->color=GREEN;
    GrPrint(dc,xx-FONT_WIDTH*3,y-4,"Dup");
    y+=10;
  } else
    switch (type) {
      case GT_INPUT:
        dc->color=GREEN;
        GrPrint(dc,xx-FONT_WIDTH-4,y-4,"%C",'A'+input1_table[output]);
        y+=10;
        break;
      case GT_NOT:
        if (!Bt(displayed_design,output)) {
          y+=16;
          DrawDesign(dc,&y,input1_table[output],depth+1,&x1,&y1);
          yy=y1;

          dc->color=BLUE;
          Sprite3(dc,xx,yy,0,imgs[type]);

          dc->color=RED;
          GrLine(dc,xx-GATE_WIDTH,yy,x1,y1);
          if (_y_out) *_y_out=yy;
        }
        break;
      case GT_AND:
      case GT_OR:
      case GT_NAND:
      case GT_NOR:
      case GT_XOR:
        if (!Bt(displayed_design,output)) {
          y+=24;
          DrawDesign(dc,&y,input1_table[output],depth+1,&x1,&y1);
          DrawDesign(dc,&y,input2_table[output],depth+1,&x2,&y2);
          yy=(y1+y2)/2;

          dc->color=BLUE;
          Sprite3(dc,xx,yy,0,imgs[type]);

          dc->color=RED;
          GrLine(dc,xx-GATE_WIDTH,yy-4,x1,y1);
          GrLine(dc,xx-GATE_WIDTH,yy+4,x2,y2);
          if (_y_out) *_y_out=yy;
        }
        break;
      case GT_AND3:
      case GT_OR3:
      case GT_NAND3:
      case GT_NOR3:
        if (!Bt(displayed_design,output)) {
          y+=32;
          DrawDesign(dc,&y,input1_table[output],depth+1,&x1,&y1);
          DrawDesign(dc,&y,input2_table[output],depth+1,&x2,&y2);
          DrawDesign(dc,&y,input3_table[output],depth+1,&x3,&y3);
          yy=(y1+y2+y3)/3;

          dc->color=BLUE;
          Sprite3(dc,xx,yy,0,imgs[type]);

          dc->color=RED;
          GrLine(dc,xx-GATE_WIDTH,yy-8,x1,y1);
          GrLine(dc,xx-GATE_WIDTH,yy  ,x2,y2);
          GrLine(dc,xx-GATE_WIDTH,yy+8,x3,y3);
          if (_y_out) *_y_out=yy;
        }
        break;
    }
  dc->color=BLACK;
  GrPrint(dc,xx,yy+3,"%04X",output);
  Bts(displayed_design,output);
  if (_y) *_y=y;
}

U0 DrawIt(CTask *,CDC *dc)
{
  I64 y=0;
  MemSet(displayed_design,0,(table_size+7)/8*sizeof(Bool));
  DrawDesign(dc,&y,output_found,0,NULL,NULL);
}

U0 FillNot(Bool *chged,I64 *num_outputs_found)
{
  I64 i,j,old_type;
  for (i=0;i<table_size;i++)
    if (gate_type_table[i]>GT_OUTPUT && !Bt(added_this_pass,i)) {
      progress1=i;
      j= (~i) & (table_size-1);
      old_type=gate_type_table[j];
      if (old_type<GT_INPUT) {
        gate_type_table[j]=GT_NOT;
        input1_table[j]=i;
        Bts(added_this_pass,j);
        *chged=TRUE;
        if (old_type==GT_OUTPUT) {
          if (output_found<0) output_found=j;
          *num_outputs_found=*num_outputs_found+1;
        }
      }
    }
}

U0 FillAnd(Bool *chged,I64 *num_outputs_found)
{
  I64 i,j,k,old_type;
  for (i=0;i<table_size;i++)
    if (gate_type_table[i]>GT_OUTPUT && !Bt(added_this_pass,i))
      for (k=0;k<table_size;k++)
        if (gate_type_table[k]>GT_OUTPUT && !Bt(added_this_pass,k)) {
          progress1=i;
          j= (i & k) & (table_size-1);
          old_type=gate_type_table[j];
          if (old_type<GT_INPUT) {
            gate_type_table[j]=GT_AND;
            input1_table[j]=i;
            input2_table[j]=k;
            Bts(added_this_pass,j);
            *chged=TRUE;
            if (old_type==GT_OUTPUT) {
              if (output_found<0) output_found=j;
              *num_outputs_found=*num_outputs_found+1;
            }
          }
        }
}

U0 FillOr(Bool *chged,I64 *num_outputs_found)
{
  I64 i,j,k,old_type;
  for (i=0;i<table_size;i++)
    if (gate_type_table[i]>GT_OUTPUT && !Bt(added_this_pass,i))
      for (k=0;k<table_size;k++)
        if (gate_type_table[k]>GT_OUTPUT && !Bt(added_this_pass,k)) {
          progress1=i;
          j= (i | k) & (table_size-1);
          old_type=gate_type_table[j];
          if (old_type<GT_INPUT) {
            gate_type_table[j]=GT_OR;
            input1_table[j]=i;
            input2_table[j]=k;
            Bts(added_this_pass,j);
            *chged=TRUE;
            if (old_type==GT_OUTPUT) {
              if (output_found<0) output_found=j;
              *num_outputs_found=*num_outputs_found+1;
            }
          }
        }
}

U0 FillNAnd(Bool *chged,I64 *num_outputs_found)
{
  I64 i,j,k,old_type;
  for (i=0;i<table_size;i++)
    if (gate_type_table[i]>GT_OUTPUT && !Bt(added_this_pass,i))
      for (k=0;k<table_size;k++)
        if (gate_type_table[k]>GT_OUTPUT && !Bt(added_this_pass,k)) {
          progress1=i;
          j= (~ (i & k)) & (table_size-1);
          old_type=gate_type_table[j];
          if (old_type<GT_INPUT) {
            gate_type_table[j]=GT_NAND;
            input1_table[j]=i;
            input2_table[j]=k;
            Bts(added_this_pass,j);
            *chged=TRUE;
            if (old_type==GT_OUTPUT) {
              if (output_found<0) output_found=j;
              *num_outputs_found=*num_outputs_found+1;
            }
          }
        }
}

U0 FillNOr(Bool *chged,I64 *num_outputs_found)
{
  I64 i,j,k,old_type;
  for (i=0;i<table_size;i++)
    if (gate_type_table[i]>GT_OUTPUT && !Bt(added_this_pass,i))
      for (k=0;k<table_size;k++)
        if (gate_type_table[k]>GT_OUTPUT && !Bt(added_this_pass,k)) {
          progress1=i;
          j= (~ (i | k)) & (table_size-1);
          old_type=gate_type_table[j];
          if (old_type<GT_INPUT) {
            gate_type_table[j]=GT_NOR;
            input1_table[j]=i;
            input2_table[j]=k;
            Bts(added_this_pass,j);
            *chged=TRUE;
            if (old_type==GT_OUTPUT) {
              if (output_found<0) output_found=j;
              *num_outputs_found=*num_outputs_found+1;
            }
          }
        }
}

U0 FillXor(Bool *chged,I64 *num_outputs_found)
{
  I64 i,j,k,old_type;
  for (i=0;i<table_size;i++)
    if (gate_type_table[i]>GT_OUTPUT && !Bt(added_this_pass,i))
      for (k=0;k<table_size;k++)
        if (gate_type_table[k]>GT_OUTPUT && !Bt(added_this_pass,k)) {
          progress1=i;
          j= (i ^ k) & (table_size-1);
          old_type=gate_type_table[j];
          if (old_type<GT_INPUT) {
            gate_type_table[j]=GT_XOR;
            input1_table[j]=i;
            input2_table[j]=k;
            Bts(added_this_pass,j);
            *chged=TRUE;
            if (old_type==GT_OUTPUT) {
              if (output_found<0) output_found=j;
              *num_outputs_found=*num_outputs_found+1;
            }
          }
        }
}

U0 FillAnd3(Bool *chged,I64 *num_outputs_found)
{
  I64 i,j,k,l,old_type;
  for (i=0;i<table_size;i++)
    if (gate_type_table[i]>GT_OUTPUT && !Bt(added_this_pass,i))
      for (k=0;k<table_size;k++)
        if (gate_type_table[k]>GT_OUTPUT && !Bt(added_this_pass,k))
          for (l=0;l<table_size;l++)
            if (gate_type_table[l]>GT_OUTPUT && !Bt(added_this_pass,l)) {
              progress1=i;
              j= (i & k & l) & (table_size-1);
              old_type=gate_type_table[j];
              if (old_type<GT_INPUT) {
                gate_type_table[j]=GT_AND3;
                input1_table[j]=i;
                input2_table[j]=k;
                input3_table[j]=l;
                Bts(added_this_pass,j);
                *chged=TRUE;
                if (old_type==GT_OUTPUT) {
                  if (output_found<0) output_found=j;
                  *num_outputs_found=*num_outputs_found+1;
                }
              }
            }
}

U0 FillOr3(Bool *chged,I64 *num_outputs_found)
{
  I64 i,j,k,l,old_type;
  for (i=0;i<table_size;i++)
    if (gate_type_table[i]>GT_OUTPUT && !Bt(added_this_pass,i))
      for (k=0;k<table_size;k++)
        if (gate_type_table[k]>GT_OUTPUT && !Bt(added_this_pass,k))
          for (l=0;l<table_size;l++)
            if (gate_type_table[l]>GT_OUTPUT && !Bt(added_this_pass,l)) {
              progress1=i;
              j= (i | k | l) & (table_size-1);
              old_type=gate_type_table[j];
              if (old_type<GT_INPUT) {
                gate_type_table[j]=GT_OR3;
                input1_table[j]=i;
                input2_table[j]=k;
                input3_table[j]=l;
                Bts(added_this_pass,j);
                *chged=TRUE;
                if (old_type==GT_OUTPUT) {
                  if (output_found<0) output_found=j;
                  *num_outputs_found=*num_outputs_found+1;
                }
              }
            }
}

U0 FillNAnd3(Bool *chged,I64 *num_outputs_found)
{
  I64 i,j,k,l,old_type;
  for (i=0;i<table_size;i++)
    if (gate_type_table[i]>GT_OUTPUT && !Bt(added_this_pass,i))
      for (k=0;k<table_size;k++)
        if (gate_type_table[k]>GT_OUTPUT && !Bt(added_this_pass,k))
          for (l=0;l<table_size;l++)
            if (gate_type_table[l]>GT_OUTPUT && !Bt(added_this_pass,l)) {
              progress1=i;
              j= (~(i & k & l)) & (table_size-1);
              old_type=gate_type_table[j];
              if (old_type<GT_INPUT) {
                gate_type_table[j]=GT_NAND3;
                input1_table[j]=i;
                input2_table[j]=k;
                input3_table[j]=l;
                Bts(added_this_pass,j);
                *chged=TRUE;
                if (old_type==GT_OUTPUT) {
                  if (output_found<0) output_found=j;
                  *num_outputs_found=*num_outputs_found+1;
                }
              }
            }
}

U0 FillNOr3(Bool *chged,I64 *num_outputs_found)
{
  I64 i,j,k,l,old_type;
  for (i=0;i<table_size;i++)
    if (gate_type_table[i]>GT_OUTPUT && !Bt(added_this_pass,i))
      for (k=0;k<table_size;k++)
        if (gate_type_table[k]>GT_OUTPUT && !Bt(added_this_pass,k))
          for (l=0;l<table_size;l++)
            if (gate_type_table[l]>GT_OUTPUT && !Bt(added_this_pass,l)) {
              progress1=i;
              j= (~(i | k | l)) & (table_size-1);
              old_type=gate_type_table[j];
              if (old_type<GT_INPUT) {
                gate_type_table[j]=GT_NOR3;
                input1_table[j]=i;
                input2_table[j]=k;
                input3_table[j]=l;
                Bts(added_this_pass,j);
                *chged=TRUE;
                if (old_type==GT_OUTPUT) {
                  if (output_found<0) output_found=j;
                  *num_outputs_found=*num_outputs_found+1;
                }
              }
            }
}

I64 FillGateTable()
{
  I64 current_gate,num_outputs_found=0;
  Bool chged=TRUE;
  passes=1;
  output_found=-1;
  ProgressBarsRst;
  progress1_max=table_size;
  '\n';
  while (num_outputs_found<num_outputs_entered && chged) {
    "Pass : %d\n",passes++;
    chged=FALSE;
    MemSet(added_this_pass,0,(table_size+7)/8);
    for (current_gate=0;current_gate<num_sel_gates &&
          num_outputs_found<num_outputs_entered;current_gate++) {
      switch (sel_gates[current_gate]) {
        case GT_NOT:   FillNot  (&chged,&num_outputs_found); break;
        case GT_AND:   FillAnd  (&chged,&num_outputs_found); break;
        case GT_OR:    FillOr   (&chged,&num_outputs_found); break;
        case GT_NAND:  FillNAnd (&chged,&num_outputs_found); break;
        case GT_NOR:   FillNOr  (&chged,&num_outputs_found); break;
        case GT_XOR:   FillXor  (&chged,&num_outputs_found); break;
        case GT_AND3:  FillAnd3 (&chged,&num_outputs_found); break;
        case GT_OR3:   FillOr3  (&chged,&num_outputs_found); break;
        case GT_NAND3: FillNAnd3(&chged,&num_outputs_found); break;
        case GT_NOR3:  FillNOr3 (&chged,&num_outputs_found); break;
      }
    }
  }
  ProgressBarsRst;
  return num_outputs_found;
}

U0 CleanUp()
{
  Free(gate_type_table);
  Free(displayed_design);
  Free(added_this_pass);
  Free(input1_table);
  Free(input2_table);
  Free(input3_table);
}

U0 DigitalLogic()
{
  gate_type_table =NULL;
  displayed_design=NULL;
  added_this_pass =NULL;
  input1_table    =NULL;
  input2_table    =NULL;
  input3_table    =NULL;

  SettingsPush; //See SettingsPush
  AutoComplete;
  WinBorder(ON);
  WinMax;
  DocClear;
  GetGates;
  try {
    Init;
    if (FillGateTable) {
      DocCursor;
      DocClear;
      Fs->draw_it=&DrawIt;
      GetChar;
      DocClear;
      Refresh(2,TRUE);
      DocBottom;
    }
  } catch
    PutExcept;
  SettingsPop;
  CleanUp;
}