mod diacritic;
mod phrase;
mod place;
mod seg;
mod syll;
mod feature;

pub use feature::*;
pub(crate) use diacritic::*;
pub use phrase::*;
pub use place::*;
pub use seg::*;
pub use syll::*;

use std :: {
    cell::RefCell, 
    collections::{HashMap, VecDeque}, 
    fmt
};

use crate :: {
    error :: { ASCAError, AliasRuntimeError, RuleRuntimeError, WordSyntaxError }, 
    rule  :: { Alpha, BinMod, SpecMod, ModKind, Modifiers, Position, SupraSegs }, 
    CARDINALS_MAP, CARDINALS_TRIE, DIACRITS
};

use crate  :: alias :: {
    parser :: { AliasParseElement, SegType }, 
    AliasPosition, Transformation
};

#[derive(Clone, Copy, PartialEq, Eq)]
pub struct SegPos {
    pub word_index: usize,
    pub syll_index: usize,
    pub seg_index: usize
}

impl PartialOrd for SegPos {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for SegPos {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        match self.word_index.cmp(&other.word_index) {
            core::cmp::Ordering::Equal => {}
            ord => return ord,
        }
        match self.syll_index.cmp(&other.syll_index) {
            core::cmp::Ordering::Equal => {}
            ord => return ord,
        }
        self.seg_index.cmp(&other.seg_index)
    }
}

impl fmt::Debug for SegPos {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}:{}:{}", self.word_index, self.syll_index, self.seg_index)
    }
}

impl SegPos {
    pub fn new(word_index: usize, syll_index: usize, seg_index: usize) -> Self {
        Self { word_index, syll_index, seg_index }
    }

    /// Must not use reversed phrase
    pub(crate) fn reversed(&self, phrase: &Phrase) -> Self {
        if phrase.in_bounds(*self) {
            SegPos::new(
                phrase.len() - 1 - self.word_index,
                phrase[self.word_index].syllables.len() - 1 - self.syll_index, 
                phrase[self.word_index].syllables[self.syll_index].segments.len() - 1 - self.seg_index
            )
        // At end word boundary
        } else {
            let syll_index = if self.syll_index < phrase[self.word_index].syllables.len() {
                phrase[self.word_index].syllables.len() - 1 - self.syll_index
            } else {
                0
            };

            let bounded_pos = phrase[self.word_index].syllables.len() - 1 - syll_index;

            let seg_index = if self.seg_index < phrase[self.word_index].syllables[bounded_pos].segments.len() {
                phrase[self.word_index].syllables[bounded_pos].segments.len() - 1 - self.seg_index
            } else {
                0
            };

            SegPos::new(
                phrase.len() - 1 - self.word_index,
                syll_index,
                seg_index
            )
        }
    }

    #[allow(unused)]
    pub(crate) fn ins_inc(&mut self, phrase: &Phrase) {
        if self.syll_index >= phrase[self.word_index].syllables.len() { return }
        
        self.seg_index += 1;
        if self.seg_index > phrase[self.word_index].syllables[self.syll_index].segments.len() {
            self.seg_index = 0;
            self.syll_index += 1;
        }
    }

    pub(crate) fn increment(&mut self, phrase: &Phrase) {
        // NOTE: Does not guarantee that the resulting position is within the bounds of the word
        if self.syll_index >= phrase[self.word_index].syllables.len() { return }

        self.seg_index += 1;
        if self.seg_index >= phrase[self.word_index].syllables[self.syll_index].segments.len() {
            self.seg_index = 0;
            self.syll_index += 1;
        }
    }

    pub(crate) fn word_increment(&mut self, phrase: &Phrase) {
        if self.word_index >= phrase.len() - 1 { return }
        self.word_index += 1;
        self.syll_index = 0;
        self.seg_index = 0;

        // Incase of "empty" words
        while !phrase[self.word_index].in_bounds(*self) {
            self.word_increment(phrase);
        }
    }

    pub(crate) fn word_decrement(&mut self, phrase: &Phrase) {
        if self.word_index == 0 { return }

        if self.word_index > phrase.len() {
            self.word_index = phrase.len() - 1;
        }

        self.word_index -= 1;
        self.syll_index = phrase[self.word_index].syllables.len() - 1;
        self.seg_index = phrase[self.word_index].syllables[self.syll_index].segments.len() - 1;

        // Incase of "empty" words
        while !phrase[self.word_index].in_bounds(*self) {
            self.word_decrement(phrase);
        }
    }

    pub(crate) fn decrement(&mut self, phrase: &Phrase) {

        if phrase.len() <= self.word_index { self.word_index = phrase.len() - 1 }

        if self.syll_index > phrase[self.word_index].syllables.len() {
            self.syll_index = phrase[self.word_index].syllables.len() - 1;
            self.seg_index = phrase[self.word_index].syllables[self.syll_index].segments.len() - 1;
        } else if self.seg_index > 0 {
            self.seg_index -= 1;
        } else if self.syll_index > 0 {
            self.syll_index -= 1;
            self.seg_index = phrase[self.word_index].syllables[self.syll_index].segments.len() - 1;
        }
        // if 0:0, do nothing
    }

    #[allow(unused)]
    pub(crate) fn at_phrase_start(&self) -> bool {
        self.word_index == 0 && self.syll_index == 0 && self.seg_index == 0
    }

    pub(crate) fn at_phrase_end(&self, phrase: &Phrase) -> bool {
        self.word_index == phrase.len() - 1 && (self.at_word_end(phrase) || !phrase.in_bounds(*self))
    }

    pub(crate) fn at_word_start(&self) -> bool {
        self.syll_index == 0 && self.seg_index == 0
    }

    pub(crate) fn at_word_end(&self, phrase: &Phrase) -> bool {
        (self.syll_index == phrase[self.word_index].syllables.len() && self.seg_index == 0) 
        || (self.syll_index == phrase[self.word_index].syllables.len() - 1 
            && self.seg_index >= phrase[self.word_index].syllables[self.syll_index].segments.len() - 1)
    }

    pub(crate) fn at_syll_start(&self) -> bool {
        // NOTE: does not account for out_of_bounds
        self.seg_index == 0
    }
    
    pub(crate) fn at_syll_end(&self, phrase: &Phrase) -> bool {
        // NOTE: returns false if out_of_bounds
        self.syll_index < phrase[self.word_index].syllables.len() && self.seg_index >= phrase[self.word_index].syllables[self.syll_index].segments.len() - 1
    }
}

#[derive(Clone)]
pub struct Word {
    pub syllables: VecDeque<Syllable>,
}

impl PartialEq for Word {
    fn eq(&self, other: &Self) -> bool {
        self.syllables == other.syllables
    }
}

impl fmt::Debug for Word {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        for (i, syll) in self.syllables.iter().enumerate() {
            for (j, seg) in syll.segments.iter().enumerate() {
                writeln!(f, "{i}:{j} | {seg:?}")?;
            }
        }
        Ok(())
    }
}

impl Word { 
    pub fn new<S: AsRef<str>>(text: S) -> Result<Self, ASCAError> {
        let mut w = Self { syllables: VecDeque::new() };
        w.setup(Self::normalise(text.as_ref()), &[])?;

        Ok(w)
    }

    pub fn with<S: AsRef<str>>(text: S, aliases: &[Transformation]) -> Result<Self, ASCAError> {
        let mut w = Self { syllables: VecDeque::new() };
        w.setup(Self::normalise(text.as_ref()), aliases)?;

        Ok(w)
    }

    // FIXME: In the docs, we say that no normalisation is done before the aliasing stage
    fn normalise<S: AsRef<str>>(input_txt: S) -> String {
        let txt = input_txt.as_ref();
        let mut output = String::with_capacity(txt.len());
        for ch in txt.chars() {
            match ch {
                'ã' => output.push_str("ã"),
                'ẽ' => output.push_str("ẽ"),
                'ĩ' => output.push_str("ĩ"),
                'õ' => output.push_str("õ"),
                'ũ' => output.push_str("ũ"),
                'ỹ' => output.push_str("ỹ"),
                'ɚ' => output.push_str("ə˞"),
                'ɝ' => output.push_str("ɜ˞"),
                'ꭤ' => output.push('ɑ'),
                'ǝ' => output.push('ə'),
                'ℇ' => output.push('ɛ'),
                'ℎ' => output.push('h'),
                'ℏ' => output.push('ħ'),
                _ => output.push(ch)
            }
        }
        output
    }

    fn as_ipa(txt: &[char], index: &mut usize) -> char {
        let ch = txt[*index];
        match ch {
            'S' => 'ʃ',
            'Z' => 'ʒ',
            'C' => 'ɕ',
            'J' => 'ʝ',
            'G' => 'ɢ',
            'N' => 'ɴ',
            'B' => 'ʙ',
            'R' => 'ʀ',
            'X' => 'χ',
            'H' => 'ʜ',
            'A' => 'ɐ',
            'E' => 'ɛ',
            'I' => 'ɪ',
            'O' => 'ɔ',
            'U' => 'ʊ',
            'Y' => 'ʏ',
            'W' => 'ɯ',
            'φ' => 'ɸ',
            'g' => 'ɡ',
            '?' => 'ʔ',
            '!' => 'ǃ',
            '^' => match txt.get(*index+1) {
                Some(asdf) => match asdf {
                    'ǃ' | '!' => { *index += 1; 'ǃ' },
                    
                    'ʘ' | 'ǀ' | 'ǁ' | '‼' | 'ǂ' |
                    'q' | 'ɢ' | 'ɴ' | 'χ' | 'ʁ' => {
                        *index += 1; *asdf
                    },
                    _ => '^'
                },
                None => '^',
            },
            '"' => match txt.get(*index+1) {
                Some(asdf) => match asdf {
                    '\'' | 'ˈ' => { *index += 1; 'ʼ' },
                    'j' => { *index += 1; 'ʲ' },
                    'w' => { *index += 1; 'ʷ' },
                    'g' | 'ɡ' => { *index += 1; 'ˠ' },
                    'h' => { *index += 1; 'ʰ' },
                    'ɦ' | 'H' => { *index += 1; 'ʱ' },
                    's' => { *index += 1; 'ˢ' },
                    'z' => { *index += 1; 'ᶻ' },
                    'l' => { *index += 1; 'ˡ' },
                    'r' => { *index += 1; 'ʵ' },
                    
                    'm' => { *index += 1; 'ᵐ' },
                    'n' => { *index += 1; 'ⁿ' },
                    'P' | 'ɲ' => { *index += 1; 'ᶮ' },
                    'T' | 'ɳ' => { *index += 1; 'ᶯ' },
                    'G' | 'ŋ' => { *index += 1; 'ᵑ' },
                    'N' | 'ɴ' => { *index += 1; 'ᶰ' },
                    
                    'v' | 'ʋ' => { *index += 1; 'ᶹ' },
                    'y' | 'ɥ' => { *index += 1; 'ᶣ' },
                    'X' | 'χ' => { *index += 1; 'ᵡ' },
                    'R' | 'ʁ' => { *index += 1; 'ʶ' },
                    'e' | 'ə' => { *index += 1; 'ᵊ' },
                    '?' | 'ʔ' => { *index += 1; 'ˀ' },

                    _ => '"'
                },
                None => '"',
            }
            other => other
        }
    }

    fn alias_apply_mods(&self, seg: &mut Segment, mods: &Modifiers, err_pos: AliasPosition) -> Result<(), AliasRuntimeError> {
        for (i, m) in mods.nodes.iter().enumerate() {
            let node = NodeKind::from_usize(i);
            if let Some(kind) = m {
                match kind {
                    ModKind::Binary(bm) => match bm {
                        BinMod::Negative => match node {
                            NodeKind::Root      => return Err(AliasRuntimeError::NodeCannotBeNone("Root".to_owned(), err_pos)),
                            NodeKind::Manner    => return Err(AliasRuntimeError::NodeCannotBeNone("Manner".to_owned(), err_pos)),
                            NodeKind::Laryngeal => return Err(AliasRuntimeError::NodeCannotBeNone("Largyneal".to_owned(), err_pos)),
                            NodeKind::Place => *seg.place = None, // e.g. Debuccalization
                            _ => seg.set_node(node, None),
                            
                        },
                        BinMod::Positive => match node {
                            NodeKind::Root      => return Err(AliasRuntimeError::NodeCannotBeSome("Root".to_owned(), err_pos)),
                            NodeKind::Manner    => return Err(AliasRuntimeError::NodeCannotBeSome("Manner".to_owned(), err_pos)),
                            NodeKind::Laryngeal => return Err(AliasRuntimeError::NodeCannotBeSome("Largyneal".to_owned(), err_pos)),
                            NodeKind::Place     => return Err(AliasRuntimeError::NodeCannotBeSome("Place".to_owned(), err_pos)),
                            // preserve node if already positive
                            _ if seg.get_node(node).is_none() => seg.set_node(node, Some(0)),
                            _ => {}
                        },
                    },
                    ModKind::Alpha(_) => unreachable!(),
                }
            }
        }

        for (i, m) in mods.feats.iter().enumerate() {
            let (n, f) = FeatKind::from_usize(i).as_node_mask();
            if let Some(kind) = m {
                match kind {
                    ModKind::Binary(bm) => match bm {
                        BinMod::Negative => seg.set_feat(n, f, false),
                        BinMod::Positive => seg.set_feat(n, f, true),
                    },
                    ModKind::Alpha(_) => unreachable!(),
                }
            }
        }

        Ok(())
    }

    fn alias_apply_length(&self, mods: &Modifiers, err_pos: AliasPosition) -> Result<Option<usize>, AliasRuntimeError> {
        let Some(len_mods) = mods.suprs.length else { return Ok(None) };

        match len_mods {
            SpecMod::First(long) => match long {
                ModKind::Binary(bm) => match bm {
                    BinMod::Positive => Ok(Some(2)),
                    BinMod::Negative => Ok(Some(1)),
                },
                ModKind::Alpha(_) => unreachable!(),
            },
            SpecMod::Second(over) => match over {
                ModKind::Binary(bm) => match bm {
                    BinMod::Positive => Ok(Some(3)),
                    BinMod::Negative => Ok(Some(1)),
                },
                ModKind::Alpha(_) => unreachable!(),
            },
            SpecMod::Both(long, over) => match (long, over) {
                (ModKind::Binary(bl), ModKind::Binary(bo)) => match (bl, bo) {
                    (BinMod::Positive, BinMod::Positive) => Ok(Some(3)),
                    (BinMod::Positive, BinMod::Negative) => Ok(Some(2)),
                    (BinMod::Negative, BinMod::Negative) => Ok(Some(1)),
                    (BinMod::Negative, BinMod::Positive) => Err(AliasRuntimeError::OverlongPosLongNeg(err_pos)),
                },
                _ => unreachable!()
            },
            SpecMod::Joined(_) => Err(AliasRuntimeError::GroupedSupras(err_pos)),
        }
    }

    fn alias_apply_stress(&self, sy: &mut Syllable, mods: &Modifiers, err_pos: AliasPosition) -> Result<(), AliasRuntimeError> {
        let Some(str_mods) = mods.suprs.stress else { return Ok(()) };

        match str_mods {
            SpecMod::First(prim) => match prim.as_bin_mod().unwrap() {
                BinMod::Positive => sy.stress = StressKind::Primary,
                BinMod::Negative => sy.stress = StressKind::Unstressed,
            },
            SpecMod::Second(sec) => match sec.as_bin_mod().unwrap() {
                BinMod::Positive => sy.stress = StressKind::Secondary,
                BinMod::Negative => if sy.stress == StressKind::Secondary {
                    sy.stress = StressKind::Unstressed;
                },
            },
            SpecMod::Both(prim, sec) => match (prim.as_bin_mod().unwrap(), sec.as_bin_mod().unwrap()) {
                (BinMod::Positive, BinMod::Positive) => sy.stress = StressKind::Secondary,
                (BinMod::Positive, BinMod::Negative) => sy.stress = StressKind::Primary,
                (BinMod::Negative, BinMod::Negative) => sy.stress = StressKind::Unstressed,
                (BinMod::Negative, BinMod::Positive) => return Err(AliasRuntimeError::SecStrPosStrNeg(err_pos)),
            },
            SpecMod::Joined(_) => return Err(AliasRuntimeError::GroupedSupras(err_pos)),
        }

        Ok(())
    }

    fn fill_segments<S: AsRef<str>>(&mut self, input_txt: S, txt: &mut Vec<char>, i: &mut usize, sy: &mut Syllable, aliases: &[Transformation]) -> Result<bool, ASCAError> {
        for alias in aliases {
            if let AliasParseElement::Replacement(string, plus) = &alias.input.kind {
                let back_pos = *i;
                let mut is_match = true;
                for ch in string.chars() {
                    if *i >= txt.len() || ch != txt[*i] {
                        is_match = false;
                    }
                    *i += 1;
                }
                if is_match {
                    match &alias.output.kind {
                        AliasParseElement::Segments(vec) => for segtype in vec {
                            match segtype {
                                SegType::Ipa(seg, modifiers) => {
                                    let mut seg = *seg;
                                    let mut length = 1;
                                    if let Some(mods) = modifiers {
                                        self.alias_apply_mods(&mut seg, mods, alias.output.position)?;
                                        self.alias_apply_stress(sy, mods, alias.output.position)?;
                                        
                                        length = self.alias_apply_length(mods, alias.output.position)?.unwrap_or(1);

                                        if let Some(tn) = &mods.suprs.tone {
                                            sy.tone = *tn;
                                        }
                                    }

                                    for _ in 0..length {
                                        sy.segments.push_back(seg);
                                    }
                                },
                                SegType::Matrix(mods) => if *plus {

                                    let seg_indices = sy.seg_indices();
                                    let Some(pos) = seg_indices.last() else {
                                        Err(AliasRuntimeError::EmptySyllable(alias.input.position))?
                                    };

                                    self.alias_apply_stress(sy, mods, alias.output.position)?;
                                    if let Some(tn) = &mods.suprs.tone {
                                        sy.tone = *tn;
                                    }
                                    
                                    let cur_length = sy.segments.len() - pos;
                                    let maybe_new_length = self.alias_apply_length(mods, alias.output.position)?;
                                    let seg = {
                                        let seg = sy.segments.get_mut(*pos).unwrap();
                                        self.alias_apply_mods(seg, mods, alias.output.position)?;
                                        *seg
                                    };
                                    if let Some(new_length) = maybe_new_length {
                                        match new_length.cmp(&cur_length) {
                                            std::cmp::Ordering::Equal => {},
                                            std::cmp::Ordering::Greater => for _ in cur_length..new_length {
                                                sy.segments.push_back(seg);
                                            },
                                            std::cmp::Ordering::Less => for _ in new_length..cur_length {
                                                sy.segments.pop_back();
                                            },
                                        }
                                    }

                                } else {
                                    self.alias_apply_stress(sy, mods, alias.output.position)?;
                                    if let Some(tn) = &mods.suprs.tone {
                                        sy.tone = *tn;
                                    }
                                    
                                    if mods.nodes.iter().any(|x| x.is_some()) || mods.feats.iter().any(|x| x.is_some()) {
                                        Err(AliasRuntimeError::IndefiniteFeatures(alias.output.position))?
                                    }

                                    if mods.suprs.length.is_some() {
                                        Err(AliasRuntimeError::LengthNoSegment(alias.output.position))?
                                    }
                                    
                                },
                            }
                            

                        },
                        AliasParseElement::SyllBound => { txt.insert(*i, '.'); },
                        AliasParseElement::Empty => {},
                        AliasParseElement::Replacement(..) => unreachable!()
                    }
                    return Ok(true);
                }
                *i = back_pos;
            }
        }

        // GET SEG
        let mut buffer = Self::as_ipa(txt, i).to_string();
        
        if CARDINALS_TRIE.contains_prefix(buffer.as_str()) {
            *i += 1;
            while *i < txt.len() {
                let mut tmp = buffer.clone(); tmp.push(Self::as_ipa(txt, i));
                if CARDINALS_TRIE.contains_prefix(tmp.as_str()) {
                    buffer.push(Self::as_ipa(txt, i));
                    *i += 1;
                    continue;
                }
                if txt[*i] == '^' {
                    tmp.pop(); tmp.push('\u{0361}');
                    if CARDINALS_TRIE.contains_prefix(tmp.as_str()) {
                        buffer.push('\u{0361}');
                        *i += 1;
                        continue;
                    }
                    tmp.pop(); tmp.push('\u{035C}');
                    if CARDINALS_TRIE.contains_prefix(tmp.as_str()) {
                        buffer.push('\u{035C}');
                        *i += 1;
                        continue;
                    }
                }
                if txt[*i-1] == '"' { *i -= 1; }
                break;
            }
            let maybe_seg = CARDINALS_MAP.get(&buffer);
            if let Some(seg_stuff) = maybe_seg {
                sy.segments.push_back(*seg_stuff);
                Ok(false)
            } else {
                // Is this needed?
                *i -= 1;

                let last_char = buffer.pop();
                let last_buffer = buffer;

                let maybe_seg = if last_buffer.is_empty() {
                    CARDINALS_MAP.get(&last_char.expect("buffer should not be empty").to_string())
                } else {
                    CARDINALS_MAP.get(&last_buffer)
                };

                if let Some(seg) = maybe_seg {
                    sy.segments.push_back(*seg) ;
                    Ok(false)
                } else {
                    Err(WordSyntaxError::UnknownChar(input_txt.as_ref().to_owned(), *i).into())
                }
            }
        } else {
            let c = buffer.chars().next().unwrap();
            for d in DIACRITS.iter() {
                if c == d.diacrit {
                    match sy.segments.back_mut() {
                        Some(s) => match s.check_and_apply_diacritic(d) {
                            Ok(_) => {
                                *i += 1;
                                return Ok(true)
                            },
                            Err((mod_index, is_node)) => {
                                if !is_node {
                                    let ft = FeatKind::from_usize(mod_index);
                                    let pos = match d.prereqs.feats[mod_index].unwrap() {
                                        ModKind::Binary(bin_mod) => bin_mod == BinMod::Positive,
                                        _ => unreachable!(),
                                    };
                                    return Err(WordSyntaxError::DiacriticDoesNotMeetPreReqsFeat(input_txt.as_ref().to_owned(), *i, ft.to_string(), pos).into())
                                } else {
                                    let nt = NodeKind::from_usize(mod_index);
                                    let pos = match d.prereqs.nodes[mod_index].unwrap() {
                                        ModKind::Binary(bin_mod) => bin_mod == BinMod::Positive,
                                        _ => unreachable!(),
                                    };
                                    return Err(WordSyntaxError::DiacriticDoesNotMeetPreReqsNode(input_txt.as_ref().to_owned(), *i, nt.to_string(), pos).into())
                                };
                            },
                        },
                        None => return Err(WordSyntaxError::DiacriticBeforeSegment(input_txt.as_ref().to_owned(), *i).into())
                    }
                }
            }
            Err(WordSyntaxError::UnknownChar(input_txt.as_ref().to_owned(), *i).into())
        }
    }

    fn setup<S: AsRef<str>>(&mut self, input_txt: S, aliases: &[Transformation]) -> Result<(), ASCAError> {
        let mut i = 0;
        let mut txt: Vec<char> = input_txt.as_ref().chars().collect();

        let mut sy = Syllable::new();

        while i < txt.len() {
            match txt[i] {
                'ˈ' | '\'' => { // Primary Stress
                    if !sy.segments.is_empty() { self.syllables.push_back(sy); }
                    sy = Syllable { segments: VecDeque::new(), stress: StressKind::Primary, tone: 0 };
                }
                'ˌ' | ','  => { // Secondary Stress
                    if !sy.segments.is_empty() { self.syllables.push_back(sy); }
                    sy = Syllable { segments: VecDeque::new(), stress: StressKind::Secondary, tone: 0 };
                }
                ';' => { // Length and Close
                    if sy.segments.is_empty() { return Err(WordSyntaxError::NoSegmentBeforeColon(input_txt.as_ref().to_owned(), i).into()) }
                    sy.segments.push_back(*sy.segments.back().unwrap()); 
                    self.syllables.push_back(sy);
                    sy = Syllable::new();
                }
                'ː' | ':' => { // Length
                    if sy.segments.is_empty() { return Err(WordSyntaxError::NoSegmentBeforeColon(input_txt.as_ref().to_owned(), i).into()) }
                    sy.segments.push_back(*sy.segments.back().unwrap());
                }
                '.' => { // Close
                    if !sy.segments.is_empty() { self.syllables.push_back(sy); }
                    sy = Syllable::new();
                }

                ch if ch.is_ascii_digit() => { // Tone
                    if sy.segments.is_empty() { continue; }

                    let mut tone_buffer = String::new();
                    while i < txt.len() && txt[i].is_ascii_digit() {
                        tone_buffer.push(txt[i]); i+=1;
                    }
                    i-=1;
                    
                    tone_buffer = tone_buffer.replace('0', "");
                    if tone_buffer.chars().count() > 4 {
                        Err(WordSyntaxError::ToneTooBig(input_txt.as_ref().to_owned(), i-tone_buffer.chars().count()))?
                    }
                    sy.tone = tone_buffer.parse().unwrap_or(0);

                    self.syllables.push_back(sy.clone());
                    sy = Syllable::new();
                }
                _ => { // Get Segment
                    let _ = self.fill_segments(&input_txt, &mut txt, &mut i, &mut sy, aliases)?;
                    i-=1;
                }
            }
            i+=1;
        }

        if !sy.segments.is_empty() {
            self.syllables.push_back(sy);
            return Ok(())
        }

        // Check for dangling '
        if sy.stress == StressKind::Primary {
            // Check if previous syllable had a segment which would indicate that ' might have been meant as ejective
            if  let Some(syll) = self.syllables.back() && !syll.segments.is_empty() { 
                return Err(WordSyntaxError::CouldNotParseEjective(input_txt.as_ref().to_owned()).into())
            }
        }
        
        // if tone or stress is set
        if sy.tone != 0 || sy.stress != StressKind::Unstressed {
            return Err(WordSyntaxError::CouldNotParse(input_txt.as_ref().to_owned()).into());
        } 

        Ok(())
    }

    fn render_normal_debug(&self) -> (String, Vec<Segment>) {
        let mut reprs = Vec::new();
        let mut buffer = String::new();
        for (i, syll) in self.syllables.iter().enumerate() {
            match syll.stress {
                StressKind::Primary => buffer.push('ˈ'), 
                StressKind::Secondary => buffer.push('ˌ'),
                StressKind::Unstressed => if i > 0 { buffer.push('.') },
            }
            for (j, seg) in syll.segments.iter().enumerate() {
                if j != 0 && *seg == syll.segments[j-1] {
                    buffer.push('ː');
                } else {
                    match &seg.get_as_grapheme() {
                        Some(str) => buffer.push_str(str),
                        None => {
                            reprs.push(*seg);
                            buffer.push('�');
                        },
                    }
                }
            }
            if syll.tone != 0 {
                buffer.push_str(&syll.tone.to_string());
            }
        }

        (buffer, reprs)
    }

    pub fn render(&self) -> String {
        let mut buffer = String::new();
        for (i, syll) in self.syllables.iter().enumerate() {
            match syll.stress {
                StressKind::Primary => buffer.push('ˈ'), 
                StressKind::Secondary => buffer.push('ˌ'),
                StressKind::Unstressed => if i > 0 { buffer.push('.') },
            }
            for (j, seg) in syll.segments.iter().enumerate() {
                if j != 0 && *seg == syll.segments[j-1] {
                    buffer.push('ː');
                } else {
                    match &seg.get_as_grapheme() {
                        Some(str) => buffer.push_str(str),
                        None => buffer.push('�'),
                    }
                }
            }
            if syll.tone != 0 {
                buffer.push_str(&syll.tone.to_string());
            }
        }

        buffer
    }

    fn alias_match_node(&self, seg: Segment, node: NodeKind, val: &ModKind) -> bool {
        match val {
            ModKind::Binary(bt) => if node == NodeKind::Place {
                let x = seg.get_node(NodeKind::Labial).is_some() 
                || seg.get_node(NodeKind::Coronal).is_some()
                || seg.get_node(NodeKind::Dorsal).is_some()
                || seg.get_node(NodeKind::Pharyngeal).is_some();
                match bt {
                    BinMod::Positive => x,
                    BinMod::Negative => !x,
                }
            } else {
                match bt {
                    BinMod::Positive => seg.get_node(node).is_some(),
                    BinMod::Negative => seg.get_node(node).is_none(),
                }
            },
            ModKind::Alpha(_) => unreachable!(),
        }
    }

    fn alias_match_seg_kind(&self, kind: &ModKind, seg: Segment, node: NodeKind, mask: u8) -> bool {
        match kind {
            ModKind::Binary(bt) => match bt {
                BinMod::Negative => seg.feat_match(node, mask, false),
                BinMod::Positive => seg.feat_match(node, mask, true),
            },
            ModKind::Alpha(_) => unreachable!(),
        }
    }

    fn alias_match_node_mod(&self, mod_kind: &Option<ModKind>, node_index: usize, seg: Segment) -> bool {
        if let Some(kind) = mod_kind {
            let node = NodeKind::from_usize(node_index);
            return self.alias_match_node(seg, node, kind)
        }
        true
    }

    fn alias_match_feat_mod(&self, md: &Option<ModKind>, feat_index: usize, seg: Segment) -> bool {
        if let Some(kind) = md { 
            let (node, mask) = FeatKind::from_usize(feat_index).as_node_mask();
            return self.alias_match_seg_kind(kind, seg, node, mask)
        }
        true
    }

    fn alias_match_stress(&self, stress: &Option<SpecMod>, syll: &Syllable, err_pos: AliasPosition) -> Result<bool, AliasRuntimeError> {
        let Some(str_mod) = stress else { return Ok(true) };

        match str_mod {
            SpecMod::First(prim) => match prim {
                ModKind::Binary(bm) => match bm {
                    BinMod::Negative => if syll.stress != StressKind::Unstressed { return Ok(false) },
                    BinMod::Positive => if syll.stress == StressKind::Unstressed { return Ok(false) },
                },
                ModKind::Alpha(_) => unreachable!(),
            },
            SpecMod::Second(sec) => match sec {
                ModKind::Binary(bm) => match bm {
                    BinMod::Negative => if syll.stress == StressKind::Secondary { return Ok(false) },
                    BinMod::Positive => if syll.stress != StressKind::Secondary { return Ok(false) },
                },
                ModKind::Alpha(_) => unreachable!(),
            },
            SpecMod::Both(prim, sec) => {
                match prim {
                    ModKind::Binary(bm) => match bm {
                        BinMod::Negative => if syll.stress != StressKind::Unstressed { return Ok(false) },
                        BinMod::Positive => if syll.stress == StressKind::Unstressed { return Ok(false) },
                    },
                    ModKind::Alpha(_) => unreachable!(),
                }
                match sec {
                    ModKind::Binary(bm) => match bm {
                        BinMod::Negative => if syll.stress == StressKind::Secondary { return Ok(false) },
                        BinMod::Positive => if syll.stress != StressKind::Secondary { return Ok(false) },
                    },
                    ModKind::Alpha(_) => unreachable!(),
                }
            },
            SpecMod::Joined(_) => return Err(AliasRuntimeError::GroupedSupras(err_pos)),
        }

        Ok(true)
    }

    fn alias_match_seg_length(&self, length: &Option<SpecMod>, syll_index: usize, seg_index: usize, err_pos: AliasPosition) -> Result<(bool, Option<usize>), AliasRuntimeError> {
        let seg_length = self.seg_length_at(SegPos { word_index: 0, syll_index, seg_index });
        let mut matched_len = false;

        if let Some(len_mods) = length {
            matched_len = true;
            match len_mods {
                SpecMod::First(long) => match long {
                    ModKind::Binary(bm) => match bm {
                        BinMod::Positive => if seg_length < 2 { return Ok((false, None)) },
                        BinMod::Negative => if seg_length > 1 { return Ok((false, None)) },
                    },
                    ModKind::Alpha(_) => unreachable!(),
                },
                SpecMod::Second(vlong) => match vlong {
                    ModKind::Binary(bm) => match bm {
                        BinMod::Positive => if seg_length < 3 { return Ok((false, None)) },
                        BinMod::Negative => if seg_length > 2 { return Ok((false, None)) },
                    },
                    ModKind::Alpha(_) => unreachable!(),
                },
                SpecMod::Both(long, vlong) => {
                    match long {
                        ModKind::Binary(bm) => match bm {
                            BinMod::Positive => if seg_length < 2 { return Ok((false, None)) },
                            BinMod::Negative => if seg_length > 1 { return Ok((false, None)) },
                        },
                        ModKind::Alpha(_) => unreachable!(),
                    }
                    match vlong {
                        ModKind::Binary(bm) => match bm {
                            BinMod::Positive => if seg_length < 3 { return Ok((false, None)) },
                            BinMod::Negative => if seg_length > 2 { return Ok((false, None)) },
                        },
                        ModKind::Alpha(_) => unreachable!(),
                    }
                },
                SpecMod::Joined(_) => return Err(AliasRuntimeError::GroupedSupras(err_pos)),
            }
        }

        if matched_len {
            Ok((true, Some(seg_length)))
        } else {
            Ok((true, None))
        }
    }

    fn alias_match_supr_mod_seg(&self, mods: &SupraSegs, syll_index: usize, seg_index: usize, err_pos: AliasPosition) -> Result<(bool, Option<usize>, bool), AliasRuntimeError> {
        let syll = &self.syllables[syll_index];

        if !self.alias_match_stress(&mods.stress, syll, err_pos)? { return Ok((false, None, false)) }

        let is_matching_tone = if let Some(t) = mods.tone.as_ref() {
            if *t != syll.tone { return Ok((false, None, true)) }
            true
        } else { false };

        let (is_match, is_matching_len) = self.alias_match_seg_length(&mods.length, syll_index, seg_index, err_pos)?;

        Ok((is_match, is_matching_len, is_matching_tone))
    }

    fn alias_match_modifiers(&self, syll_index: usize, seg_index: usize, mods: &Modifiers, err_pos: AliasPosition) -> Result<(bool, Option<usize>, bool), AliasRuntimeError> {
        let seg = self.syllables[syll_index].segments[seg_index];

        for (i, m) in mods.feats.iter().enumerate() {
            if !self.alias_match_feat_mod(m, i, seg) {
                return Ok((false, None, false))
            }
        }
        for (i, m) in mods.nodes.iter().enumerate() {
            if !self.alias_match_node_mod(m, i, seg) {
                return Ok((false, None, false))
            }
        }
        self.alias_match_supr_mod_seg( &mods.suprs, syll_index, seg_index, err_pos)
    }

    fn alias_match_ipa_with_mods(&self, syll_index: usize, seg_index: usize, ipa: &Segment, mods: &Modifiers, err_pos: AliasPosition) -> Result<(bool, Option<usize>, bool), AliasRuntimeError> {
        let mut joined_mods = ipa.as_modifiers();
        for (i, n) in mods.nodes.iter().enumerate() {
            if n.is_some() { joined_mods.nodes[i] = *n }
        }
        for (i, f) in mods.feats.iter().enumerate() {
            if f.is_some() { joined_mods.feats[i] = *f }
        }
        joined_mods.suprs = mods.suprs;

        self.alias_match_modifiers(syll_index, seg_index, &joined_mods, err_pos)   
    }

    pub(crate) fn render_debug(&self, aliases: &[Transformation]) -> Result<(String, Vec<Segment>), ASCAError> {
        if aliases.is_empty() {
            return Ok(self.render_normal_debug())
        }

        let empty: String = String::new();
        let mut bound_repl_str = None;
        let mut strip_tone = false;

        let mut buffer = String::new();
        let mut unknowns = Vec::new();

        for (i, syll) in self.syllables.iter().enumerate() {
            match syll.stress {
                StressKind::Primary => buffer.push('ˈ'), 
                StressKind::Secondary => buffer.push('ˌ'),
                StressKind::Unstressed =>  if i > 0 { buffer.push('.') },
            }

            let mut strip_len = false;

            let mut j = 0;
            'outer: while j < syll.segments.len() {
                if j != 0 && syll.segments[j] == syll.segments[j-1] && !strip_len {
                    buffer.push('ː');
                    j+=1; continue;
                }
                strip_len = false;
                for alias in aliases {
                    if let AliasParseElement::Segments(segments) = &alias.input.kind {
                        let back_pos = j;
                        let mut is_match = true;
                        let mut plus_match_len = false;
                        for segtype in segments {
                            if j >= syll.segments.len() {
                                is_match = false; break;
                            }
                            match segtype {
                                SegType::Ipa(segment, modifiers) => {
                                    if let Some(mods) = modifiers {
                                        let (m, maybe_len, maybe_tone) = self.alias_match_ipa_with_mods(i, j, segment, mods, alias.input.position)?;
                                        if !m { is_match = false; break; }
                                        if let Some(len) = maybe_len { j+=len; } else { j+=1; }
                                        if maybe_tone { strip_tone = true; }
                                    } else {
                                        if j >= syll.segments.len() || syll.segments[j] != *segment {
                                            is_match = false; break;
                                        }
                                        j+=1;
                                    }
                                },
                                SegType::Matrix(modifiers) => {
                                    let (m, maybe_len, maybe_tone) = self.alias_match_modifiers(i, j, modifiers, alias.input.position)?;
                                    if !m { is_match = false; break; }
                                    if let Some(len) = maybe_len { j+=len; plus_match_len = true; } else { j+=1; }
                                    if maybe_tone { strip_tone = true; }
                                },
                            }
                        }
                        if is_match {
                            match &alias.output.kind {
                                AliasParseElement::Replacement(repl, plus) => {
                                    if *plus {
                                        if !plus_match_len {
                                            for ind in back_pos..j {
                                                buffer.push_str(&syll.segments[ind].get_nearest_grapheme());
                                            }
                                        } else {
                                            buffer.push_str(&syll.segments[j-1].get_nearest_grapheme());
                                        }
                                    } 
                                    buffer.push_str(repl);
                                },
                                AliasParseElement::Empty => {strip_len = true;},
                                _ => unreachable!()
                            }
                            continue 'outer; 
                        }
                        j = back_pos;
                    }
                }
                match &syll.segments[j].get_as_grapheme() {
                    Some(str) => buffer.push_str(str),
                    None => {
                        unknowns.push(syll.segments[j]);
                        buffer.push('�');
                    },
                }
                j += 1;
            }

            for alias in aliases {
                if alias.input.kind == AliasParseElement::SyllBound {
                    match &alias.output.kind {
                        AliasParseElement::Replacement(repl, _) => bound_repl_str = Some(repl),
                        AliasParseElement::Empty => bound_repl_str = Some(&empty),
                        _ => unreachable!()
                    }
                }
            }

            if !strip_tone && syll.tone != 0{
                buffer.push_str(&syll.tone.to_string());
            }
        }

        if let Some(b_repl) = bound_repl_str {
            if !b_repl.is_empty() && buffer.starts_with(['ˈ', 'ˌ']) {
                let mut chars = buffer.chars();
                chars.next();
                buffer = chars.as_str().to_string();
            }
            buffer = buffer.replace(['.', 'ˈ', 'ˌ'], b_repl);
        }

        Ok((buffer, unknowns))
    }

    pub(crate) fn render_with(&self, aliases: &[Transformation]) -> Result<String, ASCAError> {
        if aliases.is_empty() {
            return Ok(self.render())
        }

        let empty: String = String::new();
        let mut bound_repl_str = None;
        let mut strip_tone = false;

        let mut buffer = String::new();

        for (i, syll) in self.syllables.iter().enumerate() {
            match syll.stress {
                StressKind::Primary => buffer.push('ˈ'), 
                StressKind::Secondary => buffer.push('ˌ'),
                StressKind::Unstressed =>  if i > 0 { buffer.push('.') },
            }

            let mut strip_len = false;

            let mut j = 0;
            'outer: while j < syll.segments.len() {
                if j != 0 && syll.segments[j] == syll.segments[j-1] && !strip_len {
                    buffer.push('ː');
                    j+=1; continue;
                }
                strip_len = false;
                for alias in aliases {
                    if let AliasParseElement::Segments(segments) = &alias.input.kind {
                        let back_pos = j;
                        let mut is_match = true;
                        let mut plus_match_len = false;
                        for segtype in segments {
                            if j >= syll.segments.len() {
                                is_match = false; break;
                            }
                            match segtype {
                                SegType::Ipa(segment, modifiers) => {
                                    if let Some(mods) = modifiers {
                                        let (m, maybe_len, maybe_tone) = self.alias_match_ipa_with_mods(i, j, segment, mods, alias.input.position)?;
                                        if !m { is_match = false; break; }
                                        if let Some(len) = maybe_len { j+=len; } else { j+=1; }
                                        if maybe_tone { strip_tone = true; }
                                    } else {
                                        if j >= syll.segments.len() || syll.segments[j] != *segment {
                                            is_match = false; break;
                                        }
                                        j+=1;
                                    }
                                },
                                SegType::Matrix(modifiers) => {
                                    let (m, maybe_len, maybe_tone) = self.alias_match_modifiers(i, j, modifiers, alias.input.position)?;
                                    if !m { is_match = false; break; }
                                    if let Some(len) = maybe_len { j+=len; plus_match_len = true; } else { j+=1; }
                                    if maybe_tone { strip_tone = true; }
                                },
                            }
                        }
                        if is_match {
                            match &alias.output.kind {
                                AliasParseElement::Replacement(repl, plus) => {
                                    if *plus {
                                        if !plus_match_len {
                                            for ind in back_pos..j {
                                                buffer.push_str(&syll.segments[ind].get_nearest_grapheme());
                                            }
                                        } else {
                                            buffer.push_str(&syll.segments[j-1].get_nearest_grapheme());
                                        }
                                    } 
                                    buffer.push_str(repl);
                                },
                                AliasParseElement::Empty => {strip_len = true;},
                                _ => unreachable!()
                            }
                            continue 'outer; 
                        }
                        j = back_pos;
                    }
                }
                match &syll.segments[j].get_as_grapheme() {
                    Some(str) => buffer.push_str(str),
                    None => buffer.push('�'),
                }
                j += 1;
            }

            for alias in aliases {
                if alias.input.kind == AliasParseElement::SyllBound {
                    match &alias.output.kind {
                        AliasParseElement::Replacement(repl, _) => bound_repl_str = Some(repl),
                        AliasParseElement::Empty => bound_repl_str = Some(&empty),
                        _ => unreachable!()
                    }
                }
            }

            if !strip_tone && syll.tone != 0{
                buffer.push_str(&syll.tone.to_string());
            }
        }

        if let Some(b_repl) = bound_repl_str {
            if !b_repl.is_empty() && buffer.starts_with(['ˈ', 'ˌ']) {
                let mut chars = buffer.chars();
                chars.next();
                buffer = chars.as_str().to_string();
            }
            buffer = buffer.replace(['.', 'ˈ', 'ˌ'], b_repl);
        }

        Ok(buffer)
    }
    
    pub fn get_syll_segments(&self, syll_index: usize) -> Option<&VecDeque<Segment>> {
        Some(&self.syllables.get(syll_index)?.segments)
    } 
    
    pub fn get_syll_segments_mut(&mut self, syll_index: usize) -> Option<&mut VecDeque<Segment>> {
        Some(&mut self.syllables.get_mut(syll_index)?.segments)
    } 

    /// Removes and returns the syllable at `syll_index` within the word, shifting all elements after it to the left.
    ///
    /// # Panics
    ///
    /// Panics if `syll_index` is out of bounds.
    pub fn remove_syll(&mut self, syll_index: usize) {
        debug_assert!(syll_index < self.syllables.len());
        self.syllables.remove(syll_index);
    }
    /// Swaps two syllable in the word.
    ///
    /// If `a` equals to `b`, it's guaranteed that elements won't change value.
    ///
    /// # Arguments
    ///
    /// * `a` - The index of the first syllable
    /// * `b` - The index of the second syllable
    ///
    /// # Panics
    ///
    /// Panics if `a` or `b` are out of bounds.
    pub fn swap_sylls(&mut self, a: usize, b: usize) {
        debug_assert!(a < self.syllables.len());
        debug_assert!(b < self.syllables.len());
        self.syllables.swap(a, b)
    }

    /// Finds number of consecutive identical segments ***within a syllable*** starting from the given index.
    /// <br>
    /// Does not take into account if said index is in the middle of the repetition
    /// # Panics
    /// If SegPos is out of bounds
    pub fn seg_length_at(&self, seg_index: SegPos) -> usize {
        self.syllables[seg_index.syll_index].get_seg_length_at(seg_index.seg_index)
    }

    // pub(crate) fn find_start_of_long_seg(&self, seg_pos: SegPos) -> SegPos {
    //     if seg_pos.seg_index == 0 {
    //         return seg_pos
    //     }
    //     let syll = &self.syllables[seg_pos.syll_index];
    //     let mut s_i = seg_pos.seg_index - 1;
    //     while s_i > 0 && syll.segments[seg_pos.seg_index] == syll.segments[s_i] {
    //         s_i -= 1;
    //     }
    //     SegPos { syll_index: seg_pos.syll_index, seg_index: s_i }
    // }

    pub(crate) fn in_bounds(&self, seg_pos: SegPos) -> bool {
        seg_pos.syll_index < self.syllables.len() && seg_pos.seg_index < self.syllables[seg_pos.syll_index].segments.len()
    }

    pub(crate) fn out_of_bounds(&self, seg_pos: SegPos) -> bool {
        seg_pos.syll_index >= self.syllables.len() || seg_pos.seg_index >= self.syllables[seg_pos.syll_index].segments.len()
    }

    pub(crate) fn get_seg_at(&self, seg_pos: SegPos) -> Option<Segment> {
        if self.in_bounds(seg_pos) {
            Some(self.syllables[seg_pos.syll_index].segments[seg_pos.seg_index])
        } else {
            None
        }
    }

    pub(crate) fn apply_seg_mods(&mut self, alphas: &RefCell<HashMap<char, Alpha>>, mods: &Modifiers, start_pos: SegPos, err_pos: Position) -> Result<i8, RuleRuntimeError> {
        self.syllables[start_pos.syll_index].apply_seg_mods(alphas, mods, start_pos.seg_index, err_pos)
    }
    
    // This is not efficient in the slightest
    // but it allows us to properly bounds check when matching the before context
    pub(crate) fn reverse(&self) -> Self {
        let mut word = self.clone();
        for syll in &mut word.syllables {
            syll.segments.make_contiguous().reverse();
        }
        word.syllables.make_contiguous().reverse();

        word
    }
}

#[cfg(test)]
mod word_tests {

    use crate::alias::{lexer::AliasLexer, parser::AliasParser, AliasKind};

    use super::*;

    #[test]
    fn test_get_grapheme() {
        let s = Word::new("n").unwrap().syllables[0].segments[0];
        assert_eq!(s.get_as_grapheme(), Some("n".to_owned()));

        let s = Word::new("x").unwrap().syllables[0].segments[0];
        assert_eq!(s.get_as_grapheme(), Some("x".to_owned()));

        let s = Word::new("xʷ").unwrap().syllables[0].segments[0];
        assert_eq!(s.get_as_grapheme(), Some("xʷ".to_owned()));
    }

    #[test]
    fn test_render_word() {
        let w = Word::new("ˌna.kiˈsa").unwrap();
        assert_eq!(w.render(), "ˌna.kiˈsa");

        let w = Word::new(",na.ki'sa").unwrap();
        assert_eq!(w.render(), "ˌna.kiˈsa");

        let w = Word::new("ˈna.ki.sa123").unwrap();
        assert_eq!(w.render(), "ˈna.ki.sa123");

        let w = Word::new("aɫ.ɫa:h").unwrap();
        assert_eq!(w.render(), "aɫ.ɫaːh");

        let w = Word::new("aɫ.ɫa;hu").unwrap();
        assert_eq!(w.render(), "aɫ.ɫaː.hu");

        let w = Word::new("ˈɫɫaa").unwrap();
        assert_eq!(w.render(), "ˈɫːaː");

        let w = Word::new("ˈt͡saa").unwrap();
        assert_eq!(w.render(), "ˈt͡saː");

        let w = Word::new("ˈt^saa").unwrap();
        assert_eq!(w.render(), "ˈt͡saː");

        let w = Word::new("ɴǃa").unwrap();
        assert_eq!(w.render(), "ɴǃa");

        let w = Word::new("ǃɴa").unwrap();
        assert_eq!(w.render(), "ǃɴa");
    }

    #[test]
    fn test_render_tone() {
        let w = Word::new("ma12").unwrap(); 
        assert_eq!(w.render(), "ma12");

        let w = Word::new("ma196").unwrap(); 
        assert_eq!(w.render(), "ma196");

        let w = Word::new("ma51").unwrap(); 
        assert_eq!(w.render(), "ma51");

        let w = Word::new("ma05").unwrap(); 
        assert_eq!(w.render(), "ma5");

        let w = Word::new("ma00").unwrap(); 
        assert_eq!(w.render(), "ma");

        let w = Word::new("ma1965").unwrap(); 
        assert_eq!(w.render(), "ma1965");

        let w = Word::new("ma190065").unwrap(); 
        assert_eq!(w.render(), "ma1965");
    }

    #[test]
    fn test_render_tone_multi() {
        assert_eq!(Word::new("ma12ma12").unwrap().render(), "ma12.ma12");
        assert_eq!(Word::new("ma00ma12").unwrap().render(), "ma.ma12");
        assert_eq!(Word::new("ma12ma00").unwrap().render(), "ma12.ma");
        assert_eq!(Word::new("ma12ma1009").unwrap().render(), "ma12.ma19");
    }


    #[test]
    fn test_render_diacritics() {
        // TODO: Test other diacritic combinations
        let w = Word::new("ˈmu.ðr̩").unwrap(); 
        assert_eq!(w.render(), "ˈmu.ðr̩");

        let w = Word::new("ˈpʰiːkʲ").unwrap(); 
        assert_eq!(w.render(), "ˈpʰiːkʲ");

        let w = Word::new("ˈpʰiikʲ").unwrap(); 
        assert_eq!(w.render(), "ˈpʰiːkʲ");
    }

    #[test]
    fn test_render_aliases() {
        match Word::new("'\"NGAN;CEUN!eB\"g.gRǝ:S!^q.φ\"hXOI?,HYZ\"wq^ʘ'p\"'a\"r") {
            Ok(w) => assert_eq!(w.render(), "ˈᶰɢɐɴː.ɕɛʊɴǃeʙˠ.ɡʀəːʃǃq.ɸʰχɔɪʔˌʜʏʒʷqʘˈpʼaʵ"),
            Err(e) => {
                println!("{}", e.format_word_error());
                assert!(false);
            }
        } 
    }

    #[test]
    fn test_americanist_aliases_replacement() {
        let into = AliasParser::new(AliasKind::Deromaniser, 
            AliasLexer::new(
                AliasKind::Deromaniser, 
                &"ł, ñ, ¢, ƛ, λ => ɬ, ɲ, t͡s, t͡ɬ, d͡ɮ".chars().collect::<Vec<_>>(), 
                0
            ).get_line().unwrap(), 
            0
        ).parse().unwrap();
        let from = AliasParser::new(AliasKind::Romaniser,   
            AliasLexer::new(
                AliasKind::Romaniser, 
                &"ɬ, ɲ, t͡s, t͡ɬ, d͡ɮ => ł, ñ, ¢, ƛ, λ".chars().collect::<Vec<_>>(), 0
            ).get_line().unwrap(), 
            0
        ).parse().unwrap();

        match Word::with("¢:añ.φλełƛ", &into) {
            Ok(w) => assert_eq!(w.render_with(&from).unwrap(), "¢ːañ.ɸλełƛ"),
            Err(e) => {
                println!("{}", e.format_word_error());
                assert!(false);
            }
        }
    }

    #[test]
    fn test_romanisation_simple() {
        let t = AliasParser::new(AliasKind::Romaniser, AliasLexer::new(AliasKind::Romaniser, &"ʃ, a:[+str], $ > sh, á, *".chars().collect::<Vec<_>>(), 0).get_line().unwrap(), 0).parse().unwrap();
        match Word::new("ʃa'ta") {
            Ok(w) => assert_eq!(w.render_with(&t).unwrap(), "shatá"),
            Err(e) => {
                println!("{}", e.format_word_error());
                assert!(false);
            }
        }
        let t = AliasParser::new(AliasKind::Romaniser, AliasLexer::new(AliasKind::Romaniser, &"k > c".chars().collect::<Vec<_>>(), 0).get_line().unwrap(), 0).parse().unwrap();
        match Word::new("'ka;ta") {
            Ok(w) => assert_eq!(w.render_with(&t).unwrap(), "ˈcaː.ta"),
            Err(e) => {
                println!("{}", e.format_word_error());
                assert!(false);
            }
        }
        let t = AliasParser::new(AliasKind::Romaniser, AliasLexer::new(AliasKind::Romaniser, &"a > o".chars().collect::<Vec<_>>(), 0).get_line().unwrap(), 0).parse().unwrap();
        match Word::new("'ka::.ta") {
            Ok(w) => assert_eq!(w.render_with(&t).unwrap(), "ˈkoːː.to"),
            Err(e) => {
                println!("{}", e.format_word_error());
                assert!(false);
            }
        }
    }

    #[test]
    fn test_romanisation_length() {
        let t = AliasParser::new(AliasKind::Romaniser, AliasLexer::new(AliasKind::Romaniser, &"ʃ:[+long], a:[+str, +long], t:[+long], $ > ssh, â, tt, *".chars().collect::<Vec<_>>(), 0).get_line().unwrap(), 0).parse().unwrap();
        match Word::new("ʃ:a't:a:") {
            Ok(w) => assert_eq!(w.render_with(&t).unwrap(), "sshattâ"),
            Err(e) => {
                println!("{}", e.format_word_error());
                assert!(false);
            }
        }
    }

    #[test]
    fn test_romanisation_syllables() {
        let t = AliasParser::new(AliasKind::Romaniser, AliasLexer::new(AliasKind::Romaniser, &"ka, ta, na, $ > カ, タ, ナ, *".chars().collect::<Vec<_>>(), 0).get_line().unwrap(), 0).parse().unwrap();
        match Word::new("ka.ta.ka.na") {
            Ok(w) => assert_eq!(w.render_with(&t).unwrap(), "カタカナ"),
            Err(e) => {
                println!("{}", e.format_word_error());
                assert!(false);
            }
        }
    }

    #[test]
    fn test_romanisation_syllables_with_tone() {
        let t = AliasParser::new(AliasKind::Romaniser, AliasLexer::new(AliasKind::Romaniser, &"ha:[tone: 55]n, ha:[tone: 51]n, y:[tone:214], $ > A, 汉, 语, *".chars().collect::<Vec<_>>(), 0).get_line().unwrap(), 0).parse().unwrap();
        match Word::new("han51.y214") {
            Ok(w) => assert_eq!(w.render_with(&t).unwrap(), "汉语"),
            Err(e) => {
                println!("{}", e.format_word_error());
                assert!(false);
            }
        }
    }

    #[test]
    fn test_romanisation_segment_with_unicode_plus() {
        let t = AliasParser::new(AliasKind::Romaniser, AliasLexer::new(AliasKind::Romaniser, &"a:[+str], $ > +@{acute}, *".chars().collect::<Vec<_>>(), 0).get_line().unwrap(), 0).parse().unwrap();
        match Word::new("'ka.ta") {
            Ok(w) => assert_eq!(w.render_with(&t).unwrap(), "káta"),
            Err(e) => {
                println!("{}", e.format_word_error());
                assert!(false);
            }
        }

        let t = AliasParser::new(AliasKind::Romaniser, AliasLexer::new(AliasKind::Romaniser, &"a:[+str], $ > @{acute}, *".chars().collect::<Vec<_>>(), 0).get_line().unwrap(), 0).parse().unwrap();
        match Word::new("'ka.ta") {
            Ok(w) => assert_eq!(w.render_with(&t).unwrap(), "ḱta"),
            Err(e) => {
                println!("{}", e.format_word_error());
                assert!(false);
            }
        }
    }

    #[test]
    fn test_romanisation_group_with_unicode_plus() {
        let t = AliasParser::new(AliasKind::Romaniser, AliasLexer::new(AliasKind::Romaniser, &"V:[+str], $ > +@{acute}, *".chars().collect::<Vec<_>>(), 0).get_line().unwrap(), 0).parse().unwrap();
        match Word::new("'ka.ta") {
            Ok(w) => assert_eq!(w.render_with(&t).unwrap(), "káta"),
            Err(e) => {
                println!("{}", e.format_word_error());
                assert!(false);
            }
        }

        let t = AliasParser::new(AliasKind::Romaniser, AliasLexer::new(AliasKind::Romaniser, &"V:[+str,+long], $ > +@{acute}, *".chars().collect::<Vec<_>>(), 0).get_line().unwrap(), 0).parse().unwrap();
        match Word::new("'ka:.ta") {
            Ok(w) => assert_eq!(w.render_with(&t).unwrap(), "káta"),
            Err(e) => {
                println!("{}", e.format_word_error());
                assert!(false);
            }
        }
    }

    #[test]
    fn test_romanisation_remove_segment() {
        let t = AliasParser::new(AliasKind::Romaniser, AliasLexer::new(AliasKind::Romaniser, &"a > *".chars().collect::<Vec<_>>(), 0).get_line().unwrap(), 0).parse().unwrap();
        match Word::new("san.da") {
            Ok(w) => assert_eq!(w.render_with(&t).unwrap(), "sn.d"),
            Err(e) => {
                println!("{}", e.format_word_error());
                assert!(false);
            }
        }
        match Word::new("sa:n.da") {
            Ok(w) => assert_eq!(w.render_with(&t).unwrap(), "sn.d"),
            Err(e) => {
                println!("{}", e.format_word_error());
                assert!(false);
            }
        }

        let t = AliasParser::new(AliasKind::Romaniser, AliasLexer::new(AliasKind::Romaniser, &"a:[+long] > *".chars().collect::<Vec<_>>(), 0).get_line().unwrap(), 0).parse().unwrap();
        match Word::new("sa:n.da") {
            Ok(w) => assert_eq!(w.render_with(&t).unwrap(), "sn.da"),
            Err(e) => {
                println!("{}", e.format_word_error());
                assert!(false);
            }
        }
    }

    #[test]
    fn test_deromanisation_simple() {
        let t = AliasParser::new(AliasKind::Deromaniser, AliasLexer::new(AliasKind::Deromaniser, &"sh, á => ʃ, a:[+str]".chars().collect::<Vec<_>>(), 0).get_line().unwrap(), 0).parse().unwrap();
        match Word::with("sha.tá", &t) {
            Ok(w) => assert_eq!(w.render(), "ʃaˈta"),
            Err(e) => {
                println!("{}", e.format_word_error());
                assert!(false);
            }
        }
    }

    #[test]
    fn test_deromanisation_length() {
        let t = AliasParser::new(AliasKind::Deromaniser, AliasLexer::new(AliasKind::Deromaniser, &"ssh, â => ʃ:[+long], a:[+str, +long]".chars().collect::<Vec<_>>(), 0).get_line().unwrap(), 0).parse().unwrap();
        match Word::with("ssha.tâ", &t) {
            Ok(w) => assert_eq!(w.render(), "ʃːaˈtaː"),
            Err(e) => {
                println!("{}", e.format_word_error());
                assert!(false);
            }
        }

        let t = AliasParser::new(AliasKind::Deromaniser, AliasLexer::new(AliasKind::Deromaniser, &"+@{circum} => [+str, +long]".chars().collect::<Vec<_>>(), 0).get_line().unwrap(), 0).parse().unwrap();
        match Word::with("tâ", &t) {
            Ok(w) => assert_eq!(w.render(), "ˈtaː"),
            Err(e) => {
                println!("{}", e.format_word_error());
                assert!(false);
            }
        }
    }

    #[test]
    fn test_deromanisation_syllables() {
        let t = AliasParser::new(AliasKind::Deromaniser, AliasLexer::new(AliasKind::Deromaniser, &"カ, タ, ナ > ka, ta, na".chars().collect::<Vec<_>>(), 0).get_line().unwrap(), 0).parse().unwrap();
        match Word::with("カ.タ.カ.ナ", &t) {
            Ok(w) => assert_eq!(w.render(), "ka.ta.ka.na"),
            Err(e) => {
                println!("{}", e.format_word_error());
                assert!(false);
            }
        }
    }

    #[test]
    fn test_deromanisation_syllables_with_tone() {
        let t = AliasParser::new(AliasKind::Deromaniser, AliasLexer::new(AliasKind::Deromaniser, &"汉, 语 => ha:[tn: 51]n, y:[tone:214]".chars().collect::<Vec<_>>(), 0).get_line().unwrap(), 0).parse().unwrap();
        match Word::with("汉.语", &t) {
            Ok(w) => assert_eq!(w.render(), "han51.y214"),
            Err(e) => {
                println!("{}", e.format_word_error());
                assert!(false);
            }
        }
    }
}