#![allow(dead_code)]

use flate2::Compression;
use flate2::write::DeflateEncoder;
use std::fs;
use std::io::Write;
use std::path::PathBuf;

pub const SQPACK_MAGIC: [u8; 8] = *b"SqPack\0\0";

pub const SQPACK_HEADER_SIZE: u32 = 1024;

pub const INDEX_ENTRY_SIZE: u32 = 16;

pub const INDEX2_ENTRY_SIZE: u32 = 8;

pub const BLOCK_INFO_SIZE: u32 = 8;

pub const BLOCK_HEADER_SIZE: u32 = 16;

pub const BLOCK_TYPE_UNCOMPRESSED: u32 = 32000;

pub fn make_sqpack_header(file_type: u32) -> Vec<u8> {
    let mut buf = Vec::with_capacity(SQPACK_HEADER_SIZE as usize);
    buf.extend_from_slice(&SQPACK_MAGIC);
    buf.push(0);
    buf.extend_from_slice(&[0, 0, 0]);
    buf.extend_from_slice(&SQPACK_HEADER_SIZE.to_le_bytes());
    buf.extend_from_slice(&1u32.to_le_bytes());
    buf.extend_from_slice(&file_type.to_le_bytes());
    buf.resize(SQPACK_HEADER_SIZE as usize, 0);
    buf
}

pub fn make_index_header(
    index_data_offset: u32,
    index_data_size: u32,
    number_of_data_file: u32,
) -> Vec<u8> {
    let mut buf = Vec::with_capacity(1024);
    buf.extend_from_slice(&1024u32.to_le_bytes());
    buf.extend_from_slice(&1u32.to_le_bytes());
    buf.extend_from_slice(&index_data_offset.to_le_bytes());
    buf.extend_from_slice(&index_data_size.to_le_bytes());
    buf.resize(buf.len() + 64, 0);
    buf.extend_from_slice(&number_of_data_file.to_le_bytes());
    buf.extend_from_slice(&0u32.to_le_bytes());
    buf.extend_from_slice(&0u32.to_le_bytes());
    buf.resize(buf.len() + 64, 0);
    buf.extend_from_slice(&0u32.to_le_bytes());
    buf.extend_from_slice(&0u32.to_le_bytes());
    buf.resize(buf.len() + 64, 0);
    buf.extend_from_slice(&0u32.to_le_bytes());
    buf.extend_from_slice(&0u32.to_le_bytes());
    buf.resize(buf.len() + 64, 0);
    buf.extend_from_slice(&0u32.to_le_bytes());
    buf.resize(buf.len() + 656, 0);
    buf.resize(buf.len() + 64, 0);
    assert_eq!(buf.len(), 1024);
    buf
}

pub fn make_index_entry_bytes(hash: u64, data: u32) -> [u8; 16] {
    let mut buf = [0u8; 16];
    buf[..8].copy_from_slice(&hash.to_le_bytes());
    buf[8..12].copy_from_slice(&data.to_le_bytes());
    buf
}

pub fn make_index2_entry_bytes(hash: u32, data: u32) -> [u8; 8] {
    let mut buf = [0u8; 8];
    buf[..4].copy_from_slice(&hash.to_le_bytes());
    buf[4..8].copy_from_slice(&data.to_le_bytes());
    buf
}

pub fn make_index_file(entries: &[(u64, u32)]) -> Vec<u8> {
    let mut buf = Vec::new();
    buf.extend_from_slice(&make_sqpack_header(2));
    let index_data_offset = SQPACK_HEADER_SIZE + 1024;
    let index_data_size = u32::try_from(entries.len()).unwrap() * INDEX_ENTRY_SIZE;
    buf.extend_from_slice(&make_index_header(index_data_offset, index_data_size, 1));
    assert_eq!(buf.len(), index_data_offset as usize);
    for (hash, data) in entries {
        buf.extend_from_slice(&make_index_entry_bytes(*hash, *data));
    }
    buf
}

pub fn make_index2_file(entries: &[(u32, u32)]) -> Vec<u8> {
    let mut buf = Vec::new();
    buf.extend_from_slice(&make_sqpack_header(2));
    let index_data_offset = SQPACK_HEADER_SIZE + 1024;
    let index_data_size = u32::try_from(entries.len()).unwrap() * INDEX2_ENTRY_SIZE;
    buf.extend_from_slice(&make_index_header(index_data_offset, index_data_size, 1));
    assert_eq!(buf.len(), index_data_offset as usize);
    for (hash, data) in entries {
        buf.extend_from_slice(&make_index2_entry_bytes(*hash, *data));
    }
    buf
}

pub fn make_dat_buf(body: &[u8]) -> Vec<u8> {
    let mut buf = make_sqpack_header(1);
    buf.extend_from_slice(body);
    buf
}

pub fn make_file_info(
    size: u32,
    kind_raw: u32,
    raw_file_size: u32,
    number_of_blocks: u32,
) -> Vec<u8> {
    let mut v = Vec::with_capacity(24);
    v.extend_from_slice(&size.to_le_bytes());
    v.extend_from_slice(&kind_raw.to_le_bytes());
    v.extend_from_slice(&raw_file_size.to_le_bytes());
    v.extend_from_slice(&0u32.to_le_bytes());
    v.extend_from_slice(&0u32.to_le_bytes());
    v.extend_from_slice(&number_of_blocks.to_le_bytes());
    v
}

pub fn make_block_info(offset: u32, compressed_size: u16, uncompressed_size: u16) -> Vec<u8> {
    let mut v = Vec::with_capacity(BLOCK_INFO_SIZE as usize);
    v.extend_from_slice(&offset.to_le_bytes());
    v.extend_from_slice(&compressed_size.to_le_bytes());
    v.extend_from_slice(&uncompressed_size.to_le_bytes());
    v
}

pub fn make_block_header(dat_block_type: u32, block_data_size: u32) -> Vec<u8> {
    let mut v = Vec::with_capacity(BLOCK_HEADER_SIZE as usize);
    v.extend_from_slice(&BLOCK_HEADER_SIZE.to_le_bytes());
    v.extend_from_slice(&0u32.to_le_bytes());
    v.extend_from_slice(&dat_block_type.to_le_bytes());
    v.extend_from_slice(&block_data_size.to_le_bytes());
    v
}

pub fn deflate_compress(input: &[u8]) -> Vec<u8> {
    let mut enc = DeflateEncoder::new(Vec::new(), Compression::default());
    enc.write_all(input).unwrap();
    enc.finish().unwrap()
}

pub fn make_standard_dat(blocks: &[(&[u8], bool)]) -> (Vec<u8>, u64) {
    let raw_total: u32 = blocks
        .iter()
        .map(|(b, _)| u32::try_from(b.len()).unwrap())
        .sum();
    let number_of_blocks = u32::try_from(blocks.len()).unwrap();

    let info_size = 24 + 8 * number_of_blocks;

    let mut block_data = Vec::new();
    let mut block_infos = Vec::new();
    for (payload, compressed) in blocks {
        let offset = u32::try_from(block_data.len()).unwrap();
        let uncompressed_size = u16::try_from(payload.len()).unwrap();
        if *compressed {
            let encoded = deflate_compress(payload);
            let compressed_size =
                u16::try_from(BLOCK_HEADER_SIZE as usize + encoded.len()).unwrap();
            block_infos.push(make_block_info(offset, compressed_size, uncompressed_size));
            block_data.extend_from_slice(&make_block_header(16000, u32::from(uncompressed_size)));
            block_data.extend_from_slice(&encoded);
        } else {
            let compressed_size =
                u16::try_from(BLOCK_HEADER_SIZE as usize + payload.len()).unwrap();
            block_infos.push(make_block_info(offset, compressed_size, uncompressed_size));
            block_data.extend_from_slice(&make_block_header(
                BLOCK_TYPE_UNCOMPRESSED,
                u32::from(uncompressed_size),
            ));
            block_data.extend_from_slice(payload);
        }
    }

    let mut body = Vec::new();
    body.extend_from_slice(&make_file_info(info_size, 2, raw_total, number_of_blocks));
    for bi in &block_infos {
        body.extend_from_slice(bi);
    }
    debug_assert_eq!(body.len(), info_size as usize);
    body.extend_from_slice(&block_data);

    let buf = make_dat_buf(&body);
    (buf, u64::from(SQPACK_HEADER_SIZE))
}

pub fn unique_tempdir(tag: &str) -> PathBuf {
    let path = std::env::temp_dir().join(format!(
        "sqpack-rs-bench-{}-{}-{}",
        tag,
        std::process::id(),
        std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .map_or(0, |d| d.as_nanos()),
    ));
    let _ = fs::remove_dir_all(&path);
    path
}

pub fn build_compressible_payload(size: usize) -> Vec<u8> {
    #[allow(clippy::cast_possible_truncation)]
    (0..size).map(|i| (i % 251) as u8).collect()
}
