use std::path::{Path, PathBuf};

use clap::{Parser, ValueEnum};

fn read_input(filename: impl AsRef<Path>) -> Vec<Vec<u8>> {
    let text = std::fs::read_to_string(filename).unwrap();

    let mut r = Vec::new();

    for line in text.lines().skip(1) {
        r.push(line.as_bytes().to_vec());
    }

    r
}

fn calculate_hamming_distance(a: &[u8], b: &[u8]) -> usize {
    a.iter().zip(b.iter()).filter(|(a, b)| a != b).count()
}

fn simple<'a>(
    strings: &[Vec<u8>],
    hamming_distances: &mut [u8],
    solution: &'a mut [u8],
    d: u8,
    depth: u8,
) -> Option<&'a [u8]> {
    let (s, &i) = strings
        .iter()
        .zip(hamming_distances.iter())
        .max_by_key(|(_, &i)| i)
        .unwrap();

    // assert_eq!(i, calculate_hamming_distance(s, solution) as u8);

    if i > d + depth {
        return None;
    } else if i <= d {
        return Some(solution);
    }

    if depth == 0 {
        return None;
    }

    for index in 0..s.len() {
        if s[index] != solution[index] {
            let temp = solution[index];
            solution[index] = s[index];
            for j in 0..hamming_distances.len() {
                if strings[j][index] == temp {
                    hamming_distances[j] += 1;
                }
                if strings[j][index] == s[index] {
                    hamming_distances[j] -= 1;
                }
            }
            if simple(strings, hamming_distances, solution, d, depth - 1).is_some() {
                return Some(solution);
            }
            for j in 0..hamming_distances.len() {
                if strings[j][index] == temp {
                    hamming_distances[j] -= 1;
                }
                if strings[j][index] == s[index] {
                    hamming_distances[j] += 1;
                }
            }
            solution[index] = temp;
        }
    }

    None
}

fn closest_string(strings: &[Vec<u8>], d: u8, method: Method) -> Option<Vec<u8>> {
    match method {
        Method::Simple => {
            let mut s = strings[0].clone();
            let mut hamming_distances = strings
                .iter()
                .map(|l| calculate_hamming_distance(&s, l) as u8)
                .collect::<Vec<_>>();
            if simple(strings, &mut hamming_distances, &mut s, d, d).is_some() {
                Some(s)
            } else {
                None
            }
        }
    }
}

#[derive(ValueEnum, Debug, Clone, Copy)]
enum Method {
    Simple,
}

#[derive(Debug, Parser)]
struct Args {
    filename: PathBuf,
    d: Option<u8>,
    #[clap(default_value = "simple")]
    method: Method,
    #[clap(short, long)]
    reverse: bool,
}

fn main() {
    let total_start = std::time::Instant::now();
    let args = Args::parse();

    let strings = read_input(args.filename);

    let result = if let Some(d) = args.d {
        let start = std::time::Instant::now();
        let r = closest_string(&strings, d, args.method);
        eprintln!("{}: {:?}", d, start.elapsed());
        r
    } else if args.reverse {
        let mut d = strings.len() as u8;
        let mut last_solution = None;
        loop {
            let start = std::time::Instant::now();
            let r = closest_string(&strings, d, args.method);
            eprintln!("{}: {:?}", d, start.elapsed());
            if let Some(r) = r {
                d = r.len() as u8 - 1;
                last_solution = Some(r);
            } else {
                break last_solution;
            };
        }
    } else {
        let mut d = 0;
        loop {
            let start = std::time::Instant::now();
            let r = closest_string(&strings, d, args.method);
            eprintln!("{}: {:?}", d, start.elapsed());
            if let Some(r) = r {
                break Some(r);
            }
            d += 1;
        }
    };

    if let Some(solution) = result {
        println!("{}", String::from_utf8(solution).unwrap());
    }
    eprintln!("Total time: {:?}", total_start.elapsed());
}
