use crate::errors::ToeErrorType;
use crate::thumbrequest::ThumbRequest;
use log::trace;
use std::fs::{set_permissions, Permissions};
use std::os::unix::fs::PermissionsExt;
use std::path::Path;

/// Service function which returns a "ready-to-insert PNG-tEXt" metadata-element as byte-vector for
/// a given keyword and value.
fn text_chunk<S: Into<String>>(keyword: &str, text: S) -> Result<Vec<u8>, ()> {
    let text = text.into();

    if keyword.is_empty() || keyword.len() > 79 || keyword.contains('\0') {
        return Err(());
    }

    if text.contains('\0') {
        return Err(());
    }

    let text = text.replace("\r\n", "\n");

    if text.is_empty() {
        return Err(());
    }
    let data = {
        let mut r = vec![];
        r.extend_from_slice(keyword.as_bytes());
        r.push(0);
        r.extend_from_slice(text.as_bytes());
        r
    };

    Ok(data)
}

/// Saves a single thumb image to the thumbnail database.
///
/// The thumb is saved as PNG file with the necessary metadata (tEXt chunks),
/// according to the freedesktop.org specification.
///
/// The thumb is first completely generated in a temporary file and then moved
/// to the target name, to avoid cucrrency-problems with other programs, and
/// as specified the freedesktop.org spec.
pub fn save_thumb(
    toe: &ThumbRequest,
    image: &image::DynamicImage,
    dest_path: &std::path::Path,
    original_size: (u32, u32),
    mime_type: &mime::Mime,
) -> Result<(), ToeErrorType> {
    let dest_path_string = dest_path.to_str().unwrap();
    let temp_path = format!("{}_{}.toetmp", dest_path_string, toe.process_id);
    trace!(
        "Saving thumb with size {}x{} to temp {}.",
        image.width(),
        image.height(),
        temp_path
    );
    let output = std::fs::OpenOptions::new()
        .write(true)
        .truncate(true)
        .create(true)
        .open(&temp_path)
        .map_err(|_| ToeErrorType::TempCantBeCreated(temp_path.to_string()))?;

    let mut encoder = png::Encoder::new(output, image.width(), image.height());
    assert!(image.color() == image::ColorType::Rgba8);
    encoder.set_color(png::ColorType::Rgba);
    encoder.set_depth(png::BitDepth::Eight);
    let mut writer = encoder
        .write_header()
        .map_err(|e| ToeErrorType::TempHeaderCantBeWritten(e.to_string()))?;

    writer
        .write_chunk(
            png::chunk::tEXt,
            &text_chunk("Thumb::URI", &toe.file_uri).unwrap(),
        )
        .map_err(|e| ToeErrorType::TempDataCantBeWritten(e.to_string()))?;

    writer
        .write_chunk(
            png::chunk::tEXt,
            &text_chunk("Thumb::MTime", toe.mtime_secs.to_string()).unwrap(),
        )
        .map_err(|e| ToeErrorType::TempDataCantBeWritten(e.to_string()))?;

    writer
        .write_chunk(
            png::chunk::tEXt,
            &text_chunk("Thumb::Size", toe.file_size.to_string()).unwrap(),
        )
        .map_err(|e| ToeErrorType::TempDataCantBeWritten(e.to_string()))?;
    writer
        .write_chunk(
            png::chunk::tEXt,
            &text_chunk("Thumb::Image::Width", original_size.0.to_string()).unwrap(),
        )
        .map_err(|e| ToeErrorType::TempDataCantBeWritten(e.to_string()))?;
    writer
        .write_chunk(
            png::chunk::tEXt,
            &text_chunk("Thumb::Image::Height", original_size.1.to_string()).unwrap(),
        )
        .map_err(|e| ToeErrorType::TempDataCantBeWritten(e.to_string()))?;
    writer
        .write_chunk(
            png::chunk::tEXt,
            &text_chunk("Thumb::Mimetype", format!("{mime_type}")).unwrap(),
        )
        .map_err(|e| ToeErrorType::TempDataCantBeWritten(e.to_string()))?;
    writer
        .write_chunk(
            png::chunk::tEXt,
            &text_chunk("Software", "allmytoes").unwrap(),
        )
        .map_err(|e| ToeErrorType::TempDataCantBeWritten(e.to_string()))?;

    writer
        .write_image_data(image.as_bytes())
        .map_err(|e| ToeErrorType::TempDataCantBeWritten(e.to_string()))?;

    if let Err(error) = set_permissions(&temp_path, Permissions::from_mode(0o600)) {
        return Err(ToeErrorType::CanNotSetPermission(
            Path::new(&temp_path).to_str().unwrap().to_string(),
            error.to_string(),
        ));
    }

    trace!("Moving temp image {} to {}", temp_path, dest_path_string);
    match std::fs::rename(&temp_path, dest_path_string) {
        Ok(()) => Ok(()),
        Err(e) => Err(ToeErrorType::TempThumbCantBeRenamed(
            temp_path,
            dest_path_string.to_string(),
            e.to_string(),
        )),
    }
}
