use std::io::{self, Write};

use crate::Annotation;
use crate::annotations::BasicAnnotation;

/// Common interface for all 2-bit packed DNA sequences in the pipeline.
///
/// Required methods: `Canonical`, `seql`, `nucleotide`, `seq_hash`, `canonical`.
/// All other methods have default implementations derived from those five.
pub trait Sequence {
    /// The canonical form of this sequence type.
    ///
    /// For types always stored canonical (`SuperKmer`, `CanonicalKmerOf`), set `Canonical = Self`.
    /// For `KmerOf`, set `Canonical = CanonicalKmerOf`.
    type Canonical: Sequence;

    /// Sequence length in nucleotides.
    fn seql(&self) -> usize;

    /// Extract nucleotide `i` (0-based from 5′ end) as a 2-bit value (A=0, C=1, G=2, T=3).
    fn nucleotide(&self, i: usize) -> u8;

    /// Hash of the sequence, used for partitioning and routing.
    fn seq_hash(&self) -> u64;

    /// Return the canonical form.
    ///
    /// For `Copy` types this is free; for heap-backed types it clones (output/debug paths only).
    fn canonical(&self) -> Self::Canonical;

    /// Return an annotation describing this sequence's metadata.
    ///
    /// Default: `BasicAnnotation { seq_length }`. Override for richer metadata.
    fn annotation(&self) -> impl Annotation {
        BasicAnnotation { seq_length: self.seql() }
    }

    /// Decode into ASCII nucleotides, writing into `writer`.
    ///
    /// Default: one byte per nucleotide via `nucleotide()`.
    /// Types with packed byte access should override with the faster DEC4 path.
    fn write_ascii<W: Write>(&self, w: &mut W) -> io::Result<()> {
        for i in 0..self.seql() {
            w.write_all(&[b"ACGT"[self.nucleotide(i) as usize]])?;
        }
        Ok(())
    }

    /// Decode into a fresh ASCII `Vec<u8>`.
    fn to_ascii(&self) -> Vec<u8> {
        let mut buf = Vec::with_capacity(self.seql());
        self.write_ascii(&mut buf).unwrap();
        buf
    }

    /// Partition index derived from `seq_hash`.
    ///
    /// * `part_bits` — number of low bits to use (partition count = `1 << part_bits`).
    fn partition(&self, part_bits: usize) -> usize {
        (mix64(self.seq_hash()) & ((1 << part_bits) - 1)) as usize
    }
}

#[inline]
pub(crate) fn mix64(x: u64) -> u64 {
    let x = x ^ (x >> 30);
    let x = x.wrapping_mul(0xbf58476d1ce4e5b9);
    let x = x ^ (x >> 27);
    let x = x.wrapping_mul(0x94d049bb133111eb);
    x ^ (x >> 31)
}
