/* Generate ffdata.{c,h} for finite fields, used in
   finfield.c
 */

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


#define MAX_FF 65536

unsigned char is_prime[MAX_FF + 1];
unsigned char is_ff[MAX_FF + 1];
unsigned      deg[MAX_FF + 1];
unsigned      ch[MAX_FF + 1];
unsigned      num_ff;
unsigned      max_deg;

void make_primes()
{
    unsigned int i, j;
    for (i = 2; i <= MAX_FF; i++)
        is_prime[i] = 1;
    for (i = 2; i * i <= MAX_FF; i++) {
        if (is_prime[i]) {
            for (j = 2 * i; j <= MAX_FF; j += i)
                is_prime[j] = 0;
        }
    }
}

void make_ff()
{
    unsigned int i, j, d;
    for (i = 2; i <= MAX_FF; i++) {
        if (is_prime[i]) {
            for (j = i, d = 1; j <= MAX_FF; j *= i, d++) {
                is_ff[j] = 1;
                deg[j] = d;
                ch[j] = i;
                num_ff++;
            }
            if (d > 0 && d - 1 > max_deg)
                max_deg = d - 1;
        }
    }
}

unsigned int needed_bits(unsigned int x)
{
    if (x == 0)
        return 0;
    return 1 + needed_bits(x >> 1);
}

void emit_code(FILE * dest, int header)
{
    unsigned i, j;
    fprintf(dest, "/* This file has been generated by etc/ffgen.c during build,\n");
    fprintf(dest, "   do not edit */\n");
    if (header) {
        fprintf(dest, "#ifndef GAP_FFDATA_H\n");
        fprintf(dest, "#define GAP_FFDATA_H\n");
        fprintf(dest, "\n");
        fprintf(dest, "#include <stdint.h>\n");
        fprintf(dest, "\n");
        fprintf(dest, "enum {\n");
        fprintf(dest, "    NUM_SHORT_FINITE_FIELDS = %d,\n", num_ff);
        fprintf(dest, "    MAXSIZE_GF_INTERNAL = %d,\n", MAX_FF);
        fprintf(dest, "    DEGREE_LARGEST_INTERNAL_FF = %d,\n", max_deg);
        fprintf(dest, "    FIELD_BITS_FFE = %d,\n", needed_bits(num_ff));
        fprintf(dest, "    VAL_BITS_FFE = %d\n", needed_bits(MAX_FF - 1));
        fprintf(dest, "};\n");
        fprintf(dest, "\n");
        fprintf(dest, "extern const uint32_t SizeFF[NUM_SHORT_FINITE_FIELDS+1];\n");
        fprintf(dest, "extern const uint8_t DegrFF[NUM_SHORT_FINITE_FIELDS+1];\n");
        fprintf(dest, "extern const uint32_t CharFF[NUM_SHORT_FINITE_FIELDS+1];\n");
        fprintf(dest, "\n");
        if (num_ff < 65536)
            fprintf(dest, "typedef uint16_t FF;\n");
        else
            fprintf(dest, "typedef uint32_t FF;\n");
        if (MAX_FF <= 65536)
            fprintf(dest, "typedef uint16_t FFV;\n");
        else
            fprintf(dest, "typedef uint32_t FFV;\n");
        fprintf(dest, "\n");
        fprintf(dest, "#endif // GAP_FFDATA_H\n");
    }
    else {
        fprintf(dest, "#include \"ffdata.h\"\n");
        fprintf(dest, "\n");
        fprintf(dest, "/* Entries are ordered by value of p^d; can use binary search\n");
        fprintf(dest, " * to find them. Indices start at 1.\n");
        fprintf(dest, " */\n");
        fprintf(dest, "\n");
        fprintf(dest, "const uint8_t DegrFF[NUM_SHORT_FINITE_FIELDS+1] = {\n");
        fprintf(dest, " %3d,", 0);
        for (i = 0, j = 1; i <= MAX_FF; i++) {
            if (is_ff[i]) {
                fprintf(dest, "%3d,", deg[i]);
                j++;
                j %= 16;
                if (!j)
                    fprintf(dest, "\n ");
            }
        }
        if (j)
            fprintf(dest, "\n");
        fprintf(dest, "};\n");
        fprintf(dest, "\n");
        fprintf(dest, "const uint32_t CharFF[NUM_SHORT_FINITE_FIELDS+1] = {\n");
        fprintf(dest, " %9d,", 0);
        for (i = 0, j = 1; i <= MAX_FF; i++) {
            if (is_ff[i]) {
                fprintf(dest, "%9d,", ch[i]);
                j++;
                j %= 6;
                if (!j)
                    fprintf(dest, "\n ");
            }
        }
        if (j)
            fprintf(dest, "\n");
        fprintf(dest, "};\n");
        fprintf(dest, "\n");
        fprintf(dest, "const uint32_t SizeFF[NUM_SHORT_FINITE_FIELDS+1] = {\n");
        fprintf(dest, " %9d,", 0);
        for (i = 0, j = 1; i <= MAX_FF; i++) {
            if (is_ff[i]) {
                fprintf(dest, "%9d,", i);
                j++;
                j %= 6;
                if (!j)
                    fprintf(dest, "\n ");
            }
        }
        if (j)
            fprintf(dest, "\n");
        fprintf(dest, "};\n");
    }
}


void emit_code_to_file_by_basename(int header, char * basename)
{
    char * filename = malloc(strlen(basename) + 3);
    strcpy(filename, basename);
    strcat(filename, header ? ".h" : ".c");
    FILE * f = fopen(filename, "w");
    if (!f) {
        perror("opening output file");
        exit(EXIT_FAILURE);
    }
    emit_code(f, header);
    fclose(f);
}


int main(int argc, char * argv[])
{
    char * opt = argc > 1 ? argv[1] : NULL;
    make_primes();
    make_ff();
    if (!opt)
        emit_code(stdout, 0);
    else if (!strcmp(opt, "h") || !strcmp(opt, ".h") || !strcmp(opt, "-h"))
        emit_code(stdout, 1);
    else if (!strcmp(opt, "c") || !strcmp(opt, ".c") || !strcmp(opt, "-c"))
        emit_code(stdout, 0);
    else if (!strcmp(opt, "-b") && argc > 2) {
        char * basename = argv[2];
        emit_code_to_file_by_basename(0, basename);
        emit_code_to_file_by_basename(1, basename);
    }
    else {
        fprintf(stderr, "Usage: ffgen [-h|-c|-b basename]\n");
        fprintf(stderr, "  -h for header file\n");
        fprintf(stderr, "  -c for C source file\n");
        fprintf(stderr,
                "  -b basename, makes both as basename.c and basename.h\n");
        return 1;
    }
    return 0;
}
