use anyhow::{bail, Result};
use image::{codecs::jpeg::JpegEncoder, DynamicImage, RgbImage};
use std::io::Cursor;

pub struct Tile {
    data: Option<RgbImage>,
    size: u64,
    level: u64,
    pub pos: (u64, u64),
}

impl Tile {
    pub fn new(pos: (u64, u64), level: u64, size: u64) -> Tile {
        Tile {
            data: None,
            level,
            size,
            pos,
        }
    }

    pub fn set_image(&mut self, img: RgbImage) -> Result<()> {
        let w = img.width();
        let h = img.height();
        let s = self.size as u32;
        if w != s || h != s {
            bail!("invalid image size, is {}x{} should be {}x{}", w, h, s, s);
        }
        self.data = Some(img);
        Ok(())
    }
    pub fn set_data(&mut self, data: Vec<u8>) -> Result<()> {
        let dyn_img = image::load_from_memory(&data)?;
        let rgb_img = dyn_img.to_rgb8();
        self.set_image(rgb_img)
    }

    pub fn is_empty(&self) -> bool {
        self.data.is_none()
    }

    pub fn image(&self) -> Result<&RgbImage> {
        match &self.data {
            Some(d) => Ok(d),
            None => bail!("Tile is empty"),
        }
    }
    pub fn data(&self) -> Result<Vec<u8>> {
        let image = self.image()?;
        let mut bytes = Cursor::new(Vec::new());
        let img = DynamicImage::ImageRgb8(image.clone());
        let encoder = JpegEncoder::new_with_quality(&mut bytes, 90);
        img.write_with_encoder(encoder)?;
        Ok(bytes.into_inner())
    }

    pub fn size(&self) -> u64 {
        self.size
    }
    pub fn level(&self) -> u64 {
        self.level
    }
    pub fn pos(&self) -> (u64, u64) {
        self.pos
    }
    pub fn index(&self) -> u64 {
        let l = self.level as u32;
        let (x, y) = self.pos;
        (4u64.pow(l) - 1).div_euclid(3) + y * 2u64.pow(self.level as u32) + x
    }

    pub fn coords(&self) -> (u64, u64) {
        let (x, y) = self.pos;
        (x * self.size, y * self.size)
    }
}
