Replace file data in a UDF image
use crate::c_includes;
pub use c_includes::UDF_BLOCK_SIZE;
use std::ffi::CString;
use std::fs::File;
use std::io::{Read, Seek, SeekFrom};
use std::marker::PhantomData;
use std::os::raw::{c_int, c_void};
use std::path::Path;

fn str_to_mutf8(input: &str) -> CString {
    let mut result = Vec::with_capacity(input.len() + 1);
    for &byte in input.as_bytes() {
        if byte == 0 {
            // MUTF-8 uses an over-long encoding to represent null characters to prevent a null byte
            // from appearing in the string.
            result.push(0xC0);
            result.push(0x80);
        } else {
            result.push(byte);
        }
    }
    return CString::new(result).unwrap();
}

#[repr(C)]
struct InputAdapter<R: Read + Seek> {
    funcs: c_includes::udfread_block_input,
    reader: R,
}

impl<R: Read + Seek> InputAdapter<R> {
    fn new(reader: R) -> *mut c_includes::udfread_block_input {
        let funcs = c_includes::udfread_block_input {
            size: Some(Self::c_size_in_blocks),
            read: Some(Self::c_read_blocks),
            close: Some(Self::c_free_reader),
        };
        Box::into_raw(Box::<Self>::new(Self { funcs, reader }))
            as *mut c_includes::udfread_block_input
    }

    unsafe extern "C" fn c_size_in_blocks(ptr: *mut c_includes::udfread_block_input) -> u32 {
        (*(ptr as *mut Self)).size_in_blocks()
    }

    fn size_in_blocks(&mut self) -> u32 {
        self.reader
            .seek(SeekFrom::End(0))
            .map(|val| val / u64::from(UDF_BLOCK_SIZE))
            .unwrap_or_else(|err| {
                eprintln!("Error getting file size: {}", err);
                0
            })
            .try_into()
            .unwrap_or(0)
    }

    unsafe extern "C" fn c_read_blocks(
        ptr: *mut c_includes::udfread_block_input,
        block_offset: u32,
        buf: *mut c_void,
        num_blocks: u32,
        _flags: c_int,
    ) -> c_int {
        (*(ptr as *mut Self)).read_blocks(block_offset, buf as *mut u8, num_blocks)
    }

    fn read_blocks(&mut self, block_offset: u32, buf: *mut u8, num_blocks: u32) -> c_int {
        // if num_blocks > c_int::MAX as u32 || num_blocks > usize::MAX / UDF_BLOCK_SIZE {
        //     eprintln!("Too many blocks requested.");
        //     return -1;
        // }

        let result: std::io::Result<usize> = (|| {
            let byte_offset = u64::from(block_offset) * u64::from(UDF_BLOCK_SIZE);
            // TODO: Ensure this doesn't overflow
            let num_bytes = num_blocks as usize * UDF_BLOCK_SIZE as usize;

            // TODO: Use ReadBuf once it is stable to avoid needing to zero the buffer.
            unsafe { std::ptr::write_bytes(buf, 0, num_bytes) };
            let mut buffer = unsafe { std::slice::from_raw_parts_mut(buf, num_bytes) };

            let mut total_bytes_read = 0usize;
            self.reader.seek(SeekFrom::Start(byte_offset))?;
            loop {
                let bytes_read = self.reader.read(buffer)?;
                if bytes_read == 0 {
                    break;
                } else if bytes_read < buffer.len() {
                    total_bytes_read += bytes_read;
                    buffer = &mut buffer[bytes_read..];
                } else {
                    // A broken Read implementation could hypothetically return bytes_read >
                    // buffer.len(). Adding buffer.len() rather than bytes_read here ensures any
                    // such brokenness isn't propegated to udfread.
                    total_bytes_read += buffer.len();
                    break;
                }
            }
            Ok(total_bytes_read)
        })();

        match result {
            // TODO: Ensure this doesn't overflow
            Ok(read) => (read / UDF_BLOCK_SIZE as usize) as c_int,
            Err(err) => {
                eprintln!("Error reading file: {}", err);
                -1
            }
        }
    }

    unsafe extern "C" fn c_free_reader(ptr: *mut c_includes::udfread_block_input) -> c_int {
        Box::from_raw(ptr as *mut Self);
        0
    }
}

pub struct UdfRead<R: Read + Seek = File> {
    udfread: *mut c_includes::udfread,
    phantom: PhantomData<InputAdapter<R>>,
}

impl UdfRead<File> {
    /// Open a UDF image from a file.
    pub fn open_image_file(path: impl AsRef<Path>) -> Option<Self> {
        let file = match File::open(path) {
            Ok(val) => Some(val),
            Err(err) => {
                eprintln!("Error opening file: {}", err);
                None
            }
        }?;

        Self::open_image_reader(file)
    }
}

impl<R: Read + Seek> UdfRead<R> {
    /// Open a UDF image from a generic reader.
    ///
    /// Note: The reader’s underlying stream can be shared (e.g., by passing a &File) given the
    /// following caveats:
    ///
    /// 1. Any call to a UdfRead method (or a method on a returned object) may change the seek
    ///    position of the stream.
    /// 2. While such a method is in progress, no other code should change the seek positon of the
    ///    stream. (This is trivially the case if the stream is only used from one thread.)
    pub fn open_image_reader(reader: R) -> Option<UdfRead<R>> {
        let input_adapter = InputAdapter::new(reader);

        let udfread = unsafe { c_includes::udfread_init() };

        if udfread.is_null() {
            return None;
        }

        let open_result = unsafe { c_includes::udfread_open_input(udfread, input_adapter) };

        if open_result < 0 {
            eprintln!("udfread_open_input failed");
            unsafe { c_includes::udfread_close(udfread) };
            return None;
        }

        Some(UdfRead {
            udfread,
            phantom: PhantomData,
        })
    }

    pub fn open_file(&self, path: &str) -> Option<UdfFile> {
        let mutf8_path = str_to_mutf8(path);
        let udffile = unsafe { c_includes::udfread_file_open(self.udfread, mutf8_path.as_ptr()) };

        if udffile.is_null() {
            return None;
        }

        Some(UdfFile {
            udffile,
            phantom: PhantomData,
        })
    }
}

impl<R: Read + Seek> Drop for UdfRead<R> {
    fn drop(&mut self) {
        unsafe { c_includes::udfread_close(self.udfread) };
    }
}

pub struct UdfFile<'a> {
    udffile: *mut c_includes::UDFFILE,
    phantom: PhantomData<&'a UdfRead>,
}

impl<'a> UdfFile<'a> {
    /// Returns the size of the file in bytes
    pub fn size(&self) -> Option<u64> {
        let result = unsafe { c_includes::udfread_file_size(self.udffile) };
        
        if result < 0 {
            None
        } else {
            Some(result as u64)
        }
    }

    /// Translates file-relative block to image-relative block
    /// 
    /// Returns None if the block is beyond the end of the file or points to a sparse region.
    pub fn block_lba(&self, block: u32) -> Option<u32> {
        let result = unsafe { c_includes::udfread_file_lba(self.udffile, block) };

        if result == 0 {
            None
        } else {
            Some(result)
        }
    }
}