use crate::{
    asset_protocols::rig::RigAsset,
    material::domains::{
        screenspace::ScreenSpaceQuadFactory,
        surface::{
            circle::SurfaceCircleFactory, grid::SurfaceGridFactory, quad::SurfaceQuadFactory,
            rig2d::SurfaceRig2dFactory, triangles2d::SurfaceTriangles2dFactory, SurfaceVertexDP,
            SurfaceVertexDPC, SurfaceVertexDPT, SurfaceVertexDPTC, SurfaceVertexDSP,
            SurfaceVertexDSPC, SurfaceVertexDSPT, SurfaceVertexDSPTC, SurfaceVertexP,
            SurfaceVertexPC, SurfaceVertexPT, SurfaceVertexPTC, SurfaceVertexSP, SurfaceVertexSPC,
            SurfaceVertexSPT, SurfaceVertexSPTC,
        },
    },
    mesh::{geometry::Geometry, vertex_factory::StaticVertexFactory, MeshError},
};
use core::assets::{
    asset::Asset,
    database::AssetsDatabase,
    protocol::{AssetLoadResult, AssetProtocol, AssetVariant, Meta},
};
use serde::{Deserialize, Serialize};
use std::str::from_utf8;

#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct MeshVertexData {
    pub texture: bool,
    pub color: bool,
    pub skinning: bool,
    pub deforming: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum SurfaceFactory {
    Quad(SurfaceQuadFactory),
    Grid(SurfaceGridFactory),
    Circle(SurfaceCircleFactory),
    Triangles2d(SurfaceTriangles2dFactory),
    Rig {
        asset: AssetVariant,
        factory: SurfaceRig2dFactory,
    },
}

impl SurfaceFactory {
    pub fn factory(
        &self,
        data: MeshVertexData,
        assets: &AssetsDatabase,
    ) -> Result<StaticVertexFactory, MeshError> {
        let MeshVertexData {
            deforming,
            skinning,
            texture,
            color,
        } = data;
        match self {
            Self::Quad(factory) => match (deforming, skinning, texture, color) {
                (true, true, true, true) => factory.factory::<SurfaceVertexDSPTC>(),
                (true, true, true, false) => factory.factory::<SurfaceVertexDSPT>(),
                (true, true, false, true) => factory.factory::<SurfaceVertexDSPC>(),
                (true, true, false, false) => factory.factory::<SurfaceVertexDSP>(),
                (true, false, true, true) => factory.factory::<SurfaceVertexDPTC>(),
                (true, false, true, false) => factory.factory::<SurfaceVertexDPT>(),
                (true, false, false, true) => factory.factory::<SurfaceVertexDPC>(),
                (true, false, false, false) => factory.factory::<SurfaceVertexDP>(),
                (false, true, true, true) => factory.factory::<SurfaceVertexSPTC>(),
                (false, true, true, false) => factory.factory::<SurfaceVertexSPT>(),
                (false, true, false, true) => factory.factory::<SurfaceVertexSPC>(),
                (false, true, false, false) => factory.factory::<SurfaceVertexSP>(),
                (false, false, true, true) => factory.factory::<SurfaceVertexPTC>(),
                (false, false, true, false) => factory.factory::<SurfaceVertexPT>(),
                (false, false, false, true) => factory.factory::<SurfaceVertexPC>(),
                (false, false, false, false) => factory.factory::<SurfaceVertexP>(),
            },
            Self::Grid(factory) => match (deforming, skinning, texture, color) {
                (true, true, true, true) => factory.factory::<SurfaceVertexDSPTC>(),
                (true, true, true, false) => factory.factory::<SurfaceVertexDSPT>(),
                (true, true, false, true) => factory.factory::<SurfaceVertexDSPC>(),
                (true, true, false, false) => factory.factory::<SurfaceVertexDSP>(),
                (true, false, true, true) => factory.factory::<SurfaceVertexDPTC>(),
                (true, false, true, false) => factory.factory::<SurfaceVertexDPT>(),
                (true, false, false, true) => factory.factory::<SurfaceVertexDPC>(),
                (true, false, false, false) => factory.factory::<SurfaceVertexDP>(),
                (false, true, true, true) => factory.factory::<SurfaceVertexSPTC>(),
                (false, true, true, false) => factory.factory::<SurfaceVertexSPT>(),
                (false, true, false, true) => factory.factory::<SurfaceVertexSPC>(),
                (false, true, false, false) => factory.factory::<SurfaceVertexSP>(),
                (false, false, true, true) => factory.factory::<SurfaceVertexPTC>(),
                (false, false, true, false) => factory.factory::<SurfaceVertexPT>(),
                (false, false, false, true) => factory.factory::<SurfaceVertexPC>(),
                (false, false, false, false) => factory.factory::<SurfaceVertexP>(),
            },
            Self::Circle(factory) => match (deforming, skinning, texture, color) {
                (true, true, true, true) => factory.factory::<SurfaceVertexDSPTC>(),
                (true, true, true, false) => factory.factory::<SurfaceVertexDSPT>(),
                (true, true, false, true) => factory.factory::<SurfaceVertexDSPC>(),
                (true, true, false, false) => factory.factory::<SurfaceVertexDSP>(),
                (true, false, true, true) => factory.factory::<SurfaceVertexDPTC>(),
                (true, false, true, false) => factory.factory::<SurfaceVertexDPT>(),
                (true, false, false, true) => factory.factory::<SurfaceVertexDPC>(),
                (true, false, false, false) => factory.factory::<SurfaceVertexDP>(),
                (false, true, true, true) => factory.factory::<SurfaceVertexSPTC>(),
                (false, true, true, false) => factory.factory::<SurfaceVertexSPT>(),
                (false, true, false, true) => factory.factory::<SurfaceVertexSPC>(),
                (false, true, false, false) => factory.factory::<SurfaceVertexSP>(),
                (false, false, true, true) => factory.factory::<SurfaceVertexPTC>(),
                (false, false, true, false) => factory.factory::<SurfaceVertexPT>(),
                (false, false, false, true) => factory.factory::<SurfaceVertexPC>(),
                (false, false, false, false) => factory.factory::<SurfaceVertexP>(),
            },
            Self::Triangles2d(factory) => match (deforming, skinning, texture, color) {
                (true, true, true, true) => factory.factory::<SurfaceVertexDSPTC>(),
                (true, true, true, false) => factory.factory::<SurfaceVertexDSPT>(),
                (true, true, false, true) => factory.factory::<SurfaceVertexDSPC>(),
                (true, true, false, false) => factory.factory::<SurfaceVertexDSP>(),
                (true, false, true, true) => factory.factory::<SurfaceVertexDPTC>(),
                (true, false, true, false) => factory.factory::<SurfaceVertexDPT>(),
                (true, false, false, true) => factory.factory::<SurfaceVertexDPC>(),
                (true, false, false, false) => factory.factory::<SurfaceVertexDP>(),
                (false, true, true, true) => factory.factory::<SurfaceVertexSPTC>(),
                (false, true, true, false) => factory.factory::<SurfaceVertexSPT>(),
                (false, true, false, true) => factory.factory::<SurfaceVertexSPC>(),
                (false, true, false, false) => factory.factory::<SurfaceVertexSP>(),
                (false, false, true, true) => factory.factory::<SurfaceVertexPTC>(),
                (false, false, true, false) => factory.factory::<SurfaceVertexPT>(),
                (false, false, false, true) => factory.factory::<SurfaceVertexPC>(),
                (false, false, false, false) => factory.factory::<SurfaceVertexP>(),
            },
            Self::Rig { asset, factory } => {
                let rig = match asset {
                    AssetVariant::Id(id) => assets.asset_by_id(*id),
                    AssetVariant::Path(path) => assets.asset_by_path(path),
                }
                .and_then(|asset| asset.get::<RigAsset>());
                let rig = match rig {
                    Some(rig) => rig,
                    None => {
                        return Err(MeshError::Internal(format!(
                            "Could not find Rig asset: {:?}",
                            asset
                        )))
                    }
                };
                let skeleton = match rig.build_skeleton() {
                    Ok(skeleton) => skeleton,
                    Err(error) => {
                        return Err(MeshError::Internal(format!(
                            "Could not get Skeleton from rig asset: {:?}. Error: {:?}",
                            asset, error
                        )))
                    }
                };
                let deformer = rig.deformer();
                match (deforming, skinning, texture, color) {
                    (true, true, true, true) => {
                        factory.factory::<SurfaceVertexDSPTC>(&skeleton, deformer)
                    }
                    (true, true, true, false) => {
                        factory.factory::<SurfaceVertexDSPT>(&skeleton, deformer)
                    }
                    (true, true, false, true) => {
                        factory.factory::<SurfaceVertexDSPC>(&skeleton, deformer)
                    }
                    (true, true, false, false) => {
                        factory.factory::<SurfaceVertexDSP>(&skeleton, deformer)
                    }
                    (true, false, true, true) => {
                        factory.factory::<SurfaceVertexDPTC>(&skeleton, deformer)
                    }
                    (true, false, true, false) => {
                        factory.factory::<SurfaceVertexDPT>(&skeleton, deformer)
                    }
                    (true, false, false, true) => {
                        factory.factory::<SurfaceVertexDPC>(&skeleton, deformer)
                    }
                    (true, false, false, false) => {
                        factory.factory::<SurfaceVertexDP>(&skeleton, deformer)
                    }
                    (false, true, true, true) => {
                        factory.factory::<SurfaceVertexSPTC>(&skeleton, deformer)
                    }
                    (false, true, true, false) => {
                        factory.factory::<SurfaceVertexSPT>(&skeleton, deformer)
                    }
                    (false, true, false, true) => {
                        factory.factory::<SurfaceVertexSPC>(&skeleton, deformer)
                    }
                    (false, true, false, false) => {
                        factory.factory::<SurfaceVertexSP>(&skeleton, deformer)
                    }
                    (false, false, true, true) => {
                        factory.factory::<SurfaceVertexPTC>(&skeleton, deformer)
                    }
                    (false, false, true, false) => {
                        factory.factory::<SurfaceVertexPT>(&skeleton, deformer)
                    }
                    (false, false, false, true) => {
                        factory.factory::<SurfaceVertexPC>(&skeleton, deformer)
                    }
                    (false, false, false, false) => {
                        factory.factory::<SurfaceVertexP>(&skeleton, deformer)
                    }
                }
            }
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SurfaceMeshAsset {
    #[serde(default)]
    pub vertex_data: MeshVertexData,
    pub factory: SurfaceFactory,
}

impl SurfaceMeshAsset {
    pub fn factory(&self, assets: &AssetsDatabase) -> Result<StaticVertexFactory, MeshError> {
        self.factory.factory(self.vertex_data, assets)
    }
}

#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct ScreenSpaceMeshAsset(pub ScreenSpaceQuadFactory);

impl ScreenSpaceMeshAsset {
    pub fn factory(&self) -> Result<StaticVertexFactory, MeshError> {
        self.0.factory()
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GeometryFactory(pub Geometry);

impl GeometryFactory {
    pub fn factory(&self, data: MeshVertexData) -> Result<StaticVertexFactory, MeshError> {
        let MeshVertexData {
            deforming,
            skinning,
            texture,
            color,
        } = data;
        match (deforming, skinning, texture, color) {
            (true, true, true, true) => self.0.factory::<SurfaceVertexDSPTC>(),
            (true, true, true, false) => self.0.factory::<SurfaceVertexDSPT>(),
            (true, true, false, true) => self.0.factory::<SurfaceVertexDSPC>(),
            (true, true, false, false) => self.0.factory::<SurfaceVertexDSP>(),
            (true, false, true, true) => self.0.factory::<SurfaceVertexDPTC>(),
            (true, false, true, false) => self.0.factory::<SurfaceVertexDPT>(),
            (true, false, false, true) => self.0.factory::<SurfaceVertexDPC>(),
            (true, false, false, false) => self.0.factory::<SurfaceVertexDP>(),
            (false, true, true, true) => self.0.factory::<SurfaceVertexSPTC>(),
            (false, true, true, false) => self.0.factory::<SurfaceVertexSPT>(),
            (false, true, false, true) => self.0.factory::<SurfaceVertexSPC>(),
            (false, true, false, false) => self.0.factory::<SurfaceVertexSP>(),
            (false, false, true, true) => self.0.factory::<SurfaceVertexPTC>(),
            (false, false, true, false) => self.0.factory::<SurfaceVertexPT>(),
            (false, false, false, true) => self.0.factory::<SurfaceVertexPC>(),
            (false, false, false, false) => self.0.factory::<SurfaceVertexP>(),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GeometryMeshAsset {
    pub vertex_data: MeshVertexData,
    pub factory: GeometryFactory,
}

impl GeometryMeshAsset {
    pub fn factory(&self) -> Result<StaticVertexFactory, MeshError> {
        self.factory.factory(self.vertex_data)
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum MeshAsset {
    Surface(SurfaceMeshAsset),
    ScreenSpace(ScreenSpaceMeshAsset),
    Geometry(GeometryMeshAsset),
    Raw(StaticVertexFactory),
}

impl MeshAsset {
    pub fn factory(&self, assets: &AssetsDatabase) -> Result<StaticVertexFactory, MeshError> {
        match self {
            Self::Surface(surface) => surface.factory(assets),
            Self::ScreenSpace(screenspace) => screenspace.factory(),
            Self::Geometry(geometry) => geometry.factory(),
            Self::Raw(factory) => Ok(factory.to_owned()),
        }
    }
}

pub struct MeshAssetProtocol;

impl AssetProtocol for MeshAssetProtocol {
    fn name(&self) -> &str {
        "mesh"
    }

    fn on_load_with_path(&mut self, path: &str, data: Vec<u8>) -> AssetLoadResult {
        let mesh = if path.ends_with(".json") {
            let data = from_utf8(&data).unwrap();
            serde_json::from_str::<MeshAsset>(data).unwrap()
        } else {
            bincode::deserialize::<MeshAsset>(&data).unwrap()
        };
        if let MeshAsset::Surface(asset) = &mesh {
            if let SurfaceFactory::Rig {
                asset: AssetVariant::Path(name),
                ..
            } = &asset.factory
            {
                let to_load = vec![("rig".to_owned(), format!("rig://{}", name))];
                return AssetLoadResult::Yield(Some(Box::new(mesh)), to_load);
            }
        }
        AssetLoadResult::Data(Box::new(mesh))
    }

    // on_load_with_path() handles loading so this is not needed, so we just make it unreachable.
    fn on_load(&mut self, _data: Vec<u8>) -> AssetLoadResult {
        unreachable!()
    }

    fn on_resume(&mut self, meta: Meta, list: &[(&str, &Asset)]) -> AssetLoadResult {
        let id = list.get(0).unwrap().1.id();
        let mut mesh = *meta.unwrap().downcast::<MeshAsset>().unwrap();
        if let MeshAsset::Surface(asset) = &mut mesh {
            if let SurfaceFactory::Rig { asset, .. } = &mut asset.factory {
                *asset = AssetVariant::Id(id);
            }
        }
        AssetLoadResult::Data(Box::new(mesh))
    }

    fn on_unload(&mut self, asset: &Asset) -> Option<Vec<AssetVariant>> {
        let id = asset.id();
        let mesh = asset.get::<MeshAsset>().unwrap();
        if let MeshAsset::Surface(asset) = &mesh {
            if let SurfaceFactory::Rig { .. } = &asset.factory {
                return Some(vec![AssetVariant::Id(id)]);
            }
        }
        None
    }
}
