use super::io::*;
use super::*;

impl<'a> GraphVerifier<'a> {
    pub(super) fn read_header(&mut self) -> Result<(), String> {
        self.validate_header_prefix()?;
        let mut header = self.read_base_header()?;
        self.read_optional_header_fields(&mut header)?;
        self.validate_header(&header)?;
        self.compact = header.incompatible_flags & INCOMPATIBLE_COMPACT != 0;
        self.header = header;
        Ok(())
    }

    pub(super) fn validate_header_prefix(&self) -> Result<(), String> {
        if self.data.len() < HEADER_MIN_SIZE {
            return Err("file too small".to_string());
        }
        if &self.data[0..8] != b"LPKSHHRH" {
            return Err("invalid journal signature".to_string());
        }
        if self.data[17..24].iter().any(|b| *b != 0) {
            return Err("reserved header bytes are non-zero".to_string());
        }
        Ok(())
    }

    pub(super) fn read_base_header(&self) -> Result<Header, String> {
        let mut header = Header::empty();
        self.read_header_flags(&mut header)?;
        self.read_header_ids(&mut header)?;
        self.read_header_layout(&mut header)?;
        self.read_header_hash_tables(&mut header)?;
        self.read_header_object_counters(&mut header)?;
        self.read_header_entry_metadata(&mut header)?;
        Ok(header)
    }

    pub(super) fn read_header_flags(&self, header: &mut Header) -> Result<(), String> {
        header.compatible_flags = u32_at(self.data, 8)?;
        header.incompatible_flags = u32_at(self.data, 12)?;
        header.state = self.data[16];
        Ok(())
    }

    pub(super) fn read_header_ids(&self, header: &mut Header) -> Result<(), String> {
        header.file_id = bytes16_at(self.data, 24)?;
        header.tail_entry_boot_id = bytes16_at(self.data, 56)?;
        Ok(())
    }

    pub(super) fn read_header_layout(&self, header: &mut Header) -> Result<(), String> {
        header.header_size = u64_at(self.data, 88)?;
        header.arena_size = u64_at(self.data, 96)?;
        header.tail_object_offset = u64_at(self.data, 136)?;
        Ok(())
    }

    pub(super) fn read_header_hash_tables(&self, header: &mut Header) -> Result<(), String> {
        header.data_hash_table_offset = u64_at(self.data, 104)?;
        header.data_hash_table_size = u64_at(self.data, 112)?;
        header.field_hash_table_offset = u64_at(self.data, 120)?;
        header.field_hash_table_size = u64_at(self.data, 128)?;
        Ok(())
    }

    pub(super) fn read_header_object_counters(&self, header: &mut Header) -> Result<(), String> {
        header.n_objects = u64_at(self.data, 144)?;
        header.n_entries = u64_at(self.data, 152)?;
        Ok(())
    }

    pub(super) fn read_header_entry_metadata(&self, header: &mut Header) -> Result<(), String> {
        header.tail_entry_seqnum = u64_at(self.data, 160)?;
        header.head_entry_seqnum = u64_at(self.data, 168)?;
        header.entry_array_offset = u64_at(self.data, 176)?;
        header.head_entry_realtime = u64_at(self.data, 184)?;
        header.tail_entry_realtime = u64_at(self.data, 192)?;
        header.tail_entry_monotonic = u64_at(self.data, 200)?;
        Ok(())
    }

    pub(super) fn read_optional_header_fields(&self, header: &mut Header) -> Result<(), String> {
        if header_contains_field(self.data, header.header_size, 216) {
            header.n_data = u64_at(self.data, 208)?;
        }
        if header_contains_field(self.data, header.header_size, 224) {
            header.n_fields = u64_at(self.data, 216)?;
        }
        if header_contains_field(self.data, header.header_size, 232) {
            header.n_tags = u64_at(self.data, 224)?;
        }
        if header_contains_field(self.data, header.header_size, 240) {
            header.n_entry_arrays = u64_at(self.data, 232)?;
        }
        if header_contains_field(self.data, header.header_size, 272) {
            header.tail_entry_offset = u64_at(self.data, 264)?;
        }
        Ok(())
    }

    pub(super) fn validate_header(&self, header: &Header) -> Result<(), String> {
        if header.header_size < HEADER_MIN_SIZE as u64 {
            return Err(format!("invalid header_size {}", header.header_size));
        }
        if header.header_size > self.data.len() as u64 {
            return Err(format!(
                "header_size {} exceeds file size",
                header.header_size
            ));
        }
        if header.header_size % 8 != 0 {
            return Err(format!("header_size {} is not aligned", header.header_size));
        }
        if header.arena_size > self.data.len() as u64 - header.header_size {
            return Err("header_size + arena_size exceeds file size".to_string());
        }
        if !matches!(header.state, 0 | 1 | 2) {
            return Err(format!("invalid journal state {}", header.state));
        }
        if header.compatible_flags & !COMPATIBLE_SUPPORTED_MASK != 0 {
            return Err(format!(
                "unsupported compatible flags 0x{:x}",
                header.compatible_flags
            ));
        }
        if header.incompatible_flags & INCOMPATIBLE_COMPACT != 0
            && self.data.len() as u64 > JOURNAL_COMPACT_SIZE_MAX
        {
            return Err("compact journal exceeds 32-bit size limit".to_string());
        }
        Ok(())
    }
}
