mod buf;

use std::io::{self, Write};

use crate::{
    cursor::{self, CursorPos, Direction},
    syntax_highlighting::Highlighter,
};
pub use buf::LineBuf;

pub struct Line {
    buf: LineBuf,
    dirty: bool,
    line_start: CursorPos,
}

impl std::ops::Deref for Line {
    type Target = LineBuf;

    fn deref(&self) -> &Self::Target {
        &self.buf
    }
}

impl Line {
    pub fn new() -> Self {
        Self {
            buf: LineBuf::new(),
            dirty: false,
            line_start: CursorPos::default(),
        }
    }

    pub fn set_begin_of_line(&mut self, pos: CursorPos) {
        self.line_start = pos;
    }

    pub fn highlight_syntax(&mut self, h: &mut Highlighter) -> std::io::Result<()> {
        if !self.dirty {
            return Ok(());
        }
        self.dirty = false;

        let buf = self.into_bytes();

        let highlights = if h.enabled {
            use crate::parse::{self, Parse};
            let mut parser = parse::Cursor::new(&buf, parse::ParseMode::Completion);
            let _ = parse::Ast::parse(&mut parser);
            parser.highlights
        } else {
            Vec::new()
        };

        let mut stdout = io::stdout().lock();
        self.line_start.f_go_to(&mut stdout)?;
        h.pretty_print(&buf, self.left_len(), highlights, &mut stdout)?;
        stdout.flush()?;

        Ok(())
    }

    pub fn mark_clean(&mut self) {
        self.dirty = false;
    }

    pub fn mark_dirty(&mut self) {
        self.dirty = true;
    }

    pub fn is_dirty(&self) -> bool {
        self.dirty
    }

    pub fn had_contents(&self) -> bool {
        self.buf.is_dirty()
    }

    pub fn is_empty(&self) -> bool {
        self.buf.is_empty()
    }

    pub fn cursor_left(&mut self) -> io::Result<()> {
        if self.buf.left() {
            let mut stdout = io::stdout().lock();
            cursor::fmove_cursor(Direction::Left, 1, &mut stdout)?;
            stdout.flush()?;
        }
        Ok(())
    }

    pub fn cursor_right(&mut self) -> io::Result<()> {
        if self.buf.right() {
            let mut stdout = io::stdout().lock();
            cursor::fmove_cursor(Direction::Right, 1, &mut stdout)?;
            stdout.flush()?;
        }
        Ok(())
    }

    pub fn move_to_begin(&mut self) -> io::Result<()> {
        let mut stdout = io::stdout().lock();
        cursor::fmove_cursor(Direction::Left, self.buf.all_left(), &mut stdout)?;
        stdout.flush()
    }

    pub fn move_to_end(&mut self) -> io::Result<()> {
        let mut stdout = io::stdout().lock();
        cursor::fmove_cursor(Direction::Right, self.buf.all_right(), &mut stdout)?;
        stdout.flush()
    }

    // move to next
    pub fn cursor_left_word(&mut self) -> io::Result<()> {
        let mut i = 0;

        // find word
        while let Some(b' ') = self.buf.get_left() {
            self.buf.left();
            i += 1;
        }

        // skip it
        while let Some(x) = self.buf.get_left()
            && !x.is_ascii_whitespace()
        {
            self.buf.left();
            i += 1;
        }

        let mut stdout = io::stdout().lock();
        cursor::fmove_cursor(Direction::Left, i, &mut stdout)?;
        stdout.flush()
    }

    pub fn cursor_right_word(&mut self) -> io::Result<()> {
        let mut i = 0;

        // find word
        while let Some(b' ') = self.buf.get_right() {
            self.buf.right();
            i += 1;
        }

        // skip it
        while let Some(x) = self.buf.get_right()
            && !x.is_ascii_whitespace()
        {
            self.buf.right();
            i += 1;
        }

        let mut stdout = io::stdout().lock();
        cursor::fmove_cursor(Direction::Right, i, &mut stdout)?;
        stdout.flush()
    }
}

macro_rules! mutating_impls {
    ($(pub fn $fun:ident(&mut $self:ident $($arg:tt)*) -> $ty:ty $body:block )*) => {
        impl Line {$(
            pub fn $fun(&mut $self $($arg)*) -> $ty {
                let result = $body;
                $self.dirty = true;
                result
            }
        )*}
    };
}

mutating_impls! {
    pub fn clear_prompt(&mut self) -> io::Result<()> {
        self.buf.clear();
        Ok(())
    }

    pub fn type_bytes(&mut self, bs: &[u8]) -> io::Result<()> {
        for b in bs.iter() {
            self.buf.add(*b);
        }
        Ok(())
    }

    pub fn type_byte(&mut self, b: u8) -> io::Result<()> {
        self.type_bytes(&[b])
    }

    pub fn del_left(&mut self) -> io::Result<()> {
        if self.buf.del_left().is_some() {
            let mut stdout = io::stdout().lock();
            cursor::fmove_cursor(Direction::Left, 1, &mut stdout)?;
            stdout.flush()?;
        }
        Ok(())
    }

    pub fn del_right(&mut self) -> io::Result<()> {
        self.buf.del_right();
        Ok(())
    }

    pub fn del_left_word(&mut self) -> io::Result<()> {
        while let Some(x) = self.buf.get_left()
            && x == b' '
        {
            self.buf.del_left();
        }
        while let Some(x) = self.buf.get_left()
            && x != b' '
        {
            self.buf.del_left();
        }
        Ok(())
    }

    pub fn del_right_word(&mut self) -> io::Result<()> {
        while let Some(x) = self.buf.get_right()
            && x == b' '
        {
            self.buf.del_right();
        }
        while let Some(x) = self.buf.get_right()
            && x != b' '
        {
            self.buf.del_right();
        }
        Ok(())
    }

    pub fn set_content(&mut self, content: Vec<u8>) -> io::Result<()> {
        self.buf.set_content(content);
        Ok(())
    }

    pub fn dump(&mut self) -> Vec<u8> {
        self.buf.dump()
    }
}
