#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_list =    "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_list;
    '\n';

    num_sel_gates = 0;
    while (num_sel_gates < GT_ENTRIES_NUM)
    {
        "%d", num_sel_gates;
        st = StrGet(" Gate: ");
        if (!*st)
        {
            Free(st);
            return;
        }
        i = ListMatch(st, gate_type_list, 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 = I64Get("\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 = I64Get("", -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 = I64Get("", -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;
    ProgressBarsReset;
    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;
            }
        }
    }
    ProgressBarsReset;

    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;
            CharGet;
            DocClear;
            Refresh(2, TRUE);
            DocBottom;
        }
    }
    catch
        PutExcept;
    SettingsPop;
    CleanUp;
}