// Copyright 2024 The Regents of the University of California
// released under BSD 3-Clause License
// author: Kevin Laeufer <laeufer@berkeley.edu>

mod common;
mod hierarchy;
mod signals;

pub use crate::ghw::common::GhwParseError;
use crate::ghw::common::{GhwDecodeInfo, HeaderData};
use crate::signals::SignalSource;
use crate::viewers::ProgressCount;
use crate::{Hierarchy, LoadOptions, TimeTable};
use std::io::{BufRead, Seek, SeekFrom};
use std::sync::atomic::Ordering;

/// Checks header to see if we are dealing with a GHW file.
pub fn is_ghw(input: &mut (impl BufRead + Seek)) -> bool {
    let is_ghw = hierarchy::read_ghw_header(input).is_ok();
    // try to reset input
    let _ = input.seek(SeekFrom::Start(0));
    is_ghw
}

pub type Result<T> = std::result::Result<T, GhwParseError>;

pub fn read_header<P: AsRef<std::path::Path>>(
    filename: P,
    options: &LoadOptions,
) -> Result<(Hierarchy, ReadBodyContinuation, u64)> {
    let f = std::fs::File::open(filename)?;
    let mut input = std::io::BufReader::new(f);
    let (hierarchy, header, decode_info, body_len) = read_header_internal(&mut input, options)?;
    let cont = ReadBodyContinuation {
        header,
        decode_info,
        input: Input::File(input),
    };
    Ok((hierarchy, cont, body_len))
}

pub fn read_header_from_bytes(
    bytes: Vec<u8>,
    options: &LoadOptions,
) -> Result<(Hierarchy, ReadBodyContinuation, u64)> {
    let mut input = std::io::Cursor::new(bytes);
    let (hierarchy, header, decode_info, body_len) = read_header_internal(&mut input, options)?;
    let cont = ReadBodyContinuation {
        header,
        decode_info,
        input: Input::Bytes(input),
    };
    Ok((hierarchy, cont, body_len))
}

pub fn read_body(
    data: ReadBodyContinuation,
    hierarchy: &Hierarchy,
    progress: Option<ProgressCount>,
) -> Result<(SignalSource, TimeTable)> {
    let (source, time_table) = match data.input {
        Input::Bytes(mut input) => match progress {
            Some(p) => {
                let mut wrapped = ProgressTracker::new(input, p);
                signals::read_signals(&data.header, data.decode_info, hierarchy, &mut wrapped)?
            }
            None => signals::read_signals(&data.header, data.decode_info, hierarchy, &mut input)?,
        },
        Input::File(mut input) => match progress {
            Some(p) => {
                let mut wrapped = ProgressTracker::new(input, p);
                signals::read_signals(&data.header, data.decode_info, hierarchy, &mut wrapped)?
            }
            None => signals::read_signals(&data.header, data.decode_info, hierarchy, &mut input)?,
        },
    };
    Ok((source, time_table))
}

pub struct ReadBodyContinuation {
    header: HeaderData,
    decode_info: GhwDecodeInfo,
    input: Input,
}

enum Input {
    Bytes(std::io::Cursor<Vec<u8>>),
    File(std::io::BufReader<std::fs::File>),
}

fn read_header_internal(
    input: &mut (impl BufRead + Seek),
    _options: &LoadOptions,
) -> Result<(Hierarchy, HeaderData, GhwDecodeInfo, u64)> {
    let header = hierarchy::read_ghw_header(input)?;
    let header_len = input.stream_position()?;

    // currently we do read the directory, however we are not using it yet
    let _sections = hierarchy::try_read_directory(&header, input)?;
    input.seek(SeekFrom::Start(header_len))?;
    // TODO: use actual section positions

    let (decode_info, hierarchy) = hierarchy::read_hierarchy(&header, input)?;

    // determine body length
    let body_start = input.stream_position()?;
    input.seek(SeekFrom::End(0))?;
    let file_size = input.stream_position()?;
    input.seek(SeekFrom::Start(body_start))?;
    let body_len = file_size - body_start;

    Ok((hierarchy, header, decode_info, body_len))
}

struct ProgressTracker<T: BufRead> {
    inner: T,
    progress: ProgressCount,
}

impl<T: BufRead> ProgressTracker<T> {
    fn new(inner: T, progress: ProgressCount) -> Self {
        Self { inner, progress }
    }
}

impl<T: BufRead> std::io::Read for ProgressTracker<T> {
    #[inline]
    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
        let len = self.inner.read(buf)?;
        self.progress.fetch_add(len as u64, Ordering::SeqCst);
        Ok(len)
    }
}

impl<T: BufRead> BufRead for ProgressTracker<T> {
    #[inline]
    fn fill_buf(&mut self) -> std::io::Result<&[u8]> {
        self.inner.fill_buf()
    }

    #[inline]
    fn consume(&mut self, amt: usize) {
        self.inner.consume(amt);
        self.progress.fetch_add(amt as u64, Ordering::SeqCst);
    }
}
