#![allow(non_upper_case_globals)]

use core::{
    ffi::{c_char, c_int},
    fmt,
    ops::{Deref, DerefMut},
    ptr::{null_mut, NonNull},
};

use alloc::{boxed::Box, collections::btree_set::BTreeSet, vec::Vec};
use bitflags::bitflags;
use yggdrasil_rt::{
    io::{FileMode, OpenOptions, RawFd, SeekFrom},
    path::Path,
    sync::mutex::{Mutex, RawMutex},
};

use crate::{
    error::{EResult, TryFromExt},
    headers::{
        errno::Errno,
        stdio::{BUFSIZ, UNGETC_MAX, _IOFBF, _IOLBF, _IONBF},
    },
};

use super::{
    buffer::{FileWriter, FullBufferedWriter, LineBufferedWriter, ReadBuffer, UnbufferedWriter},
    raw::RawFile,
    AsRawFd, BufRead, FromRawFd, Read, Seek, Write,
};

macro locked_op($self:expr, $op:expr) {{
    #![allow(unused_unsafe)]
    $self.lock.lock();
    let result = unsafe { $op };
    unsafe { $self.lock.release() };
    result
}}

bitflags! {
    pub struct FileFlags: u32 {
        const READ = 1 << 0;
        const WRITE = 1 << 1;
        const EOF = 1 << 2;
        const ERROR = 1 << 3;
        const BUILTIN = 1 << 16;
    }
}

pub trait FileOpenSource {
    fn open_with(self, open: OpenOptions) -> EResult<FILE>;
}

pub enum BufferingMode {
    Full,
    Line,
    None,
}

#[derive(Debug, PartialEq, Clone, Copy)]
pub enum Direction {
    Read,
    Write,
}

pub enum FileBacking {
    File(ManagedFile),
}

pub struct ManagedFile {
    inner: RawFile,
    reference: bool,
}

pub struct FILE {
    lock: RawMutex,

    inner: FileBacking,
    read_buffer: Option<ReadBuffer<'static>>,
    write_buffer: Box<dyn FileWriter>,

    flags: FileFlags,

    ungetc: Vec<u8>,
    // NOTE:
    // man setvbuf(3):
    //  The setvbuf() function may be used only after opening a stream and
    //  before any other  operations have been performed on it.
    last_operation: Option<Direction>,
}

struct FileSet {
    files: BTreeSet<NonNull<FILE>>,
}

// ManagedFile

impl ManagedFile {
    pub fn open_at<P: AsRef<Path>>(
        at: Option<RawFd>,
        pathname: P,
        opts: OpenOptions,
        mode: FileMode,
    ) -> EResult<Self> {
        let inner = RawFile::open_at(at, pathname, opts, mode)?;
        EResult::Ok(Self {
            inner,
            reference: false,
        })
    }

    pub unsafe fn close(&mut self) -> EResult<()> {
        if self.reference {
            unreachable!("Cannot close a reference ManagedFile")
        }
        self.reference = true;
        RawFile::close(&mut self.inner)
    }
}

impl Deref for ManagedFile {
    type Target = RawFile;

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

impl DerefMut for ManagedFile {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.inner
    }
}

impl FromRawFd for ManagedFile {
    unsafe fn from_raw_fd(fd: RawFd) -> Self {
        Self::from(RawFile::from_raw_fd(fd))
    }
}

impl From<RawFile> for ManagedFile {
    fn from(inner: RawFile) -> Self {
        Self {
            inner,
            reference: false,
        }
    }
}

impl Drop for ManagedFile {
    fn drop(&mut self) {
        if !self.reference {
            unsafe { self.close().ok() };
        }
    }
}

// FileBacking

impl FileBacking {
    pub unsafe fn make_ref(&self) -> Self {
        match self {
            Self::File(file) => Self::File(ManagedFile {
                inner: RawFile::from_raw_fd(file.as_raw_fd()),
                reference: true,
            }),
        }
    }

    pub unsafe fn close(&mut self) -> EResult<()> {
        match self {
            Self::File(file) => file.close(),
        }
    }
}

impl Write for FileBacking {
    fn write(&mut self, data: &[u8]) -> EResult<usize> {
        match self {
            Self::File(file) => file.write(data),
        }
    }

    fn flush(&mut self) -> EResult<()> {
        match self {
            Self::File(file) => file.flush(),
        }
    }
}

impl Read for FileBacking {
    fn read(&mut self, data: &mut [u8]) -> EResult<usize> {
        match self {
            Self::File(file) => file.read(data),
        }
    }
}

impl Seek for FileBacking {
    fn seek(&mut self, pos: SeekFrom) -> EResult<u64> {
        match self {
            Self::File(file) => file.seek(pos),
        }
    }
}

// FILE

impl FILE {
    unsafe fn from_backing(
        backing: FileBacking,
        flags: FileFlags,
        buffering: BufferingMode,
    ) -> Self {
        let write_buffer = backing.make_ref();
        let write_buffer: Box<dyn FileWriter> = match buffering {
            BufferingMode::None => Box::new(UnbufferedWriter::new(write_buffer)),
            BufferingMode::Line => {
                Box::new(LineBufferedWriter::with_capacity(write_buffer, BUFSIZ))
            }
            BufferingMode::Full => {
                Box::new(FullBufferedWriter::with_capacity(write_buffer, BUFSIZ))
            }
        };

        Self {
            lock: RawMutex::new(),

            inner: backing,
            write_buffer,
            read_buffer: None,

            flags,

            ungetc: Vec::new(),
            last_operation: None,
        }
    }

    unsafe fn from_raw_file(file: RawFile, flags: FileFlags) -> Self {
        Self::from_managed_file(file.into(), flags)
    }

    unsafe fn from_managed_file(file: ManagedFile, flags: FileFlags) -> Self {
        assert!(!flags.contains(FileFlags::BUILTIN));
        let backing = FileBacking::File(file);
        Self::from_backing(backing, flags, BufferingMode::Full)
    }

    unsafe fn new_builtin(fd: RawFd, flags: FileFlags, buffering: BufferingMode) -> Self {
        let inner = FileBacking::File(ManagedFile::from_raw_fd(fd));
        Self::from_backing(inner, flags, buffering)
    }

    pub unsafe fn close(mut ptr: NonNull<Self>) -> EResult<()> {
        let file = ptr.as_mut();

        if file.flags.contains(FileFlags::WRITE) {
            file.flush()?;
        }

        if !file.flags.contains(FileFlags::BUILTIN) {
            let mut this = Box::from_raw(file);
            let result = FileBacking::close(&mut this.inner);
            drop(this);
            result
        } else {
            EResult::Ok(())
        }
    }

    pub fn fd(&self) -> Option<RawFd> {
        match &self.inner {
            FileBacking::File(file) => Some(file.as_raw_fd()),
        }
    }

    pub fn is_eof(&self) -> bool {
        self.flags.contains(FileFlags::EOF)
    }

    pub fn is_error(&self) -> bool {
        self.flags.contains(FileFlags::ERROR)
    }

    pub fn clear_error(&mut self) {
        self.flags.remove(FileFlags::ERROR);
    }

    pub fn try_lock(&self) -> bool {
        self.lock.try_lock()
    }

    pub unsafe fn lock(&mut self) {
        self.lock.lock();
    }

    pub unsafe fn unlock(&mut self) {
        self.lock.release();
    }

    unsafe fn reading_locked(&self) -> bool {
        !self.flags.contains(FileFlags::WRITE) || self.last_operation == Some(Direction::Read)
    }

    pub fn reading(&self) -> bool {
        locked_op!(self, self.reading_locked())
    }

    unsafe fn writing_locked(&self) -> bool {
        !self.flags.contains(FileFlags::WRITE) || self.last_operation == Some(Direction::Write)
    }

    pub fn writing(&self) -> bool {
        locked_op!(self, self.writing_locked())
    }

    pub fn is_readable(&self) -> bool {
        self.flags.contains(FileFlags::READ)
    }

    pub fn is_writeable(&self) -> bool {
        self.flags.contains(FileFlags::WRITE)
    }

    unsafe fn pending_bytes_locked(&self) -> usize {
        self.write_buffer.pending_bytes()
    }

    pub fn pending_bytes(&self) -> usize {
        locked_op!(self, self.pending_bytes_locked())
    }

    unsafe fn buffer_size_locked(&self) -> usize {
        self.write_buffer.capacity()
    }

    pub fn buffer_size(&self) -> usize {
        locked_op!(self, self.buffer_size_locked())
    }

    unsafe fn is_line_buffered_locked(&self) -> bool {
        self.write_buffer.is_line_buffered()
    }

    pub fn is_line_buffered(&self) -> bool {
        locked_op!(self, self.is_line_buffered_locked())
    }

    pub fn set_error(&mut self) {
        locked_op!(self, {
            self.flags |= FileFlags::ERROR;
        });
    }

    // pub fn reset(&mut self) {
    //     if let Some(read_buffer) = self.read_buffer.as_mut() {
    //         read_buffer.reset();
    //     }
    //     self.write_buffer.reset();
    //     self.flags &= !(FileFlags::ERROR | FileFlags::EOF);
    //     self.ungetc.clear();
    //     self.last_operation = None;
    // }

    pub fn setvbuf(&mut self, mode: c_int, buffer: *mut c_char, size: usize) -> EResult<()> {
        locked_op!(self, self.setvbuf_unlocked(mode, buffer, size))
    }

    pub fn ungetc(&mut self, ch: u8) -> EResult<()> {
        locked_op!(self, self.ungetc_unlocked(ch))
    }

    pub unsafe fn read_unlocked(&mut self, data: &mut [u8]) -> EResult<usize> {
        if !self.flags.contains(FileFlags::READ) {
            self.flags |= FileFlags::ERROR;
            return EResult::Err(Errno::EBADF);
        }

        self.set_direction(Direction::Read)?;

        if !self.ungetc.is_empty() {
            let amount = core::cmp::min(self.ungetc.len(), data.len());
            data[..amount].copy_from_slice(&self.ungetc[..amount]);
            self.ungetc.drain(..amount);
            return EResult::Ok(amount);
        }

        if self.read_buffer.is_some() {
            let buf = self.fill_buf()?;
            let len = core::cmp::min(data.len(), buf.len());
            data[..len].copy_from_slice(&buf[..len]);
            self.consume(len);
            EResult::Ok(len)
        } else {
            match self.inner.read(data) {
                EResult::Ok(0) => {
                    self.flags |= FileFlags::EOF;
                    EResult::Ok(0)
                }
                EResult::Ok(len) => EResult::Ok(len),
                EResult::Err(err) => {
                    self.flags |= FileFlags::ERROR;
                    EResult::Err(err)
                }
            }
        }
    }

    pub unsafe fn write_unlocked(&mut self, data: &[u8]) -> EResult<usize> {
        if !self.flags.contains(FileFlags::WRITE) {
            self.flags |= FileFlags::ERROR;
            return EResult::Err(Errno::EBADF);
        }

        self.set_direction(Direction::Write)?;

        match self.write_buffer.write(data) {
            EResult::Ok(len) => EResult::Ok(len),
            EResult::Err(err) => {
                self.flags |= FileFlags::ERROR;
                EResult::Err(err)
            }
        }
    }

    unsafe fn flush_unlocked(&mut self) -> EResult<()> {
        self.ungetc.clear();
        self.last_operation = None;

        match self.write_buffer.flush() {
            EResult::Ok(()) => EResult::Ok(()),
            EResult::Err(err) => {
                self.flags |= FileFlags::ERROR;
                EResult::Err(err)
            }
        }
    }

    unsafe fn seek_unlocked(&mut self, off: SeekFrom) -> EResult<u64> {
        self.flush_unlocked()?;

        match self.inner.seek(off) {
            EResult::Ok(pos) => {
                self.flags &= !FileFlags::EOF;
                EResult::Ok(pos)
            }
            EResult::Err(err) => {
                self.flags |= FileFlags::ERROR;
                EResult::Err(err)
            }
        }
    }

    unsafe fn ungetc_unlocked(&mut self, ch: u8) -> EResult<()> {
        // ungetc() for write doesn't make any sense
        self.set_direction(Direction::Read)?;
        if self.ungetc.len() == UNGETC_MAX {
            return EResult::Err(Errno::ENOMEM);
        }
        self.ungetc.push(ch);
        EResult::Ok(())
    }

    unsafe fn setvbuf_unlocked(
        &mut self,
        mode: c_int,
        buffer: *mut c_char,
        capacity: usize,
    ) -> EResult<()> {
        let mode = BufferingMode::e_try_from(mode)?;

        if self.last_operation.is_some() {
            self.flags |= FileFlags::ERROR;
            return EResult::Err(Errno::EINVAL);
        }

        self.ungetc.clear();

        let mut read_capacity = capacity;
        let mut write_capacity = capacity;

        // Set read buffer
        match mode {
            BufferingMode::None => self.read_buffer = None,
            BufferingMode::Line | BufferingMode::Full => {
                if read_capacity == 0 {
                    read_capacity = BUFSIZ;
                }

                if buffer.is_null() {
                    self.read_buffer = Some(ReadBuffer::owned(read_capacity));
                } else {
                    let slice =
                        unsafe { core::slice::from_raw_parts_mut(buffer.cast(), read_capacity) };
                    self.read_buffer = Some(ReadBuffer::borrowed(slice));
                }
            }
        }

        // Set write buffer
        let file_ref = unsafe { self.inner.make_ref() };
        match mode {
            BufferingMode::None => self.write_buffer = Box::new(UnbufferedWriter::new(file_ref)),
            BufferingMode::Line => {
                if write_capacity == 0 {
                    write_capacity = BUFSIZ;
                }
                self.write_buffer =
                    Box::new(LineBufferedWriter::with_capacity(file_ref, write_capacity));
            }
            BufferingMode::Full => {
                if write_capacity == 0 {
                    write_capacity = BUFSIZ;
                }
                self.write_buffer =
                    Box::new(FullBufferedWriter::with_capacity(file_ref, write_capacity));
            }
        }

        EResult::Ok(())
    }

    unsafe fn set_direction(&mut self, direction: Direction) -> EResult<()> {
        match self.last_operation.replace(direction) {
            Some(dir) if dir != direction => {
                self.flags |= FileFlags::ERROR;
                EResult::Err(Errno::EINVAL)
            }
            _ => EResult::Ok(()),
        }
    }
}

impl Read for FILE {
    fn read(&mut self, data: &mut [u8]) -> EResult<usize> {
        locked_op!(self, self.read_unlocked(data))
    }
}

impl Write for FILE {
    fn write(&mut self, data: &[u8]) -> EResult<usize> {
        locked_op!(self, self.write_unlocked(data))
    }

    fn flush(&mut self) -> EResult<()> {
        locked_op!(self, self.flush_unlocked())
    }
}

impl Seek for FILE {
    fn seek(&mut self, pos: SeekFrom) -> EResult<u64> {
        locked_op!(self, self.seek_unlocked(pos))
    }
}

impl BufRead for FILE {
    fn fill_buf(&mut self) -> EResult<&[u8]> {
        let buffer = self.read_buffer.as_mut().unwrap();
        match buffer.fill_from(&mut self.inner) {
            EResult::Ok(slice) => {
                if slice.is_empty() {
                    self.flags |= FileFlags::EOF;
                }
                EResult::Ok(slice)
            }
            EResult::Err(err) => {
                self.flags |= FileFlags::ERROR;
                EResult::Err(err)
            }
        }
    }

    fn consume(&mut self, amount: usize) {
        let buffer = self.read_buffer.as_mut().unwrap();
        buffer.consume(amount);
    }
}

impl FileOpenSource for &Path {
    fn open_with(self, opts: OpenOptions) -> EResult<FILE> {
        let f = ManagedFile::open_at(None, self, opts, FileMode::default_file())?;
        let mut flags = FileFlags::empty();
        if opts.contains(OpenOptions::READ) {
            flags |= FileFlags::READ;
        }
        if opts.contains(OpenOptions::WRITE) {
            flags |= FileFlags::WRITE;
        }
        EResult::Ok(unsafe { FILE::from_managed_file(f, flags) })
    }
}

impl FileOpenSource for RawFd {
    fn open_with(self, opts: OpenOptions) -> EResult<FILE> {
        let f = unsafe { RawFile::from_raw_fd(self) };
        let mut flags = FileFlags::empty();
        if opts.contains(OpenOptions::READ) {
            flags |= FileFlags::READ;
        }
        if opts.contains(OpenOptions::WRITE) {
            flags |= FileFlags::WRITE;
        }
        EResult::Ok(unsafe { FILE::from_raw_file(f, flags) })
    }
}

impl fmt::Write for FILE {
    fn write_str(&mut self, s: &str) -> fmt::Result {
        self.write_all(s.as_bytes())
            .into_result(|_| fmt::Error, true)
    }
}

impl TryFromExt<c_int> for BufferingMode {
    fn e_try_from(value: c_int) -> EResult<Self> {
        match value {
            _IOFBF => EResult::Ok(Self::Full),
            _IOLBF => EResult::Ok(Self::Line),
            _IONBF => EResult::Ok(Self::None),
            _ => EResult::Err(Errno::EINVAL),
        }
    }
}

impl FileSet {
    const fn new() -> Self {
        Self {
            files: BTreeSet::new(),
        }
    }

    fn insert(&mut self, file: NonNull<FILE>) {
        self.files.insert(file);
    }

    fn remove(&mut self, file: &NonNull<FILE>) -> bool {
        self.files.remove(file)
    }

    fn cleanup(&mut self) {
        while let Some(file) = self.files.pop_first() {
            unsafe { FILE::close(file) }.ok();
        }
    }
}

unsafe impl Sync for FileSet {}

#[no_mangle]
pub static mut stdout: *mut FILE = null_mut();
#[no_mangle]
pub static mut stderr: *mut FILE = null_mut();
#[no_mangle]
pub static mut stdin: *mut FILE = null_mut();

static OPEN_FILES: Mutex<FileSet> = Mutex::new(FileSet::new());

pub fn deregister_file(file: NonNull<FILE>) -> bool {
    OPEN_FILES.lock().remove(&file)
}

pub fn register_file(file: NonNull<FILE>) {
    OPEN_FILES.lock().insert(file);
}

pub unsafe fn init() {
    let stdin_ = Box::leak(Box::new(FILE::new_builtin(
        RawFd::STDIN,
        FileFlags::READ,
        BufferingMode::Line,
    )));
    let stdout_ = Box::leak(Box::new(FILE::new_builtin(
        RawFd::STDOUT,
        FileFlags::WRITE,
        BufferingMode::Line,
    )));
    let stderr_ = Box::leak(Box::new(FILE::new_builtin(
        RawFd::STDERR,
        FileFlags::WRITE,
        BufferingMode::None,
    )));

    stdin = stdin_;
    stdout = stdout_;
    stderr = stderr_;

    let mut open_files = OPEN_FILES.lock();
    open_files.insert(NonNull::from(stdin_));
    open_files.insert(NonNull::from(stdout_));
    open_files.insert(NonNull::from(stderr_));
}

pub unsafe fn cleanup() {
    OPEN_FILES.lock().cleanup();
}
