use std::fmt::Debug;
use std::convert::TryFrom;
use enumflags2::BitFlags;
use crate::error::*;

pub type Id = [u8; 2];
pub type SubfieldId = [u8; 2];
pub type Name = String;
pub type Comment = String;
pub type Crc16 = u16;
pub type Crc32 = u32;

#[derive(BitFlags, Debug, Copy, Clone, PartialEq)]
#[repr(u8)]
pub enum Flags {
    Text = 1,
    HeaderCrc = 2,
    Extra = 4,
    Name = 8,
    Comment = 16,
    Reserved0 = 32,
    Reserved1 = 64,
    Reserved2 = 128,
}

#[derive(BitFlags, Debug, Copy, Clone, PartialEq)]
#[repr(u8)]
pub enum CompressionFlags {
    BestCompression = 2,
    FastestCompression = 4,
}

#[derive(Debug, Copy, Clone, PartialEq)]
#[repr(u8)]
pub enum CompressionMethod {
    Reserved0 = 0,
    Reserved1 = 1,
    Reserved2 = 2,
    Reserved3 = 3,
    Reserved4 = 4,
    Reserved5 = 5,
    Reserved6 = 6,
    Reserved7 = 7,
    Deflate = 8,
}

impl TryFrom<u8> for CompressionMethod {
    type Error = Error;
    fn try_from(byte: u8) -> std::result::Result<Self, Self::Error> {
        use CompressionMethod::*;
        match byte {
            0 => Ok(Reserved0),
            1 => Ok(Reserved1),
            2 => Ok(Reserved2),
            3 => Ok(Reserved3),
            4 => Ok(Reserved4),
            5 => Ok(Reserved5),
            6 => Ok(Reserved6),
            7 => Ok(Reserved7),
            8 => Ok(Deflate),
            _ => Err(Error(ErrorKind::InvalidCompressionMethod, None)),
        }
    }
}

impl Default for CompressionMethod {
    fn default() -> Self { CompressionMethod::Deflate }
}

#[derive(Debug, Copy, Clone, PartialEq)]
#[repr(u8)]
pub enum OperatingSystem {
    Fat = 0,
    Amiga = 1,
    Vms = 2,
    Unix = 3,
    VmCms = 4,
    Atari = 5,
    Hpfs = 6,
    Macintosh = 7,
    ZSystem = 8,
    Cpm = 9,
    Tops = 10,
    Ntfs = 11,
    Qdos = 12,
    Acorn = 13,
    Unknown = 255,
}

impl Default for OperatingSystem {
    fn default() -> Self { OperatingSystem::Unknown }
}

impl TryFrom<u8> for OperatingSystem {
    type Error = Error;
    fn try_from(byte: u8) -> std::result::Result<Self, Self::Error> {
        use OperatingSystem::*;
        match byte {
            0 => Ok(Fat),
            1 => Ok(Amiga),
            2 => Ok(Vms),
            3 => Ok(Unix),
            4 => Ok(VmCms),
            5 => Ok(Atari),
            6 => Ok(Hpfs),
            7 => Ok(Macintosh),
            8 => Ok(ZSystem),
            9 => Ok(Cpm),
            10 => Ok(Tops),
            11 => Ok(Ntfs),
            12 => Ok(Qdos),
            13 => Ok(Acorn),
            155 => Ok(Unknown),
            _ => Err(Error(ErrorKind::InvalidCompressionMethod, None)),
        }
    }
}

#[derive(Debug, Copy, Clone, PartialEq)]
pub enum SubFieldType {
    ApolloFileTypeInformation,
}

impl From<SubFieldType> for [u8; 2] {
    fn from(t: SubFieldType) -> Self {
        use SubFieldType::*;
        match t {
            ApolloFileTypeInformation => [0x41, 0x70], // AP
        }
    }
}

impl Header {
    pub const GZIP_ID: [u8; 2] = [0x1f, 0x8b];
}

#[derive(Default, Debug, Clone)]
pub struct Extra {
    pub length: u16,
    pub subfield_id: SubfieldId,
    pub data_length: u16,
    pub data: Vec<u8>,
}

#[derive(Default, Debug, Clone)]
pub struct Header {
    pub id: Id,
    pub compression_method: CompressionMethod,
    pub flags: BitFlags<Flags>,
    pub modification_time: u32,
    pub compression_flags: BitFlags<CompressionFlags>,
    pub operating_system: OperatingSystem,
    pub extra: Option<Extra>,
    pub name: Option<Name>,
    pub comment: Option<Comment>,
    pub crc: Option<Crc16>,
}

#[derive(Default, Clone)]
pub struct Member {
    pub header: Header,
    pub data: Vec<u8>,
    pub crc: Crc32,
    pub input_size: u32,
}

impl Debug for Member {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
        f.debug_struct("GZIP Member")
            .field("Header", &self.header)
            .field("Read Data Size", &self.data.len())
            .field("Input Size", &self.input_size)
            .field("CRC32", &self.crc)
            .finish()
    }
}