use crate::misc::decompress_reader;
use smol_str::SmolStr;
use std::io;
use std::io::prelude::*;

#[derive(Debug, Clone)]
pub struct Entry {
    pub src: SmolStr,
    pub command: Command,
}
#[derive(Debug, Clone)]
pub enum Command {
    Move { dst: SmolStr },
    Copy { dst: SmolStr },
    DeltaMove { dst: SmolStr },
    DeltaCopy { dst: SmolStr },
    Delta,
    Update,
    Delete,
    Add,
}

impl Command {
    pub fn to_byte(&self) -> u8 {
        match self {
            Self::Move { .. } => 0x00,
            Self::Copy { .. } => 0x01,
            Self::DeltaMove { .. } => 0x02,
            Self::DeltaCopy { .. } => 0x03,
            Self::Delta => 0x04,
            Self::Update => 0x05,
            Self::Delete => 0x06,
            Self::Add => 0x07,
        }
    }
}

fn read_path_cmd(reader: &mut impl Read) -> Result<(SmolStr, u8), io::Error> {
    let mut path: Vec<u8> = vec![];
    let mut c = [0u8; 1];
    loop {
        reader.read_exact(&mut c)?;
        match c[0] {
            0..=7 => {
                break;
            }
            _ => {
                path.push(c[0]);
            }
        }
    }
    Ok((SmolStr::from(std::str::from_utf8(&path).unwrap()), c[0]))
}

impl Entry {
    pub fn from_io(reader: &mut impl Read) -> Result<Self, io::Error> {
        let (src, cmd) = read_path_cmd(reader)?;

        let command = match cmd {
            0x0 => Command::Move {
                dst: read_path_cmd(reader)?.0,
            },
            0x1 => Command::Copy {
                dst: read_path_cmd(reader)?.0,
            },
            0x2 => Command::DeltaMove {
                dst: read_path_cmd(reader)?.0,
            },
            0x3 => Command::DeltaCopy {
                dst: read_path_cmd(reader)?.0,
            },
            0x4 => Command::Delta,
            0x5 => Command::Update,
            0x6 => Command::Delete,
            _ => Command::Add,
        };

        Ok(Self { src, command })
    }

    pub fn to_io(&self, writer: &mut impl Write) -> Result<(), io::Error> {
        writer.write_all(self.src.as_bytes())?;
        writer.write_all(&[self.command.to_byte()])?;

        match &self.command {
            Command::Move { dst }
            | Command::Copy { dst }
            | Command::DeltaMove { dst }
            | Command::DeltaCopy { dst } => {
                writer.write_all(dst.as_bytes())?;
                writer.write_all(&[0x00])?;
            }
            _ => {}
        }

        Ok(())
    }
}

pub fn read_entries(patch_dir: &String) -> Result<Vec<Entry>, io::Error> {
    let mut patch_file = decompress_reader(format!("{}/index", patch_dir))?;
    let mut patch_entries: Vec<Entry> = vec![];
    loop {
        match Entry::from_io(&mut patch_file) {
            Ok(e) => {
                patch_entries.push(e);
            }
            Err(e) => {
                if e.kind() == io::ErrorKind::UnexpectedEof {
                    break;
                } else {
                    return Err(e);
                }
            }
        }
    }
    Ok(patch_entries)
}
