// SPDX-License-Identifier: LGPL-2.1-or-later OR GPL-2.0-or-later OR MPL-2.0
// SPDX-FileCopyrightText: 2024 Gabriel Marcano <gabemarcano@yahoo.com>

use crate::error::Error;

use std::io::Read;
use std::io::Seek;
use std::io::SeekFrom;
use std::str;
use std::vec::Vec;

use encoding_rs::WINDOWS_1252;

pub struct Banner {
    pub data: Vec<u16>,
    pub game_name: String,
    pub developer: String,
    pub full_title: String,
    pub full_developer: String,
    pub description: String,
}

fn from_latin1(data: &[u8]) -> Result<String, Error> {
    let (result, _, error) = WINDOWS_1252.decode(data);
    if error {
        return Err(Error::Parse("unable to convert to latin1".into()));
    }
    Ok(result.to_string())
}

impl TryFrom<&[u8]> for Banner {
    type Error = Error;

    fn try_from(binary: &[u8]) -> Result<Self, Self::Error> {
        if binary.len() != 6496 {
            return Err(Error::Parse("wrong size for a banner".into()));
        }

        let data = binary[0x20..0x1820]
            .chunks_exact(2)
            .map(|a| u16::from_be_bytes([a[0], a[1]]))
            .collect();
        let game_name = trim(&from_latin1(&binary[0x1820..0x1840])?).to_string();
        let developer = trim(&from_latin1(&binary[0x1840..0x1860])?).to_string();
        let full_title = trim(&from_latin1(&binary[0x1860..0x18A0])?).to_string();
        let full_developer = trim(&from_latin1(&binary[0x18A0..0x18e0])?).to_string();
        let description = trim(&from_latin1(&binary[0x18E0..0x1960])?).to_string();

        Ok(Self {
            data,
            game_name,
            developer,
            full_title,
            full_developer,
            description,
        })
    }
}

impl Banner {
    /// Returns a new instance of a Gamecube banner.
    ///
    /// # Errors
    /// [`Error::Io`] if there's an error while reading or seeking from the IO object,
    /// [`Error::Utf8`] if there are strings that are not valid UTF8 (FIXME is this a valid
    /// complaint for GCN strings?)
    pub fn new<T: Read + Seek>(io: &mut T) -> Result<Self, Error> {
        io.seek(SeekFrom::Start(0x20))?;
        let mut data = vec![0u8; 0x1800];
        io.read_exact(&mut data)?;
        let data = data
            .chunks_exact(2)
            .map(|a| u16::from_be_bytes([a[0], a[1]]))
            .collect();
        let mut tmp = [0u8; 0x20];
        io.read_exact(&mut tmp)?;
        let game_name = trim(&from_latin1(&tmp)?).to_string();
        io.read_exact(&mut tmp)?;
        let developer = trim(&from_latin1(&tmp)?).to_string();
        let mut tmp = [0u8; 0x40];
        io.read_exact(&mut tmp)?;
        let full_title = trim(&from_latin1(&tmp)?).to_string();
        io.read_exact(&mut tmp)?;
        let full_developer = trim(&from_latin1(&tmp)?).to_string();
        let mut tmp = [0u8; 0x80];
        io.read_exact(&mut tmp)?;
        let description = trim(&from_latin1(&tmp)?).to_string();

        Ok(Self {
            data,
            game_name,
            developer,
            full_title,
            full_developer,
            description,
        })
    }

    #[must_use]
    pub fn extract_rgba_banner_image(&self) -> Vec<u8> {
        let width = 96;
        let height = 32;
        let tile_w = 4;
        let tile_h = 4;
        let mut index = 0;
        let mut output = vec![0u8; width * height * 4];
        // 4x4 tiles
        for y in (0..height).step_by(tile_h) {
            for x in (0..width).step_by(tile_w) {
                // Iterate through each pixel in the tile
                for ty in 0..tile_h {
                    for tx in 0..tile_w {
                        let pixel: u16 = self.data[index];
                        index += 1;
                        let r = ((((pixel >> 10) & 0x1F) * 0xFF / 0x1F) & 0xFF) as u8;
                        let g = ((((pixel >> 5) & 0x1F) * 0xFF / 0x1F) & 0xFF) as u8;
                        let b = (((pixel & 0x1F) * 0xFF / 0x1F) & 0xFF) as u8;
                        let a = 0xFF;

                        output[4 * ((y + ty) * 96 + (x + tx))] = r;
                        output[4 * ((y + ty) * 96 + (x + tx)) + 1] = g;
                        output[4 * ((y + ty) * 96 + (x + tx)) + 2] = b;
                        output[4 * ((y + ty) * 96 + (x + tx)) + 3] = a;
                    }
                }
            }
        }
        output
    }
}

/// Trims null and whitespace characters (in that order) from the given string.
fn trim(string: &str) -> &str {
    string.trim_matches('\0').trim()
}
