use crate::faker::barcode::raw::*;
use crate::faker::boolean::raw::Boolean;
use crate::faker::numerify_sym;
use crate::locales::Data;
use crate::{Dummy, Fake};
use rand::seq::IndexedRandom;
use rand::RngExt;

const ISBN_MAX_LENGTH: usize = 13;

struct IsbnProperties {
    ean: &'static str,
    group: &'static str,
    registrant: String,
    publication: String,
}

impl IsbnProperties {
    fn to_string(&self, sum10: bool) -> String {
        let mut ean = self.ean;
        if sum10 {
            ean = "";
        }
        format!(
            "{}{}{}{}",
            ean, self.group, self.registrant, self.publication
        )
    }
}

fn checksum10(properties: &IsbnProperties) -> String {
    let all = properties.to_string(true);
    let chars: Vec<char> = all.chars().collect();

    let mut sum = 0;
    for (i, v) in chars.iter().enumerate() {
        if let Ok(_v) = v.to_string().parse::<i32>() {
            sum += (i + 1) * _v as usize;
        }
    }

    let check_digit = sum % 11;
    if check_digit == 10 {
        return "X".to_string();
    }
    check_digit.to_string()
}

fn checksum13(properties: &IsbnProperties) -> i32 {
    let all = properties.to_string(false);
    let chars: Vec<char> = all.chars().collect();

    let mut sum = 0;
    for (i, v) in chars.iter().enumerate() {
        if let Ok(_v) = v.to_string().parse::<i32>() {
            if i % 2 == 0 {
                sum += _v;
            } else {
                sum += _v * 3;
            }
        }
    }

    let remainder = sum % 10;
    let mut check_digit = 10 - remainder;
    if check_digit == 10 {
        check_digit = 0;
    }
    check_digit
}

fn get_properties<L: Data, R: RngExt + ?Sized>(_c: L, rng: &mut R) -> IsbnProperties {
    let ean = L::ISBN_EAN;
    let rules = *L::isbn_rules();
    let keys: Vec<&'static str> = rules.keys().cloned().collect();
    let group = keys.choose(rng).unwrap();

    let reg_pub_len = ISBN_MAX_LENGTH - ean.chars().count() - group.chars().count() - 1;
    let reg_pub = numerify_sym(&"#".repeat(reg_pub_len), rng);

    let mut reg_len = 0;
    let suffix_reg_pub = &reg_pub[..reg_pub_len - 1];

    for r in &rules[group] {
        if r.min <= suffix_reg_pub && suffix_reg_pub <= r.max {
            reg_len = r.registrant_len;
            break;
        }
    }
    IsbnProperties {
        ean,
        group,
        registrant: reg_pub[..reg_len as usize].to_string(),
        publication: reg_pub[reg_len as usize..reg_pub_len].to_string(),
    }
}

impl<L: Data + Copy> Dummy<Isbn10<L>> for String {
    fn dummy_with_rng<R: RngExt + ?Sized>(c: &Isbn10<L>, rng: &mut R) -> Self {
        let properties = get_properties(c.0, rng);
        format!(
            "{}-{}-{}-{}",
            properties.group,
            properties.registrant,
            properties.publication,
            checksum10(&properties)
        )
    }
}

impl<L: Data + Copy> Dummy<Isbn13<L>> for String {
    fn dummy_with_rng<R: RngExt + ?Sized>(c: &Isbn13<L>, rng: &mut R) -> Self {
        let properties = get_properties(c.0, rng);
        format!(
            "{}-{}-{}-{}-{}",
            properties.ean,
            properties.group,
            properties.registrant,
            properties.publication,
            checksum13(&properties)
        )
    }
}

impl<L: Data + Copy> Dummy<Isbn<L>> for String {
    fn dummy_with_rng<R: RngExt + ?Sized>(c: &Isbn<L>, rng: &mut R) -> Self {
        if Boolean(c.0, 50).fake_with_rng(rng) {
            return Isbn13(c.0).fake_with_rng(rng);
        }
        Isbn10(c.0).fake_with_rng(rng)
    }
}
