// stdlib/crypto/dna.sy — DNA encoding for Cardea
//
// Maps bytes to nucleotide sequences and back. Uses base-3 encoding
// (A/C/G) with T reserved for error markers — ~1.6 bits per nucleotide.
// Supports point mutation driven by entropy and Hamming-distance detection.
//
// Aislinn McCubbins, April 2026

// Get a nucleotide character from a ternary digit
// 0 -> A, 1 -> C, 2 -> G, (3 -> T reserved for errors)
fn ternary_to_nucleotide(d: Int) -> String {
    if d == 0 { return "A" }
    if d == 1 { return "C" }
    if d == 2 { return "G" }
    return "T"
}

fn nucleotide_to_ternary(n: String) -> Int {
    if str_eq(n, "A") { return 0 }
    if str_eq(n, "C") { return 1 }
    if str_eq(n, "G") { return 2 }
    return 3
}

// Encode a byte as 5 nucleotides (base-3, values 0..242)
// Each byte up to 242 fits in 5 base-3 digits (3^5 = 243)
fn byte_to_nucleotides(b: Int) -> Array {
    let mut result = array_new()
    let mut remaining = b
    let mut i = 0
    while i < 5 {
        let digit = remaining - ((remaining / 3) * 3)
        result = array_push(result, ternary_to_nucleotide(digit))
        remaining = remaining / 3
        i = i + 1
    }
    return result
}

// Generate an initial DNA sequence of `length` nucleotides from a seed byte tensor
fn seed_to_dna(seed: Tensor, length: Int) -> Array {
    let mut dna = array_new()
    let seed_len = tensor_len(seed)
    let mut si = 0
    while array_len(dna) < length {
        let byte_val = t_index_get(seed, si % seed_len)
        // Clamp to 0..242 for clean base-3
        let mut byte_int = byte_val
        if byte_int > 242.0 { byte_int = byte_int - 13.0 }
        let nucs = byte_to_nucleotides(byte_int)
        let mut ni = 0
        while ni < array_len(nucs) {
            if array_len(dna) < length {
                dna = array_push(dna, array_get(nucs, ni))
            }
            ni = ni + 1
        }
        si = si + 1
    }
    return dna
}

// Serialize a DNA array back to a byte tensor (5 nucleotides -> 1 byte)
fn dna_to_bytes(dna: Array) -> Tensor {
    let n = array_len(dna)
    let num_bytes = n / 5
    let mut bytes = array_new()
    let mut b = 0
    while b < num_bytes {
        let mut val = 0
        let mut place = 1
        let mut i = 0
        while i < 5 {
            let nuc = array_get(dna, b * 5 + i)
            let t = nucleotide_to_ternary(nuc)
            val = val + t * place
            place = place * 3
            i = i + 1
        }
        bytes = array_push(bytes, val + 0.0)
        b = b + 1
    }
    // Pack into a tensor
    let mut result = t_zeros(num_bytes)
    let mut j = 0
    while j < num_bytes {
        result = t_index_set(result, j, array_get(bytes, j))
        j = j + 1
    }
    return result
}

// Rotate a nucleotide by offset (wraps A->C->G->A)
fn rotate_nucleotide(nuc: String, offset: Int) -> String {
    let t = nucleotide_to_ternary(nuc)
    let new_t = (t + offset) - (((t + offset) / 3) * 3)
    return ternary_to_nucleotide(new_t)
}

// Point-mutate a DNA sequence using entropy-driven randomness.
// For each position, if the entropy byte at that position is below
// `mutation_rate * 256`, flip the nucleotide by a rotation derived
// from the next entropy byte. This is deterministic given the same
// entropy input, which is critical for reproducible re-derivation.
fn dna_point_mutate(dna: Array, entropy: Tensor, mutation_rate: Float) -> Array {
    let mut result = dna
    let n = array_len(dna)
    let entropy_len = tensor_len(entropy)
    let threshold = mutation_rate * 256.0
    let mut i = 0
    while i < n {
        let e1 = t_index_get(entropy, i % entropy_len)
        if e1 < threshold {
            let e2 = t_index_get(entropy, (i + 1) % entropy_len)
            let mut rot = 1
            if e2 > 85.0 { rot = 2 }
            let old_nuc = array_get(result, i)
            let new_nuc = rotate_nucleotide(old_nuc, rot)
            result = array_set(result, i, new_nuc)
        }
        i = i + 1
    }
    return result
}

// Hamming distance between two DNA sequences
fn dna_hamming(a: Array, b: Array) -> Int {
    let n = array_len(a)
    let m = array_len(b)
    let mut len = n
    if m < len { len = m }
    let mut diff = 0
    let mut i = 0
    while i < len {
        if str_eq(array_get(a, i), array_get(b, i)) == false {
            diff = diff + 1
        }
        i = i + 1
    }
    return diff
}

// Convert a DNA array into a display string for logging
fn dna_display(dna: Array) -> String {
    let mut s = ""
    let mut i = 0
    while i < array_len(dna) {
        s = s + array_get(dna, i)
        i = i + 1
    }
    return s
}
