use std::fs::File;
use std::io::{self, BufReader, Cursor, Read, Seek, SeekFrom};
use std::path::Path;
use std::thread::sleep;

use byteorder::{LittleEndian, ReadBytesExt};

fn main() -> io::Result<()> {
    // Entry point of the PDB parsing program
    let args: Vec<String> = std::env::args().collect();
    if args.len() != 2 {
        eprintln!("Usage: {} <path_to_pdb_file>", args[0]);
        std::process::exit(1);
    }

    let pdb_path = &args[1];

    println!("Parsing PDB file: {}", pdb_path);

    // Open the PDB file
    let file = match File::open(pdb_path) {
        Ok(f) => f,
        Err(e) => {
            eprintln!("Failed to open file {}: {}", pdb_path, e);
            std::process::exit(1);
        }
    };
    let mut reader = BufReader::new(file);

    // Parse the PDB file
    let pdb = match PdbFile::parse(&mut reader) {
        Ok(pdb) => pdb,
        Err(e) => {
            eprintln!("Failed to parse PDB file: {}", e);
            std::process::exit(1);
        }
    };

    // Output the parsed information
    pdb.print_info();

    Ok(())
}

/// Represents a PDB file
struct PdbFile {
    page_size: u32,
    fpm_page: u32,
    num_pages: u32,
    root_stream_size: u32,
    root_stream_pages: Vec<u32>,
    streams: Vec<PdbStream>,
}

/// Represents a stream within the PDB file
struct PdbStream {
    size: u32,
    pages: Vec<u32>,
    data: Vec<u8>,
}

impl PdbFile {
    /// Parses the PDB file from the given reader
    fn parse(reader: &mut BufReader<File>) -> io::Result<Self> {
        // Read and parse the SuperBlock (first 64 bytes)
        let mut superblock = [0u8; 64];
        reader.read_exact(&mut superblock)?;

        // Verify the PDB signature
        let signature = &superblock[0..32];
        let expected_signature = b"Microsoft C/C++ MSF 7.00";
        if &signature[0..expected_signature.len()] != expected_signature {
            return Err(io::Error::new(
                io::ErrorKind::InvalidData,
                "Invalid PDB signature.",
            ));
        }

        // Parse SuperBlock fields
        let mut cursor = Cursor::new(&superblock);

        // Skip the signature (32 bytes)
        cursor.seek(SeekFrom::Start(32))?;

        // Read PDB header fields
        let page_size = cursor.read_u32::<LittleEndian>()?;
        let fpm_page = cursor.read_u32::<LittleEndian>()?;
        let num_pages = cursor.read_u32::<LittleEndian>()?;
        let root_stream_size = cursor.read_u32::<LittleEndian>()?;
        let _reserved = cursor.read_u32::<LittleEndian>()?;

        // Read Root Directory Index entries from SuperBlock
        let mut rdi_entries = Vec::new();
        while cursor.position() < 64 {
            let page_number = cursor.read_u32::<LittleEndian>()?;
            rdi_entries.push(page_number);
        }

        // Calculate the number of pages the directory stream spans
        let directory_size = root_stream_size;
        let directory_pages = Self::calculate_page_count(directory_size, page_size);

        // Calculate the number of entries needed in the Root Directory Index
        let entries_per_page = page_size / 4; // Number of u32s per page
        let num_rdi_entries =
            Self::calculate_page_count(directory_pages * 4, page_size);

        // Read the full Root Directory Index
        let root_dir_index_pages = if num_rdi_entries <= rdi_entries.len() as u32 {
            // Root Directory Index entries fit in SuperBlock
            rdi_entries[0..num_rdi_entries as usize].to_vec()
        } else {
            // Root Directory Index spans multiple levels
            Self::read_page_number_array(
                reader,
                page_size,
                &rdi_entries,
                num_rdi_entries,
            )?
        };

        // Read the directory pages list from the Root Directory Index Pages
        let directory_pages_list = Self::read_page_number_array(
            reader,
            page_size,
            &root_dir_index_pages,
            directory_pages,
        )?;

        // Read the root directory stream data
        let root_stream_data = Self::read_stream(
            reader,
            page_size,
            &directory_pages_list,
            root_stream_size,
        )?;

        // Parse the root directory stream
        let (streams, stream_pages) =
            Self::parse_root_stream(&root_stream_data, page_size, reader)?;

        // Load the data for each stream
        let mut pdb_streams = Vec::new();
        for (i, (size, pages)) in streams.into_iter().enumerate() {
            if size == u32::MAX {
                // Stream does not exist
                pdb_streams.push(PdbStream {
                    size,
                    pages,
                    data: Vec::new(),
                });
                continue;
            }

            let data = Self::read_stream(reader, page_size, &pages, size)?;
            pdb_streams.push(PdbStream { size, pages, data });
        }

        Ok(PdbFile {
            page_size,
            fpm_page,
            num_pages,
            root_stream_size,
            root_stream_pages: directory_pages_list,
            streams: pdb_streams,
        })
    }

    /// Prints information about the PDB file
    fn print_info(&self) {
        println!("--- PDB File Information ---");
        println!("Page Size: {}", self.page_size);
        println!("Number of Pages: {}", self.num_pages);
        println!("Root Stream Size: {}", self.root_stream_size);
        println!("Root Stream Pages: {:?}", self.root_stream_pages);
        println!("Total Streams: {}", self.streams.len());
        println!();

        for (i, stream) in self.streams.iter().enumerate() {
            if stream.size == u32::MAX {
                println!("Stream {}: [No data]", i);
                continue;
            }

            println!("Stream {}:", i);
            println!("  Size: {} bytes", stream.size);
            println!("  Pages: {:?}", stream.pages);
            // For demonstration, parse specific streams
            match i {
                1 => {
                    println!("  -- PDB Info Stream --");
                    // Parse PDB Info Stream
                    self.parse_pdb_info_stream(stream);
                }
                2 => {
                    println!("  -- TPI Stream --");
                    // Parse TPI Stream
                    self.parse_tpi_stream(stream);
                }
                4 => {
                    println!("  -- IPI Stream --");
                    // Parse IPI Stream
                    self.parse_tpi_stream(stream);
                }
                _ => {
                    // Other streams can be parsed similarly
                }
            }
            println!();
        }
    }

    /// Parses the PDB Info Stream (Stream 1)
    fn parse_pdb_info_stream(&self, stream: &PdbStream) {
        let mut cursor = Cursor::new(&stream.data);

        // Read PDB Info header
        let version = cursor.read_u32::<LittleEndian>().unwrap_or(0);
        let signature = cursor.read_u32::<LittleEndian>().unwrap_or(0);
        let age = cursor.read_u32::<LittleEndian>().unwrap_or(0);
        let mut guid = [0u8; 16];
        cursor.read_exact(&mut guid).unwrap_or(());

        println!("  Version: 0x{:08X}", version);
        println!("  Signature: 0x{:08X}", signature);
        println!("  Age: {}", age);
        println!("  GUID: {:02X?}", guid);
    }

    /// Parses the TPI Stream (Stream 2 or 4)
    fn parse_tpi_stream(&self, stream: &PdbStream) {
        let mut cursor = Cursor::new(&stream.data);

        // Read TPI Stream header
        let version = cursor.read_u32::<LittleEndian>().unwrap_or(0);
        let header_size = cursor.read_u32::<LittleEndian>().unwrap_or(0);

        println!("  Version: 0x{:08X}", version);
        println!("  Header Size: {}", header_size);

        // Additional parsing can be implemented here
    }

    /// Parses the Root Stream Directory
    fn parse_root_stream(
        root_stream_data: &[u8],
        page_size: u32,
        reader: &mut BufReader<File>,
    ) -> io::Result<(Vec<(u32, Vec<u32>)>, Vec<Vec<u32>>)> {
        let mut cursor = Cursor::new(root_stream_data);

        // Read the number of streams
        let num_streams = cursor.read_u32::<LittleEndian>()?;

        // Read the sizes of each stream
        let mut stream_sizes = Vec::new();
        for _ in 0..num_streams {
            let size = cursor.read_u32::<LittleEndian>()?;
            stream_sizes.push(size);
        }

        // Read the page lists for each stream
        let mut stream_pages: Vec<Vec<u32>> = Vec::new();
        for &size in &stream_sizes {
            if size == u32::MAX {
                // Stream does not exist
                stream_pages.push(Vec::new());
                continue;
            }

            // Calculate the number of pages for this stream
            let num_pages = Self::calculate_page_count(size, page_size);

            let mut pages = Vec::new();
            for _ in 0..num_pages {
                let page_number = cursor.read_u32::<LittleEndian>()?;
                pages.push(page_number);
            }
            stream_pages.push(pages);
        }

        // Pair sizes with their pages
        let streams: Vec<(u32, Vec<u32>)> =
            stream_sizes.into_iter().zip(stream_pages.clone()).collect();

        Ok((streams, stream_pages))
    }

    /// Reads an array of page numbers from the given pages
    fn read_page_number_array(
        reader: &mut BufReader<File>,
        page_size: u32,
        pages: &[u32],
        num_items: u32,
    ) -> io::Result<Vec<u32>> {
        let mut result = Vec::new();
        let mut items_to_read = num_items;
        let entries_per_page = page_size / 4;
        for &page in pages {
            let page_offset = page as u64 * page_size as u64;
            reader.seek(SeekFrom::Start(page_offset))?;

            // Read the entire page
            let mut buffer = vec![0u8; page_size as usize];
            reader.read_exact(&mut buffer)?;

            let mut cursor = Cursor::new(buffer);

            for _ in 0..entries_per_page {
                if items_to_read == 0 {
                    break;
                }
                let page_number = cursor.read_u32::<LittleEndian>()?;
                result.push(page_number);
                items_to_read -= 1;
            }

            if items_to_read == 0 {
                break;
            }
        }

        if items_to_read > 0 {
            return Err(io::Error::new(
                io::ErrorKind::UnexpectedEof,
                "Not enough data to read all page number entries.",
            ));
        }

        Ok(result)
    }

    /// Reads a stream from the file
    fn read_stream(
        reader: &mut BufReader<File>,
        page_size: u32,
        pages: &[u32],
        stream_size: u32,
    ) -> io::Result<Vec<u8>> {
        let mut stream_data = Vec::with_capacity(stream_size as usize);
        let mut remaining = stream_size as usize;

        for &page in pages {
            let page_offset = page as u64 * page_size as u64;
            reader.seek(SeekFrom::Start(page_offset))?;

            let bytes_to_read = std::cmp::min(page_size as usize, remaining);
            let mut buffer = vec![0u8; bytes_to_read];
            reader.read_exact(&mut buffer)?;
            stream_data.extend_from_slice(&buffer);
            remaining -= bytes_to_read;

            if remaining == 0 {
                break;
            }
        }

        Ok(stream_data)
    }

    /// Calculates the number of pages needed to store the given size
    fn calculate_page_count(size: u32, page_size: u32) -> u32 {
        (size + page_size - 1) / page_size
    }
}

Stop printing out the streams. Instead. Print out the project/file directory that the PDB shows. File line numbers perchance aswell.
│ │ ├── App.V8Tree.dir
│ │ │ └── cmake_pch.cx
│ │ │ ├── v8tree
Like a tree like this

Also print out types/structures/classes/complex templates/inlined. Make this very advanced.