orinfar 0.0.1

A Text Editor For Witches
use crate::{
    Mode,
    buffer::Buffer,
    motion::Motion,
    register::RegisterHandler,
    undo::{Action, UndoTree},
};

pub struct Operator<'a> {
    pub name: &'a str,
    command: fn(
        end: usize,
        buffer: &mut Buffer,
        register_handler: &mut RegisterHandler,
        mode: &mut Mode,
        undo_tree: &mut UndoTree,
    ),
}

impl<'a> Operator<'a> {
    pub fn new(
        name: &'a str,
        command: fn(
            end: usize,
            buffer: &mut Buffer,
            register_handler: &mut RegisterHandler,
            mode: &mut Mode,
            undo_tree: &mut UndoTree,
        ),
    ) -> Self {
        Self { name, command }
    }

    pub fn execute(
        &self,
        motion: &Motion,
        buffer: &mut Buffer,
        register_handler: &mut RegisterHandler,
        mode: &mut Mode,
        undo_tree: &mut UndoTree,
    ) {
        let mut end = motion.evaluate(buffer);
        if !motion.inclusive && end != buffer.get_end_of_line() {
            end = usize::max(end, 1) - 1;
        }

        (self.command)(end, buffer, register_handler, mode, undo_tree);
    }

    pub fn entire_line(
        &self,
        buffer: &mut Buffer,
        register_handler: &mut RegisterHandler,
        mode: &mut Mode,
        undo_tree: &mut UndoTree,
    ) {
        buffer.start_of_line();
        let end_of_line = buffer.get_end_of_line();
        let reg_before = register_handler.get_reg().to_string();

        (self.command)(end_of_line, buffer, register_handler, mode, undo_tree);
        if reg_before != register_handler.get_reg()
            && register_handler.get_reg().chars().last().unwrap_or('\n') != '\n'
        {
            register_handler.push_reg(&'\n');
        }
    }
}

/// Iterates over a range, allowing an operation to be performed across the range.
/// This is more general than just say, capitalizing every character.
///
/// Thir is how operations like yank and delete are able to work across arbitrary ranges, even
/// ranges defined by the contents of the file, such as 'yfi'
///
/// # Params
/// - `mode`: The current mode the editor is in (eg. Visual, Insert, Command).
/// - `register_handler`: The manager handler, there should just be one for the entire editor.
///   This is necessary for the yanking of items, for example.
/// - `buffer`: The actual underlying text buffer object.
/// - `end`: The index at which to end the traversal on. Must be greater than the initial value of `buffer.cursor`
/// - `initial_callback`: The function which is run before the iteration.
/// - `iter_callback`: The function which runs for every character in the iteration. It should
///   assume that `buffer.cursor` is the current index of the iteration.
///   This is the only callback incapable of accessing the current mode.
/// - `after_callback`: The function which runs after the iteration is complete. This callback is
///   the only one provided with the original index of the cursor before the
///   iteration. One use-case of this callback could be to reset the position of
///   the cursor after the iteration.
///
/// Any of the given callbacks may be noops. Each callback is free modify
pub fn iterate_range(
    mode: &mut Mode,
    register_handler: &mut RegisterHandler,
    buffer: &mut Buffer,
    end: usize,
    initial_callback: fn(
        register_handler: &mut RegisterHandler,
        buffer: &mut Buffer,
        mode: &mut Mode,
    ),
    iter_callback: fn(register_handler: &mut RegisterHandler, buffer: &mut Buffer),
    after_callback: fn(
        start: usize,
        register_handler: &mut RegisterHandler,
        buffer: &mut Buffer,
        mode: &mut Mode,
    ),
) {
    let anchor = buffer.cursor;
    let count = (i32::try_from(end).unwrap() - i32::try_from(anchor).unwrap()).abs();
    initial_callback(register_handler, buffer, mode);
    (0..=count).for_each(|_| iter_callback(register_handler, buffer));
    after_callback(anchor, register_handler, buffer, mode);
}

const fn noop(
    _start: usize,
    _register_handler: &mut RegisterHandler,
    _buffer: &mut Buffer,
    _mode: &mut Mode,
) {
}

const fn reset_position(
    start: usize,
    _register_handler: &mut RegisterHandler,
    buffer: &mut Buffer,
    _mode: &mut Mode,
) {
    buffer.cursor = start;
}

fn insert(
    _start: usize,
    _register_handler: &mut RegisterHandler,
    _buffer: &mut Buffer,
    mode: &mut Mode,
) {
    mode.insert();
}

fn clear_reg(register_handler: &mut RegisterHandler, _buffer: &mut Buffer, _mode: &mut Mode) {
    register_handler.empty_reg();
}

fn delete_char(register_handler: &mut RegisterHandler, buffer: &mut Buffer) {
    if buffer.rope.len_chars() <= buffer.cursor {
        return;
    }
    register_handler.push_reg(&buffer.get_curr_char());
    buffer.delete_curr_char();
}
pub fn delete(
    end: usize,
    buffer: &mut Buffer,
    register_handler: &mut RegisterHandler,
    mode: &mut Mode,
    undo_tree: &mut UndoTree,
) {
    let end_of_file = buffer.rope.len_chars();
    if end == end_of_file && end != 0 {
        buffer.cursor -= 1;
    }

    iterate_range(
        mode,
        register_handler,
        buffer,
        end,
        clear_reg,
        delete_char,
        noop,
    );
    // TODO
    // Currently using the 'd' command across lines will break because of this
    buffer.update_list_use_current_line();

    let text = register_handler.get_reg();
    let action = Action::delete(buffer.cursor, &text);
    undo_tree.new_action(action);
}

fn yank_char(register_handler: &mut RegisterHandler, buffer: &mut Buffer) {
    register_handler.push_reg(&buffer.get_curr_char());
    if buffer.cursor + 1 < buffer.rope.len_chars() {
        buffer.next_char();
    }
}
pub fn yank(
    end: usize,
    buffer: &mut Buffer,
    register_handler: &mut RegisterHandler,
    mode: &mut Mode,
    _undo_tree: &mut UndoTree,
) {
    if end == buffer.rope.len_chars() && end == buffer.cursor {
        return;
    }
    let end_of_file = buffer.rope.len_chars();
    if end == end_of_file && end != 0 {
        buffer.cursor -= 1;
    }
    iterate_range(
        mode,
        register_handler,
        buffer,
        end,
        clear_reg,
        yank_char,
        reset_position,
    );
}

pub fn change(
    end: usize,
    buffer: &mut Buffer,
    register_handler: &mut RegisterHandler,
    mode: &mut Mode,
    undo_tree: &mut UndoTree,
) {
    let end_of_file = buffer.rope.len_chars();
    if end == end_of_file && end != 0 {
        buffer.cursor -= 1;
    }

    iterate_range(
        mode,
        register_handler,
        buffer,
        end,
        clear_reg,
        delete_char,
        insert,
    );

    // TODO
    // Currently using the 'c' command across lines will break because of this
    buffer.update_list_use_current_line();

    let text = register_handler.get_reg();
    let action = Action::delete(buffer.cursor, &text);
    undo_tree.new_action(action);
}