use std::iter::Iterator;

use glc::{
    error::Error as GlcError,
    shader::{Program, Shader, Type},
    texture::{Data, FilterMag, FilterMin, Target, Texture, Wrap},
};
use space_crush_common::misc::Vfs;
use specs::World;

use crate::Error;

/* WorldHelper */

pub trait WorldHelper {
    fn load_program<I>(&self, iter: I) -> Result<Program, Error>
    where
        I: IntoIterator<Item = (Type, &'static str)>;

    fn load_texture(&self, path: &str) -> Result<Texture, Error>;
}

impl WorldHelper for World {
    fn load_program<I>(&self, iter: I) -> Result<Program, Error>
    where
        I: IntoIterator<Item = (Type, &'static str)>,
    {
        let vfs = self.fetch::<Vfs>();

        Program::from_shaders_result(iter.into_iter().map(|(t, p)| {
            let path = vfs.join(p)?;
            let mut file = path.open_file()?;
            let path = path.parent().unwrap();
            let shader = Shader::from_reader(t, &mut file, |include| {
                let p = if include.starts_with('.') {
                    path.join(include)
                } else {
                    vfs.join(include)
                };

                let p = match p {
                    Ok(p) => p,
                    Err(err) => return Err(GlcError::ShaderInclude(format!("{}", err))),
                };

                let mut r = match p.open_file() {
                    Ok(r) => r,
                    Err(err) => return Err(GlcError::ShaderInclude(format!("{}", err))),
                };

                let mut code = String::default();
                if let Err(err) = r.read_to_string(&mut code) {
                    return Err(GlcError::ShaderInclude(format!("{}", err)));
                }

                Ok(code)
            })?;

            Ok(shader)
        }))
    }

    fn load_texture(&self, path: &str) -> Result<Texture, Error> {
        let vfs = self.fetch::<Vfs>();

        let mut file = vfs.join(path)?.open_file()?;
        let mut data = Data::from_reader(&mut file)?;
        data.convert_to(data.format().info().format_gl_support)?;

        let mut texture = Texture::new(Target::Texture2D)?;
        texture.upload(&data, true)?;
        texture.set_filter(FilterMin::LinearMipmapLinear, FilterMag::Linear)?;
        texture.set_wrap(
            Wrap::ClampToBorder,
            Wrap::ClampToBorder,
            Wrap::ClampToBorder,
        )?;

        Ok(texture)
    }
}
