// This file is part of ICU4X. For terms of use, please see the file
// called LICENSE at the top level of the ICU4X source tree
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).

use icu_casemap::CaseMapper;
use icu_casemap::greek_to_me::{
    self, GreekDiacritics, GreekPrecomposedLetterData, GreekVowel, PackedGreekPrecomposedLetterData,
};
use icu_normalizer::DecomposingNormalizerBorrowed;
use icu_properties::{
    CodePointMapData,
    props::{GeneralCategory, GeneralCategoryGroup, Script},
};
use std::collections::BTreeMap;
use std::fmt::Write;

include!("../src/greek_to_me/data.rs");

fn main() {
    fn greek_precomposed_letter_data(ch: char) -> u8 {
        let decomposer = DecomposingNormalizerBorrowed::new_nfd();
        let sc = CodePointMapData::<Script>::new();
        let gc = CodePointMapData::<GeneralCategory>::new();
        let cm = CaseMapper::new();

        if sc.get(ch) != Script::Greek || !GeneralCategoryGroup::Letter.contains(gc.get(ch)) {
            return 0;
        }

        let mut nfd = decomposer.normalize_iter([ch].into_iter());

        let letter = nfd.next().unwrap();
        assert!(GeneralCategoryGroup::Letter.contains(gc.get(letter)));
        let letter = cm.simple_uppercase(letter);

        let data = if let Ok(vowel) = GreekVowel::try_from(letter) {
            let mut diacritics = GreekDiacritics::default();
            for diacritic in nfd {
                match diacritic {
                    greek_to_me::diacritics!(ACCENTS) => diacritics.accented = true,
                    greek_to_me::diacritics!(DIALYTIKA) => diacritics.dialytika = true,
                    greek_to_me::diacritics!(YPOGEGRAMMENI) => diacritics.ypogegrammeni = true,
                    greek_to_me::diacritics!(BREATHING_AND_LENGTH) => {}
                    _ => panic!("Unhandled vowel diacritic {diacritic:?}"),
                }
            }
            GreekPrecomposedLetterData::Vowel(vowel, diacritics)
        } else {
            let is_rho = letter == greek_to_me::CAPITAL_RHO;
            for diacritic in nfd {
                match diacritic {
                    // Rho takes breathing marks but other consonants should not
                    greek_to_me::diacritics!(BREATHING_AND_LENGTH) if is_rho => {}
                    _ => panic!("Unhandled consonant diacritic {diacritic:?}"),
                }
            }
            GreekPrecomposedLetterData::Consonant(is_rho)
        };

        let packed = PackedGreekPrecomposedLetterData::from(data);
        assert_eq!(
            Ok(data),
            packed.try_into(),
            "packed data for {ch} ({packed:?}) must roundtrip!"
        );
        packed.0
    }

    // Greek_And_Coptic block (U+0370..U+03FF)
    let vec_370 = ('\u{0370}'..='\u{03FF}')
        .map(greek_precomposed_letter_data)
        .collect::<Vec<_>>();

    // Greek_Extended block (U+1F00..U+1FFF)
    let vec_1f00 = ('\u{1F00}'..='\u{1FFF}')
        .map(greek_precomposed_letter_data)
        .collect::<Vec<_>>();

    // All characters that canonically decompose to a single Greek letter but are not in the Greek blocks.
    let mut extras = BTreeMap::new();
    for ch in (0..=char::MAX as u32).filter_map(char::from_u32) {
        if matches!(ch, '\u{0370}'..='\u{03FF}' | '\u{1F00}'..='\u{1FFF}') {
            continue;
        }

        let mut nfd = DecomposingNormalizerBorrowed::new_nfd().normalize_iter([ch].into_iter());

        if let (Some(c @ ('\u{0370}'..='\u{03FF}' | '\u{1F00}'..='\u{1FFF}')), None) =
            (nfd.next(), nfd.next())
        {
            let c_i = c as usize;

            extras.insert(
                ch,
                if c_i <= 0x3FF {
                    vec_370[c_i - 0x370]
                } else {
                    vec_1f00[c_i - 0x1f00]
                },
            );
        }
    }

    let vec_370 = &vec_370[..vec_370.iter().rposition(|&x| x != 0).unwrap() + 1];
    let vec_1f00 = &vec_1f00[..vec_1f00.iter().rposition(|&x| x != 0).unwrap() + 1];

    let matches = DATA_370 == vec_370
        && DATA_1F00 == vec_1f00
        && (0..=char::MAX as u32)
            .filter_map(char::from_u32)
            .all(|ch| match_extras(ch) == extras.get(&ch).copied());

    if !matches {
        let mut others = String::new();
        for (&ch, &data) in &extras {
            writeln!(&mut others, "        {ch:?} => {data},").unwrap();
        }
        println!(
            r#"Please copy the following file to src/greek_to_me/data.rs:
========================================================
// This file is part of ICU4X. For terms of use, please see the file
// called LICENSE at the top level of the ICU4X source tree
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).

// This file is generated by running `cargo test --test gen_greek_to_me --features compiled_data,datagen
//
// Do not edit manually

// All u8s in this file are PackedGreekPrecomposedLetterDatas, see parent module

/// Data for characters in U+370-U+3FF
pub(crate) const DATA_370: [u8; 0x{:x}] = {vec_370:?};
/// Data for characters in U+1F00-U+1FFF
pub(crate) const DATA_1F00: [u8; 0x{:x}] = {vec_1f00:?};

/// Characters like the ohm sign that do not belong in the two blocks above
pub(crate) fn match_extras(ch: char) -> Option<u8> {{
    Some(match ch {{
{others}
        _ => return None
    }})
}}
========================================================"#,
            vec_370.len(),
            vec_1f00.len(),
        );

        panic!(
            "Found mismatch between generated Greek specialcasing data and checked-in data. Please check in the updated file shown above."
        );
    }
}
