use std::fmt::Write as _;

use crate::canvas::{Canvas, ToIndex};
use crate::image::SharedImage;
use crate::resource::ResourceItem;
use crate::settings::{RESOURCE_ARCHIVE_DIRNAME, TILEMAP_SIZE};
use crate::types::Tile;
use crate::utils::{as_u32, parse_hex_string, simplify_string};

impl ToIndex for Tile {
    fn to_index(&self) -> usize {
        0
    }
}

pub struct Tilemap {
    pub(crate) canvas: Canvas<Tile>,
    pub image: SharedImage,
}

pub type SharedTilemap = shared_type!(Tilemap);

impl Tilemap {
    pub fn new(width: u32, height: u32, image: SharedImage) -> SharedTilemap {
        new_shared_type!(Self {
            canvas: Canvas::new(width, height),
            image,
        })
    }

    pub const fn width(&self) -> u32 {
        self.canvas.width()
    }

    pub const fn height(&self) -> u32 {
        self.canvas.height()
    }

    pub fn set(&mut self, x: i32, y: i32, data_str: &[&str]) {
        let width = simplify_string(data_str[0]).len() as u32 / 4;
        let height = data_str.len() as u32;
        let tilemap = Self::new(width, height, self.image.clone());
        {
            let mut tilemap = tilemap.lock();
            for y in 0..height {
                let src_data = simplify_string(data_str[y as usize]);
                for x in 0..width {
                    let index = x as usize * 4;
                    let tile = parse_hex_string(&src_data[index..index + 4]).unwrap();
                    tilemap.canvas.data[y as usize][x as usize] =
                        (((tile >> 8) & 0xff) as u8, (tile & 0xff) as u8);
                }
            }
        }
        self.blt(
            x as f64,
            y as f64,
            tilemap,
            0.0,
            0.0,
            width as f64,
            height as f64,
            None,
        );
    }

    pub fn clip(&mut self, x: f64, y: f64, width: f64, height: f64) {
        self.canvas.clip(x, y, width, height);
    }

    pub fn clip0(&mut self) {
        self.canvas.clip0();
    }

    pub fn camera(&mut self, x: f64, y: f64) {
        self.canvas.camera(x, y);
    }

    pub fn camera0(&mut self) {
        self.canvas.camera0();
    }

    pub fn cls(&mut self, tile: Tile) {
        self.canvas.cls(tile);
    }

    pub fn pget(&mut self, x: f64, y: f64) -> Tile {
        self.canvas.pget(x, y)
    }

    pub fn pset(&mut self, x: f64, y: f64, tile: Tile) {
        self.canvas.pset(x, y, tile);
    }

    pub fn line(&mut self, x1: f64, y1: f64, x2: f64, y2: f64, tile: Tile) {
        self.canvas.line(x1, y1, x2, y2, tile);
    }

    pub fn rect(&mut self, x: f64, y: f64, width: f64, height: f64, tile: Tile) {
        self.canvas.rect(x, y, width, height, tile);
    }

    pub fn rectb(&mut self, x: f64, y: f64, width: f64, height: f64, tile: Tile) {
        self.canvas.rectb(x, y, width, height, tile);
    }

    pub fn circ(&mut self, x: f64, y: f64, radius: f64, tile: Tile) {
        self.canvas.circ(x, y, radius, tile);
    }

    pub fn circb(&mut self, x: f64, y: f64, radius: f64, tile: Tile) {
        self.canvas.circb(x, y, radius, tile);
    }

    pub fn elli(&mut self, x: f64, y: f64, width: f64, height: f64, tile: Tile) {
        self.canvas.elli(x, y, width, height, tile);
    }

    pub fn ellib(&mut self, x: f64, y: f64, width: f64, height: f64, tile: Tile) {
        self.canvas.ellib(x, y, width, height, tile);
    }

    pub fn tri(&mut self, x1: f64, y1: f64, x2: f64, y2: f64, x3: f64, y3: f64, tile: Tile) {
        self.canvas.tri(x1, y1, x2, y2, x3, y3, tile);
    }

    pub fn trib(&mut self, x1: f64, y1: f64, x2: f64, y2: f64, x3: f64, y3: f64, tile: Tile) {
        self.canvas.trib(x1, y1, x2, y2, x3, y3, tile);
    }

    pub fn fill(&mut self, x: f64, y: f64, tile: Tile) {
        self.canvas.fill(x, y, tile);
    }

    pub fn blt(
        &mut self,
        x: f64,
        y: f64,
        tilemap: shared_type!(Self),
        tilemap_x: f64,
        tilemap_y: f64,
        width: f64,
        height: f64,
        transparent: Option<Tile>,
    ) {
        if let Some(tilemap) = tilemap.try_lock() {
            self.canvas.blt(
                x,
                y,
                &tilemap.canvas,
                tilemap_x,
                tilemap_y,
                width,
                height,
                transparent,
                None,
            );
        } else {
            let copy_width = as_u32(width.abs());
            let copy_height = as_u32(height.abs());
            let mut canvas = Canvas::new(copy_width, copy_height);
            canvas.blt(
                0.0,
                0.0,
                &self.canvas,
                tilemap_x,
                tilemap_y,
                copy_width as f64,
                copy_height as f64,
                None,
                None,
            );
            self.canvas
                .blt(x, y, &canvas, 0.0, 0.0, width, height, transparent, None);
        }
    }
}

impl ResourceItem for Tilemap {
    fn resource_name(item_no: u32) -> String {
        RESOURCE_ARCHIVE_DIRNAME.to_string() + "tilemap" + &item_no.to_string()
    }

    fn is_modified(&self) -> bool {
        for y in 0..self.height() {
            for x in 0..self.width() {
                if self.canvas.data[y as usize][x as usize] != (0, 0) {
                    return true;
                }
            }
        }
        false
    }

    fn clear(&mut self) {
        self.cls((0, 0));
    }

    fn serialize(&self) -> String {
        let mut output = String::new();
        for y in 0..self.height() {
            for x in 0..self.width() {
                let tile = self.canvas.data[y as usize][x as usize];
                let _ = write!(output, "{:02x}{:02x}", tile.0, tile.1);
            }
            output += "\n";
        }
        let _ = write!(
            output,
            "{}",
            crate::image_no(self.image.clone()).unwrap_or(0)
        );
        output
    }

    fn deserialize(&mut self, version: u32, input: &str) {
        for (y, line) in input.lines().enumerate() {
            if y < TILEMAP_SIZE as usize {
                if version < 10500 {
                    string_loop!(x, tile, line, 3, {
                        let tile = parse_hex_string(&tile).unwrap();
                        self.canvas.data[y][x] = ((tile % 32) as u8, (tile / 32) as u8);
                    });
                } else {
                    string_loop!(x, tile, line, 4, {
                        let tile_x = parse_hex_string(&tile[0..2]).unwrap();
                        let tile_y = parse_hex_string(&tile[2..4]).unwrap();
                        self.canvas.data[y][x] = (tile_x as u8, tile_y as u8);
                    });
                }
            } else {
                self.image = crate::image(line.parse().unwrap());
            }
        }
    }
}
