#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <time.h>
#include <gmp.h>
#include <openssl/sha.h>
#include <sodium.h>
#include <secp256k1.h>

#define PHI 1.6180339887498948482045868343656381177203091798057628621354486227
#define SQRT_PHI 1.272019649514069
#define MAX_NODES 10000
#define SLOTS_PER_INSTANCE 4
#define NUM_INSTANCES 8
#define TOTAL_SLOTS 32
#define RING_SIZE 8

static uint32_t PRIMES[32] = {2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131};
static uint32_t FIBONACCI[32] = {1,1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,1597,2584,4181,6765,10946,17711,28657,46368,75025,121393,196418,317811,514229,832040,1346269,2178309};

typedef struct { secp256k1_ge p; } G1Point;
typedef struct { secp256k1_ge p; } G2Point;

void pairing_mult(G1Point *a, G2Point *b, const secp256k1_scalar *scalar, secp256k1_context *ctx) {
    secp256k1_gej a_j, result;
    secp256k1_ge_to_gej(&a_j, &a->p);
    secp256k1_ecmult(&result, &a_j, scalar, NULL);
    secp256k1_ge_set_gej(&a->p, &result);
    secp256k1_ge_to_gej(&a_j, &b->p);
    secp256k1_ecmult(&result, &a_j, scalar, NULL);
    secp256k1_ge_set_gej(&b->p, &result);
}

typedef struct {
    uint64_t commitment[2];
    uint64_t proof[8];
} Bulletproof;

void bulletproof_generate(uint64_t value, uint64_t randomness, Bulletproof *bp, secp256k1_context *ctx) {
    if (value >= (1ULL << 32)) {
        fprintf(stderr, "Bulletproof value overflow\n");
        exit(1);
    }
    secp256k1_ge G, H;
    secp256k1_ge_set_xonly(&G, &secp256k1_curve_G);
    secp256k1_scalar value_scalar, rand_scalar;
    secp256k1_scalar_set_int(&value_scalar, value);
    secp256k1_scalar_set_int(&rand_scalar, randomness);
    secp256k1_gej commit;
    secp256k1_ecmult(&commit, &G, &value_scalar, &rand_scalar);
    secp256k1_ge commit_point;
    secp256k1_ge_set_gej(&commit_point, &commit);
    bp->commitment[0] = commit_point.x.n[0];
    bp->commitment[1] = commit_point.y.n[0];
    for (int i = 0; i < 8; i++) {
        bp->proof[i] = (value >> (i * 4)) & 0xFFFF;
    }
}

typedef struct {
    uint8_t signature[RING_SIZE][32];
    uint8_t key_image[32];
} RingSignature;

void ring_signature_generate(uint8_t *message, const uint8_t private_key[32], const uint8_t public_keys[RING_SIZE][32], RingSignature *rs) {
    if (sodium_init() < 0) {
        fprintf(stderr, "libsodium initialization failed\n");
        exit(1);
    }
    uint8_t hash[32];
    crypto_hash_sha256(hash, message, TOTAL_SLOTS);
    uint8_t c[RING_SIZE][32];
    uint8_t s[RING_SIZE][32];
    uint8_t r[32];
    crypto_core_ed25519_random(r);
    for (int i = 0; i < RING_SIZE; i++) {
        if (i == 0) {
            crypto_core_ed25519_scalar_mul(rs->key_image, private_key, hash);
            crypto_core_ed25519_add(c[i], public_keys[i], hash);
            crypto_core_ed25519_scalar_mul(s[i], r, private_key);
        } else {
            crypto_core_ed25519_random(s[i]);
            crypto_core_ed25519_add(c[i], public_keys[i], s[i]);
        }
    }
    for (int i = 0; i < RING_SIZE; i++) {
        memcpy(rs->signature[i], s[i], 32);
    }
}

typedef struct {
    double mantissa;
    int64_t exponent;
    int bits_mant;
    int bits_exp;
} Slot4096;

Slot4096 slot_init(int bits_mant, int bits_exp) {
    Slot4096 slot = { .bits_mant = bits_mant, .bits_exp = bits_exp };
    slot.mantissa = (double)rand() / RAND_MAX;
    slot.exponent = (rand() % (1LL << bits_exp)) - (1LL << (bits_exp - 1));
    return slot;
}

double slot_value(Slot4096 *slot) {
    return slot->mantissa * pow(2.0, (double)slot->exponent);
}

void slot_phi_scale(Slot4096 *slot, double phi) {
    slot->mantissa *= phi;
    if (slot->mantissa >= 1.0) {
        slot->mantissa /= 2.0;
        slot->exponent += 1;
    }
}

uint64_t slot_pack_base_inf(Slot4096 *slot) {
    uint64_t m_int = (uint64_t)(slot->mantissa * (1ULL << slot->bits_mant));
    uint64_t e_int = (uint64_t)(slot->exponent + (1LL << (slot->bits_exp - 1)));
    return (m_int << slot->bits_exp) | (e_int & ((1ULL << slot->bits_exp) - 1));
}

typedef struct {
    Slot4096 slots[SLOTS_PER_INSTANCE];
    int n;
    uint8_t instance_id;
    double omega;
    double r_dim;
} PhiVector;

PhiVector phi_vector_init_dynamic(int instance_id, int bits_mant[SLOTS_PER_INSTANCE], int bits_exp[SLOTS_PER_INSTANCE]) {
    PhiVector vec = { .n = SLOTS_PER_INSTANCE, .instance_id = instance_id };
    vec.omega = pow(PHI, -7 * (instance_id + 1));
    vec.r_dim = 0.3 + instance_id * 0.1;
    for (int i = 0; i < vec.n; i++) {
        vec.slots[i] = slot_init(bits_mant[i], bits_exp[i]);
    }
    return vec;
}

void phi_vector_compute_dn(PhiVector *vec, double r, int k, mpf_t *tmp) {
    mpf_t scaling, phi, r_pow_k, result;
    mpf_init2(scaling, 256);
    mpf_init2(phi, 256);
    mpf_init2(r_pow_k, 256);
    mpf_init2(result, 256);
    mpf_set_d(phi, PHI);

    for (int i = 0; i < vec->n; i++) {
        int n = vec->instance_id * SLOTS_PER_INSTANCE + i + 1;
        mpf_set_ui(tmp[0], FIBONACCI[n-1]);
        mpf_set_ui(tmp[1], PRIMES[n-1]);
        mpf_set_d(tmp[2], pow(2.0, n));
        mpf_set_d(tmp[3], vec->omega);
        mpf_pow_ui(r_pow_k, phi, k);
        mpf_mul(scaling, tmp[0], tmp[1]);
        mpf_mul(scaling, scaling, tmp[2]);
        mpf_div(scaling, scaling, r_pow_k);
        mpf_mul(result, scaling, tmp[3]);
        slot_phi_scale(&vec->slots[i], mpf_get_d(result));
    }
    mpf_clear(scaling);
    mpf_clear(phi);
    mpf_clear(r_pow_k);
    mpf_clear(result);
}

void phi_vector_pack_binary(PhiVector *vec, uint8_t *binary, int offset) {
    for (int i = 0; i < vec->n; i++) {
        uint64_t packed = slot_pack_base_inf(&vec->slots[i]);
        binary[offset + i] = (packed & 0x1) ? 1 : 0;
    }
}

typedef struct {
    G1Point a[2];
    G2Point b[2];
    G1Point c[2];
    uint64_t input[4];
    Bulletproof bp;
    RingSignature rs;
    uint64_t commitment[2];
} ZKP;

void zchgsnark_generate_proof(uint8_t *binary, uint8_t instance_id, uint8_t recursion_depth, uint32_t compute_units, const uint8_t private_key[32], const uint8_t public_keys[RING_SIZE][32], ZKP *proof, secp256k1_context *ctx) {
    if (compute_units >= (1ULL << 32)) {
        fprintf(stderr, "Compute units overflow\n");
        exit(1);
    }
    unsigned char hash[32];
    crypto_hash_sha256(hash, binary, TOTAL_SLOTS);
    uint64_t witness[8];
    for (int i = 0; i < 4; i++) {
        witness[i] = binary[instance_id * 4 + i];
        witness[i + 4] = (uint64_t)(PHI * 1e6 * witness[i]) % (1ULL << 32);
    }
    secp256k1_scalar scalar;
    secp256k1_scalar_set_b32(&scalar, hash, NULL);
    pairing_mult(&proof->a[0], &proof->b[0], &scalar, ctx);
    proof->input[0] = *(uint64_t*)&hash[0];
    proof->input[1] = instance_id;
    proof->input[2] = recursion_depth;
    proof->input[3] = compute_units;
    bulletproof_generate(compute_units, *(uint64_t*)&hash[0], &proof->bp, ctx);
    proof->commitment[0] = proof->bp.commitment[0];
    proof->commitment[1] = proof->bp.commitment[1];
    ring_signature_generate(binary, private_key, public_keys, &proof->rs);
    if (binary[2] == binary[6]) {
        proof->input[0] ^= (1ULL << 63);
    }
}

typedef struct {
    PhiVector *vectors;
    int num_vectors;
    int *slot_assignments;
    uint8_t binary[TOTAL_SLOTS];
    unsigned char hash[32];
    uint64_t epoch;
    uint32_t compute_units_total;
    uint8_t private_key[32];
    uint8_t public_keys[RING_SIZE][32];
} Monolith;

void monolith_init(Monolith *mono, int max_nodes) {
    if (sodium_init() < 0) {
        fprintf(stderr, "libsodium initialization failed\n");
        exit(1);
    }
    mono->vectors = (PhiVector *)calloc(max_nodes, sizeof(PhiVector));
    if (!mono->vectors) {
        fprintf(stderr, "Memory allocation failed\n");
        exit(1);
    }
    mono->slot_assignments = (int *)calloc(max_nodes, sizeof(int));
    if (!mono->slot_assignments) {
        free(mono->vectors);
        fprintf(stderr, "Memory allocation failed\n");
        exit(1);
    }
    mono->num_vectors = 0;
    mono->epoch = 0;
    mono->compute_units_total = 0;
    memset(mono->binary, 0, TOTAL_SLOTS);
    randombytes_buf(mono->private_key, 32);
    for (int i = 0; i < RING_SIZE; i++) {
        crypto_core_ed25519_random(mono->public_keys[i]);
    }
}

void monolith_add_vector(Monolith *mono, PhiVector *vec, int node_id, mpf_t *tmp) {
    if (mono->num_vectors >= MAX_NODES) {
        fprintf(stderr, "Maximum nodes exceeded\n");
        return;
    }
    mono->vectors[mono->num_vectors] = *vec;
    mono->slot_assignments[mono->num_vectors] = node_id;
    phi_vector_compute_dn(&mono->vectors[mono->num_vectors], 1.0, 1, tmp);
    mono->num_vectors++;
}

void monolith_collect(Monolith *mono, uint32_t compute_units) {
    if (compute_units >= (1ULL << 32)) {
        fprintf(stderr, "Compute units overflow\n");
        exit(1);
    }
    memset(mono->binary, 0, TOTAL_SLOTS);
    for (int i = 0; i < mono->num_vectors; i++) {
        phi_vector_pack_binary(&mono->vectors[i], mono->binary, mono->vectors[i].instance_id * SLOTS_PER_INSTANCE);
    }
    crypto_hash_sha256(mono->hash, mono->binary, TOTAL_SLOTS);
    mono->compute_units_total += compute_units;
    mono->epoch++;
}

void monolith_submit(Monolith *mono, FILE *output, ZKP *proof, secp256k1_context *ctx) {
    zchgsnark_generate_proof(mono->binary, 0, 1, mono->compute_units_total, mono->private_key, mono->public_keys, proof, ctx);
    fprintf(output, "Epoch %lu: Binary: [", mono->epoch);
    for (int i = 0; i < TOTAL_SLOTS; i++) {
        fprintf(output, "%d%s", mono->binary[i], i < TOTAL_SLOTS-1 ? "," : "");
    }
    fprintf(output, "]\nHash: 0x");
    for (int i = 0; i < 32; i++) {
        fprintf(output, "%02x", mono->hash[i]);
    }
    fprintf(output, "\nZKP: a=[%lu,%lu], b=[[%lu,%lu],[%lu,%lu]], c=[%lu,%lu], input=[%lu,%lu,%lu,%lu], commitment=[%lu,%lu]\n",
            proof->a[0].p.x.n[0], proof->a[0].p.y.n[0],
            proof->b[0].p.x.n[0], proof->b[0].p.x.n[1],
            proof->b[0].p.y.n[0], proof->b[0].p.y.n[1],
            proof->c[0].p.x.n[0], proof->c[0].p.y.n[0],
            proof->input[0], proof->input[1], proof->input[2], proof->input[3],
            proof->commitment[0], proof->commitment[1]);
}

void monolith_free(Monolith *mono) {
    free(mono->vectors);
    free(mono->slot_assignments);
}

int main(int argc, char *argv[]) {
    srand(time(NULL));
    if (sodium_init() < 0) {
        fprintf(stderr, "libsodium initialization failed\n");
        return 1;
    }
    secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY);
    if (!ctx) {
        fprintf(stderr, "secp256k1 context creation failed\n");
        return 1;
    }
    int bits_mant[SLOTS_PER_INSTANCE] = {32, 36, 40, 44};
    int bits_exp[SLOTS_PER_INSTANCE] = {16, 18, 20, 22};
    
    Monolith mono;
    monolith_init(&mono, MAX_NODES);
    
    mpf_t tmp[4];
    for (int i = 0; i < 4; i++) {
        mpf_init2(tmp[i], 256);
    }
    
    for (int i = 0; i < NUM_INSTANCES; i++) {
        PhiVector vec = phi_vector_init_dynamic(i, bits_mant, bits_exp);
        monolith_add_vector(&mono, &vec, i, tmp);
    }
    
    ZKP proof;
    FILE *output = fopen("submission.txt", "w");
    if (!output) {
        fprintf(stderr, "Failed to open output file\n");
        monolith_free(&mono);
        for (int i = 0; i < 4; i++) mpf_clear(tmp[i]);
        secp256k1_context_destroy(ctx);
        return 1;
    }
    monolith_collect(&mono, 1000);
    monolith_submit(&mono, output, &proof, ctx);
    fclose(output);
    
    for (int i = 0; i < 4; i++) {
        mpf_clear(tmp[i]);
    }
    monolith_free(&mono);
    secp256k1_context_destroy(ctx);
    return 0;
}