orinfar 0.0.1

A Text Editor For Witches
use crate::{
    buffer::Buffer,
    utility::{is_symbol, on_next_input_buffer_only},
};
use crossterm::event::KeyCode;

pub struct Motion<'a> {
    pub name: &'a str,
    command: fn(buffer: &mut Buffer),
    pub inclusive: bool,
}

impl<'a> Motion<'a> {
    pub fn exclusive(name: &'a str, command: fn(buffer: &mut Buffer)) -> Self {
        Self {
            name,
            command,
            inclusive: false,
        }
    }

    pub fn inclusive(name: &'a str, command: fn(buffer: &mut Buffer)) -> Self {
        Self {
            name,
            command,
            inclusive: true,
        }
    }

    // Called when the motion should be applied directly
    pub fn apply(&self, buffer: &mut Buffer) {
        (self.command)(buffer);
    }

    // Called when the motion is chained to an operator
    // Doesn't apply the motion to the buffer but returns where the motion would have gone
    pub fn evaluate(&self, buffer: &Buffer) -> usize {
        let mut fake_buffer = buffer.clone();
        (self.command)(&mut fake_buffer);

        fake_buffer.cursor
    }
}

pub fn prev_char(buffer: &mut Buffer) {
    buffer.prev_char();
}

pub fn next_row(buffer: &mut Buffer) {
    if buffer.is_last_row() {
        return;
    }

    buffer.next_line();
}

pub fn prev_row(buffer: &mut Buffer) {
    if buffer.get_row() == 0 {
        return;
    }

    buffer.prev_line();

    let len = buffer.get_curr_line().len_chars();

    let col = if len > 0 {
        usize::min(buffer.get_col(), len - 1)
    } else {
        0
    };
    buffer.set_col(col);
}

pub fn next_char(buffer: &mut Buffer) {
    buffer.next_char();
}

pub fn word(buffer: &mut Buffer) {
    if buffer.get_curr_line().len_chars() == buffer.get_col() {
        return;
    }
    let mut c = buffer.get_curr_char();

    let last_legal_char = buffer.rope.len_chars() - 1;

    if is_symbol(c) {
        while is_symbol(c) && buffer.cursor < last_legal_char {
            c = unwrap_or_break!(buffer.next_and_char());
        }
    } else if c.is_alphanumeric() || c == '_' {
        while (c.is_alphanumeric() || c == '_') && buffer.cursor < last_legal_char {
            c = unwrap_or_break!(buffer.next_and_char());
        }
    }

    while c.is_whitespace() && buffer.cursor < last_legal_char {
        if c == '\n' {
            if buffer.cursor < last_legal_char {
                buffer.cursor += 1;
            }
            return;
        }
        c = unwrap_or_break!(buffer.next_and_char());
    }
}

pub fn back(buffer: &mut Buffer) {
    if buffer.cursor == 0 {
        return;
    }
    let mut prev_char = unwrap_or_return!(buffer.get_prev_char());

    if prev_char == '\n' && buffer.cursor > 0 {
        buffer.cursor -= 1;
        return;
    }

    if is_symbol(prev_char) {
        while is_symbol(prev_char) && buffer.cursor > 0 {
            buffer.prev_char();
            prev_char = unwrap_or_break!(buffer.get_prev_char());
        }
    } else if prev_char.is_alphanumeric() || prev_char == '_' {
        while prev_char.is_alphanumeric() || prev_char == '_' && buffer.cursor > 0 {
            buffer.prev_char();
            prev_char = unwrap_or_break!(buffer.get_prev_char());
        }
    } else {
        while prev_char.is_whitespace() && buffer.cursor > 0 {
            buffer.prev_char();
            prev_char = unwrap_or_break!(buffer.get_prev_char());
        }
        if prev_char.is_alphanumeric() {
            while prev_char.is_alphanumeric() && buffer.cursor > 0 {
                buffer.prev_char();
                prev_char = unwrap_or_break!(buffer.get_prev_char());
            }
        } else if is_symbol(prev_char) {
            while is_symbol(prev_char) && buffer.cursor > 0 {
                buffer.prev_char();
                prev_char = unwrap_or_break!(buffer.get_prev_char());
            }
        }
    }
}

pub fn end_of_word(buffer: &mut Buffer) {
    if buffer.get_curr_line().len_chars() == buffer.get_col() {
        return;
    }

    let last_legal_char = buffer.rope.len_chars() - 1;
    let mut next_char = unwrap_or_return!(buffer.get_next_char());

    if next_char == '\n' {
        if buffer.cursor < last_legal_char {
            buffer.cursor += 2;
        }
        return;
    }

    if is_symbol(next_char) {
        while is_symbol(next_char) && buffer.cursor < last_legal_char {
            buffer.next_char();
            next_char = unwrap_or_break!(buffer.get_next_char());
        }
    } else if next_char.is_alphanumeric() || next_char == '_' {
        while next_char.is_alphanumeric() || next_char == '_' && buffer.cursor < last_legal_char {
            buffer.next_char();
            next_char = unwrap_or_break!(buffer.get_next_char());
        }
    } else {
        while next_char.is_whitespace() && buffer.cursor < last_legal_char {
            buffer.next_char();
            next_char = unwrap_or_break!(buffer.get_next_char());
        }
        if next_char.is_alphanumeric() {
            while next_char.is_alphanumeric() && buffer.cursor < last_legal_char {
                buffer.next_char();
                next_char = unwrap_or_break!(buffer.get_next_char());
            }
        } else if is_symbol(next_char) {
            while is_symbol(next_char) && buffer.cursor < last_legal_char {
                buffer.next_char();
                next_char = unwrap_or_break!(buffer.get_next_char());
            }
        }
    }
}

pub fn end_of_line(buffer: &mut Buffer) {
    buffer.end_of_line();
}

pub fn beginning_of_line(buffer: &mut Buffer) {
    buffer.set_col(buffer.first_non_whitespace_col());
}

pub fn find(buffer: &mut Buffer) {
    fn find(key: KeyCode, buffer: &mut Buffer) {
        if let KeyCode::Char(target) = key {
            let anchor = buffer.cursor;
            loop {
                if buffer.cursor == buffer.get_end_of_line() {
                    break;
                }
                if buffer.get_curr_char() == target {
                    return;
                }
                buffer.cursor += 1;
            }

            buffer.cursor = anchor;
        }
    }

    on_next_input_buffer_only(buffer, find).expect("Failed to get character to find");
}

pub fn find_until(buffer: &mut Buffer) {
    fn find_until(key: KeyCode, buffer: &mut Buffer) {
        if let KeyCode::Char(target) = key {
            let anchor = buffer.cursor;
            loop {
                if buffer.cursor == buffer.get_end_of_line() {
                    break;
                }
                if buffer.get_curr_char() == target {
                    if buffer.cursor != 0 {
                        buffer.cursor -= 1;
                    }
                    return;
                }

                buffer.cursor += 1;
            }

            buffer.cursor = anchor;
        }
    }

    on_next_input_buffer_only(buffer, find_until).expect("Failed to get character to find");
}

pub fn find_back(buffer: &mut Buffer) {
    fn find_back(key: KeyCode, buffer: &mut Buffer) {
        if let KeyCode::Char(target) = key {
            let anchor = buffer.cursor;
            loop {
                if buffer.cursor == 0 {
                    break;
                }
                if buffer.get_curr_char() == target {
                    return;
                }

                buffer.cursor -= 1;
            }

            buffer.cursor = anchor;
        }
    }

    on_next_input_buffer_only(buffer, find_back).expect("Failed to get character to find");
}

/// Goes to the opposite bracket corresponding to the next bracket in the line (inclusive with  the
/// current character).
pub fn next_corresponding_bracket(buffer: &mut Buffer) {
    let anchor = buffer.cursor;
    let end_of_line = buffer.get_end_of_line();

    let mut c = buffer.get_curr_char();

    while c != '{' && c != '[' && c != '(' && c != ')' && c != ']' && c != '}' {
        if end_of_line > buffer.cursor {
            buffer.cursor += 1;
            c = buffer.get_curr_char();
        } else {
            buffer.cursor = anchor;
            return;
        }
    }

    let end_of_file = buffer.rope.len_chars();
    let mut count = 0;
    let start_character = c;
    let (search_character, direction): (char, i32) = match c {
        '{' => ('}', 1),
        '[' => (']', 1),
        '(' => (')', 1),
        '}' => ('{', -1),
        ']' => ('[', -1),
        ')' => ('(', -1),
        _ => panic!("Bug in next_bracket function"),
    };

    // TODO
    // There's probably a better way to do this
    let condition: Box<dyn Fn(usize) -> bool> = match direction {
        1 => Box::new(|cursor: usize| end_of_file > cursor + 1),
        -1 => Box::new(|cursor: usize| i32::try_from(cursor).unwrap() > 0),
        _ => unreachable!(),
    };

    loop {
        if condition(buffer.cursor) {
            buffer.cursor =
                usize::try_from(i32::try_from(buffer.cursor).unwrap() + direction).unwrap();
            c = buffer.get_curr_char();
        } else {
            buffer.cursor = anchor;
            return;
        }

        if c == start_character {
            count += 1;
        } else if c == search_character {
            if count == 0 {
                break;
            }
            count -= 1;
        }
    }
}

/// Moves the cursor to the next empty line after a non-empty line
pub fn next_newline(buffer: &mut Buffer) {
    while buffer.is_empty_line() {
        if buffer.is_last_row() {
            return;
        }
        next_row(buffer);
    }

    while !buffer.is_empty_line() {
        if buffer.is_last_row() {
            return;
        }
        next_row(buffer);
    }
}

/// Moves the cursor to the next empty line after a non-empty line
pub fn prev_newline(buffer: &mut Buffer) {
    while buffer.is_empty_line() {
        if buffer.is_first_row() {
            return;
        }
        prev_row(buffer);
    }

    while !buffer.is_empty_line() {
        if buffer.is_first_row() {
            return;
        }
        prev_row(buffer);
    }
}