use std::{
    ffi::OsStr,
    fmt::Write as FmtWrite,
    fs::File,
    io::{self, BufRead, BufReader, BufWriter, Write as IoWrite},
    path::{Path, PathBuf},
};

use libterm::{Color, CursorStyle, Term};
use unicode_width::UnicodeWidthChar;

use crate::{
    config::Config,
    error::Error,
    line::{Line, TextLike},
};

#[derive(Default)]
pub struct View {
    cursor_column: usize,
    cursor_row: usize,
    column_offset: usize,
    row_offset: usize,
    width: usize,
    height: usize,
    offset_x: usize,
}

#[derive(Clone, Copy, PartialEq)]
pub enum Mode {
    Normal,
    Insert,
}

pub enum SetMode {
    Normal,
    InsertBefore,
    InsertAfter,
}

pub struct Buffer {
    lines: Vec<Line>,
    dirty: bool,
    mode_dirty: bool,
    mode: Mode,
    view: View,
    name: Option<String>,
    path: Option<PathBuf>,
    modified: bool,
}

impl Mode {
    pub fn as_str(&self) -> &str {
        match self {
            Self::Normal => "NORMAL",
            Self::Insert => "INSERT",
        }
    }
}

impl View {
    pub fn set_row(&mut self, row: usize) {
        self.cursor_row = row;

        if self.cursor_row < self.row_offset {
            self.row_offset = self.cursor_row;
        } else if self.cursor_row >= self.row_offset + self.height {
            self.row_offset = self.cursor_row - self.height + 1;
        }
    }

    pub fn set_column(&mut self, config: &Config, col: usize, line: Option<&Line>) {
        let Some(line) = line else {
            self.column_offset = 0;
            self.cursor_column = 0;
            return;
        };

        self.cursor_column = col;

        if line.display_width(config.tab_width) < self.width {
            self.column_offset = 0;
            return;
        }

        let width_to_cursor = line
            .span(..self.cursor_column)
            .display_width(config.tab_width);

        if width_to_cursor < self.column_offset {
            self.column_offset = width_to_cursor;
        } else if width_to_cursor >= self.column_offset + self.width {
            self.column_offset = width_to_cursor - self.width + 1;
        }
    }

    pub fn reset(&mut self) {
        self.column_offset = 0;
        self.row_offset = 0;
        self.cursor_row = 0;
        self.cursor_column = 0;
    }

    // pub fn resize(&mut self, width: usize, height: usize) {
    //     self.width = width;
    //     self.height = height;

    //     self.column_offset = 0;
    //     self.row_offset = 0;
    // }
}

impl Buffer {
    pub fn empty() -> Self {
        Self {
            lines: vec![],
            dirty: true,
            mode_dirty: true,
            view: View::default(),
            mode: Mode::Normal,
            name: None,
            path: None,
            modified: false,
        }
    }

    fn read_lines<P: AsRef<Path>>(path: P) -> io::Result<Vec<Line>> {
        let input = BufReader::new(File::open(path)?);
        let lines = input.lines().collect::<Result<Vec<_>, _>>()?;
        let lines = lines
            .into_iter()
            .map(|line| Line::from_str(line.trim_end_matches('\n')))
            .collect();
        Ok(lines)
    }

    pub fn open<P: AsRef<Path>>(path: P) -> io::Result<Self> {
        let path = path.as_ref();
        let name = path.file_name().and_then(OsStr::to_str).map(String::from);
        let lines = if path.exists() {
            Self::read_lines(path)?
        } else {
            vec![]
        };

        Ok(Self {
            lines,
            name,
            path: Some(path.into()),
            mode: Mode::Normal,
            dirty: true,
            mode_dirty: true,
            view: View::default(),
            modified: false,
        })
    }

    pub fn reopen<P: AsRef<Path>>(&mut self, path: P) -> io::Result<()> {
        let path = path.as_ref();
        let name = path.file_name().and_then(OsStr::to_str).map(String::from);
        let lines = if path.exists() {
            Self::read_lines(path)?
        } else {
            vec![]
        };

        self.lines = lines;
        self.modified = false;
        self.mode = Mode::Normal;
        self.dirty = true;
        self.mode_dirty = true;
        self.view.reset();

        self.path = Some(path.into());
        self.name = name;

        Ok(())
    }

    pub fn save(&mut self) -> Result<(), Error> {
        let path = self.path.as_ref().ok_or(Error::NoPath)?;
        let mut writer = BufWriter::new(File::create(path).map_err(Error::WriteError)?);

        for line in self.lines.iter() {
            writer
                .write_all(line.to_string().as_ref())
                .map_err(Error::WriteError)?;
            writer.write_all(b"\n").map_err(Error::WriteError)?;
        }

        self.modified = false;

        Ok(())
    }

    pub fn set_path<P: AsRef<Path>>(&mut self, path: P) {
        let path = PathBuf::from(path.as_ref());
        let name = path.file_name().and_then(OsStr::to_str).map(String::from);

        self.path = Some(path);
        self.name = name;
    }

    pub fn set_mode(&mut self, config: &Config, mode: SetMode) {
        let dst_mode = match mode {
            SetMode::Normal => Mode::Normal,
            SetMode::InsertAfter | SetMode::InsertBefore => Mode::Insert,
        };

        if dst_mode == self.mode {
            return;
        }

        self.mode = dst_mode;
        self.mode_dirty = true;
        match mode {
            SetMode::Normal => self.move_cursor(config, -1, 0),
            SetMode::InsertBefore => self.move_cursor(config, 0, 0),
            SetMode::InsertAfter => self.move_cursor(config, 1, 0),
        }
    }

    pub fn mode(&self) -> Mode {
        self.mode
    }

    pub fn name(&self) -> Option<&String> {
        self.name.as_ref()
    }

    pub fn path(&self) -> Option<&PathBuf> {
        self.path.as_ref()
    }

    pub fn row_offset(&self) -> usize {
        self.view.row_offset
    }

    pub fn len(&self) -> usize {
        self.lines.len()
    }

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

    pub fn cursor_row(&self) -> usize {
        self.view.cursor_row
    }

    pub fn set_position(&mut self, config: &Config, px: usize, py: usize) {
        self.dirty = true;

        if self.lines.is_empty() {
            self.view.reset();
            return;
        }

        // Move to row
        self.view.set_row(py.min(self.lines.len() - 1));

        // Set mode- and line-len-adjusted column
        if let Some(line) = self.lines.get(self.view.cursor_row)
            && !line.is_empty()
        {
            match self.mode {
                // Limited by line.len()
                Mode::Normal => self
                    .view
                    .set_column(config, px.min(line.len() - 1), Some(line)),
                Mode::Insert => self.view.set_column(config, px.min(line.len()), Some(line)),
            }
        } else {
            self.view.set_column(config, 0, None);
        }
    }

    pub fn to_line_end(&mut self, config: &Config) {
        let len = self
            .lines
            .get(self.view.cursor_row)
            .map(Line::len)
            .unwrap_or(0);

        self.set_position(config, len, self.view.cursor_row);
    }

    pub fn to_first_line(&mut self, config: &Config) {
        let len = self
            .lines
            .get(self.view.cursor_row)
            .map(Line::len)
            .unwrap_or(0);

        self.set_position(config, len, 0);
    }

    pub fn to_last_line(&mut self, config: &Config) {
        if self.lines.is_empty() {
            return;
        }

        let len = self
            .lines
            .get(self.view.cursor_row)
            .map(Line::len)
            .unwrap_or(0);

        self.set_position(config, len, self.lines.len() - 1);
    }

    pub fn set_column(&mut self, config: &Config, x: usize) {
        self.set_position(config, x, self.view.cursor_row);
    }

    pub fn move_cursor(&mut self, config: &Config, dx: isize, dy: isize) {
        let px = (self.view.cursor_column as isize + dx).max(0) as usize;
        let py = (self.view.cursor_row as isize + dy).max(0) as usize;

        self.set_position(config, px, py);
    }

    pub fn resize(&mut self, config: &Config, offset_x: usize, width: usize, height: usize) {
        self.dirty = true;
        self.view.height = height;
        self.view.width = width;
        self.view.offset_x = offset_x;

        self.view.set_row(self.view.cursor_row);
        self.view.set_column(
            config,
            self.view.cursor_column,
            self.lines.get(self.view.cursor_row),
        );
    }

    pub fn display_cursor(&self, config: &Config) -> (usize, usize) {
        if self.lines.is_empty() {
            return (0, 0);
        }

        // assert!(self.view.column_offset <= self.view.cursor_column);
        assert!(self.view.row_offset <= self.view.cursor_row);

        let line = &self.lines[self.view.cursor_row];
        assert!(self.view.cursor_column <= line.len());

        let column = line
            .span(..self.view.cursor_column)
            .display_width(config.tab_width);
        assert!(self.view.column_offset <= column);

        (
            column - self.view.column_offset,
            self.view.cursor_row - self.view.row_offset,
        )
    }

    pub fn width(&self) -> usize {
        self.view.width
    }

    pub fn height(&self) -> usize {
        self.view.height
    }

    pub fn is_modified(&self) -> bool {
        self.modified
    }

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

    fn display_line(
        &self,
        config: &Config,
        term: &mut Term,
        row: usize,
        line: &Line,
    ) -> Result<(), Error> {
        let mut pos = 0;
        term.set_cursor_position(row, self.view.offset_x)
            .map_err(Error::TerminalError)?;

        let span = line.skip_to_width(self.view.column_offset, config.tab_width);
        let long_line = span.display_width(config.tab_width) > self.view.width;

        for &ch in span.iter() {
            if pos >= self.view.width {
                break;
            }

            if ch == '\t' {
                let old_pos = pos;
                let new_pos = (pos + config.tab_width) & !(config.tab_width - 1);
                pos = new_pos;

                for i in old_pos..new_pos {
                    if i >= self.view.width {
                        break;
                    }
                    if i == old_pos {
                        term.set_foreground(Color::Blue)
                            .map_err(Error::TerminalError)?;
                        term.write_char('>').map_err(Error::TerminalFmtError)?;
                        term.set_foreground(Color::Default)
                            .map_err(Error::TerminalError)?;
                    } else {
                        term.write_char(' ').map_err(Error::TerminalFmtError)?;
                    }
                }
            } else {
                write!(term, "{}", ch).map_err(Error::TerminalFmtError)?;
                pos += ch.width().unwrap_or(1);
            }
        }

        if long_line {
            term.set_cursor_position(row, self.view.width + self.view.offset_x)
                .map_err(Error::TerminalError)?;
            term.set_foreground(Color::Black)
                .map_err(Error::TerminalError)?;
            term.set_background(Color::White)
                .map_err(Error::TerminalError)?;
            term.write_char('>').map_err(Error::TerminalFmtError)?;
            term.set_foreground(Color::Default)
                .map_err(Error::TerminalError)?;
            term.set_background(Color::Default)
                .map_err(Error::TerminalError)?;
        }

        Ok(())
    }

    pub fn display(&mut self, config: &Config, term: &mut Term) -> Result<(), Error> {
        match self.mode {
            Mode::Normal => {
                term.set_cursor_style(CursorStyle::Default)
                    .map_err(Error::TerminalError)?;
            }
            Mode::Insert => {
                term.set_cursor_style(CursorStyle::Line)
                    .map_err(Error::TerminalError)?;
            }
        }

        for (row, line) in self
            .lines
            .iter()
            .skip(self.view.row_offset)
            .take(self.view.height)
            .enumerate()
        {
            self.display_line(config, term, row, line)?;
        }

        Ok(())
    }

    pub fn set_terminal_cursor(&mut self, config: &Config, term: &mut Term) -> Result<(), Error> {
        let (x, y) = self.display_cursor(config);
        if self.mode_dirty {
            match self.mode {
                Mode::Normal => term
                    .set_cursor_style(CursorStyle::Default)
                    .map_err(Error::TerminalError)?,
                Mode::Insert => term
                    .set_cursor_style(CursorStyle::Line)
                    .map_err(Error::TerminalError)?,
            }
        }
        term.set_cursor_position(y, x + self.view.offset_x)
            .map_err(Error::TerminalError)?;
        self.mode_dirty = false;

        Ok(())
    }

    pub fn newline_before(&mut self) {
        self.modified = true;
        self.lines.insert(self.view.cursor_row, Line::new());
    }

    pub fn newline_after(&mut self, break_line: bool) {
        if self.lines.is_empty() {
            self.lines.push(Line::new());
            return;
        }
        self.modified = true;

        let newline = if break_line {
            self.lines[self.view.cursor_row].split_off(self.view.cursor_column)
        } else {
            Line::new()
        };
        self.lines.insert(self.view.cursor_row + 1, newline);
    }

    pub fn insert(&mut self, config: &Config, ch: char) {
        if self.lines.is_empty() {
            assert_eq!(self.view.cursor_row, 0);
            self.lines.push(Line::new());
        }

        let line = &mut self.lines[self.view.cursor_row];
        line.insert(self.view.cursor_column, ch);
        self.move_cursor(config, 1, 0);
        self.modified = true;
    }

    pub fn erase_backward(&mut self, config: &Config) {
        if self.lines.is_empty() {
            return;
        }

        if self.view.cursor_column == 0 {
            if self.view.cursor_row != 0 {
                let line = self.lines.remove(self.view.cursor_row);
                let prev_line = &mut self.lines[self.view.cursor_row - 1];

                let len = prev_line.len();
                prev_line.extend(line);
                self.set_position(config, len, self.view.cursor_row - 1);
                self.modified = true;
            }
            return;
        }

        let line = &mut self.lines[self.view.cursor_row];
        line.remove(self.view.cursor_column - 1);
        self.move_cursor(config, -1, 0);
        self.modified = true;
    }

    pub fn erase_forward(&mut self) {
        if self.lines.is_empty() {
            return;
        }

        let line = &mut self.lines[self.view.cursor_row];
        if self.view.cursor_column == line.len() {
            return;
        }
        line.remove(self.view.cursor_column);
        self.dirty = true;
        self.modified = true;
    }

    pub fn kill_line(&mut self, config: &Config) {
        if self.lines.is_empty() {
            return;
        }

        self.lines.remove(self.view.cursor_row);
        self.move_cursor(config, 0, 1);
        self.modified = true;
    }

    pub fn number_width(&mut self) -> usize {
        if self.lines.is_empty() {
            1
        } else {
            self.lines.len().ilog10() as usize + 1
        }
    }
}
