use std::fs::File;
use std::io;
use std::io::prelude::*;
use std::io::BufReader;

#[derive(Debug, Clone, PartialEq)]
pub enum Command {
    Add,
    Delta,
    Delete,
    Move,
    DeltaMove,
    Copy,
    DeltaCopy,
    Update,
}

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

#[derive(Debug, Clone)]
pub struct Entry {
    pub src: String,
    pub command: Command,
    pub dst: Option<String>,
}

fn read_path_cmd(reader: &mut impl Read) -> Result<(String, Command), 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((String::from_utf8(path).unwrap(), Command::from_byte(c[0])))
}

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

        let dst = match command {
            Command::Move | Command::DeltaMove | Command::Copy | Command::DeltaCopy => {
                let (value, _) = read_path_cmd(reader)?;
                Some(value)
            }
            _ => None,
        };

        Ok(Self { src, command, dst })
    }

    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()])?;
        if self.command == Command::Move
            || self.command == Command::DeltaMove
            || self.command == Command::Copy
            || self.command == Command::DeltaCopy
        {
            writer.write_all(self.dst.as_ref().unwrap().as_bytes())?;
            writer.write_all(&[0x00])?;
        }
        Ok(())
    }
}

pub fn read_entries(patch_dir: String) -> Result<Vec<Entry>, io::Error> {
    let mut patch_file = BufReader::new(File::open(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)
}
