use std::fs::File;
use std::io::{BufReader, BufWriter};
use std::io::{Read, Write};
use std::io::{ErrorKind, Seek, SeekFrom};

enum AccessMode {
    Read(BufReader<File>),
    Write(BufWriter<File>),
    None,
}

impl AccessMode {
    pub fn unwrap(self) -> File {
        match self {
            Self::Read(reader) => reader.into_inner(),
            Self::Write(writer) => writer.into_inner().unwrap(),
            Self::None => unreachable!(),
        }
    }
}

impl Default for AccessMode {
    fn default() -> Self {
        Self::None
    }
}

pub struct BufferedFile {
    file: AccessMode,
}

impl BufferedFile {
    pub fn new(file: File) -> Self {
        Self {
            file: AccessMode::Read(BufReader::new(file)),
        }
    }

    pub fn read_byte(&mut self) -> u8 {
        let mut buffer = [0u8; 1];

        let read_result = match &mut self.file {
            AccessMode::Read(reader) => reader.read_exact(&mut buffer),
            AccessMode::Write(writer) => {
                let address = writer.stream_position().unwrap();
                let file = std::mem::take(&mut self.file).unwrap();
                let mut reader = BufReader::new(file);
                reader.seek(SeekFrom::Start(address)).unwrap();
                let read_result = reader.read_exact(&mut buffer);
                self.file = AccessMode::Read(reader);
                read_result
            }
            AccessMode::None => unreachable!(),
        };

        match read_result {
            Ok(_) => buffer[0],
            Err(error) => match error.kind() {
                ErrorKind::UnexpectedEof => 0,
                _ => panic!("{error:?}"),
            }
        }
    }

    pub fn write_byte(&mut self, byte: u8) {
        let mut buffer = [byte; 1];

        let write_result = match &mut self.file {
            AccessMode::Write(writer) => writer.write_all(&mut buffer),
            AccessMode::Read(reader) => {
                let address = reader.stream_position().unwrap();
                let file = std::mem::take(&mut self.file).unwrap();
                let mut writer = BufWriter::new(file);
                writer.seek(SeekFrom::Start(address)).unwrap();
                let write_result = writer.write_all(&mut buffer);
                self.file = AccessMode::Write(writer);
                write_result
            }
            AccessMode::None => unreachable!(),
        };

        write_result.unwrap();
    }

    pub fn pointer(&mut self) -> u32 {
        let position = match &mut self.file {
            AccessMode::Read(reader) => reader.stream_position(),
            AccessMode::Write(writer) => writer.stream_position(),
            AccessMode::None => unreachable!(),
        };
        u32::try_from(position.unwrap()).unwrap_or(u32::MAX)
    }

    pub fn set_pointer(&mut self, pointer: u32) {
        let position = SeekFrom::Start(pointer as u64);
        match &mut self.file {
            AccessMode::Read(reader) => reader.seek(position).unwrap(),
            AccessMode::Write(writer) => writer.seek(position).unwrap(),
            AccessMode::None => unreachable!(),
        };
    }

    pub fn length(&mut self) -> u32 {
        let length = match &mut self.file {
            AccessMode::Read(reader) => reader.stream_len(),
            AccessMode::Write(writer) => writer.stream_len(),
            AccessMode::None => unreachable!(),
        };
        u32::try_from(length.unwrap()).unwrap_or(u32::MAX)
    }

    pub fn set_length(&mut self, length: u32) {
        match &mut self.file {
            AccessMode::Read(_) => {
                let file = std::mem::take(&mut self.file).unwrap();
                file.set_len(length as u64).unwrap();
                self.file = AccessMode::Read(BufReader::new(file));
            }
            AccessMode::Write(_) => {
                let file = std::mem::take(&mut self.file).unwrap();
                file.set_len(length as u64).unwrap();
                self.file = AccessMode::Read(BufReader::new(file));
            }
            AccessMode::None => unreachable!(),
        };
    }
}