/*
    wearechat - generic multiplayer game with voip
    Copyright (C) 2025 metamuffin
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, version 3 of the License only.
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.
    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see .
*/
#![feature(iter_array_chunks)]
pub mod mesh;
use anyhow::{Result, bail};
use clap::Parser;
use gltf::{Gltf, image::Source, import_buffers};
use image::{ImageReader, codecs::webp::WebPEncoder};
use log::info;
use mesh::import_mesh;
use rand::random;
use std::{
    fs::File,
    io::{Cursor, Read, Write},
    net::{SocketAddr, TcpStream},
    path::{Path, PathBuf},
    thread::{self, sleep},
    time::Duration,
};
use weareshared::{
    Vec3A,
    helper::ReadWrite,
    packets::{Data, Object, Packet, Resource},
    resources::{EnvironmentPart, Image, LightPart, Prefab},
    store::ResourceStore,
    vec3a,
};
#[derive(Parser)]
struct Args {
    address: SocketAddr,
    /// Path to a glTF file, binary or json format
    scene: PathBuf,
    /// Send all resources to the server then quit
    #[arg(short, long)]
    push: bool,
    /// Spin the object
    #[arg(long)]
    spin: bool,
    /// Remove all other object from the world
    #[arg(short, long)]
    clear: bool,
    /// Add the object to the world
    #[arg(short, long)]
    add: bool,
    /// Transcode all textures to WebP
    #[arg(short, long)]
    webp: bool,
    /// Add skybox
    #[arg(short, long)]
    skybox: Option,
    /// Override prefab name
    #[arg(short, long)]
    name: Option,
}
fn main() -> Result<()> {
    env_logger::init_from_env("LOG");
    let args = Args::parse();
    let mut sock = TcpStream::connect(args.address)?;
    let store = ResourceStore::new_memory();
    Packet::Connect(random()).write(&mut sock)?;
    let path_base = args.scene.parent().unwrap();
    let mut gltf = Gltf::from_reader_without_validation(File::open(&args.scene)?)?;
    let blob = gltf.blob.take();
    let buffers = import_buffers(&gltf, Some(path_base), blob)?;
    let mut prefab = Prefab::default();
    prefab.name = args.name.or(gltf
        .default_scene()
        .map(|n| n.name())
        .flatten()
        .map(|n| n.to_owned()));
    for node in gltf.nodes() {
        if let Some(mesh) = node.mesh() {
            import_mesh(
                mesh,
                &buffers,
                &store,
                path_base,
                &node,
                &mut prefab,
                args.webp,
            )?;
        }
        let (position, _, _) = node.transform().decomposed();
        if let Some(light) = node.light() {
            let emission = Some(Vec3A::from_array(light.color()) * light.intensity());
            prefab.light.push((
                Vec3A::from_array(position),
                store.set(&LightPart {
                    emission,
                    ..Default::default()
                })?,
            ));
        }
    }
    if let Some(skybox) = args.skybox {
        let mut buf = Vec::new();
        File::open(skybox)?.read_to_end(&mut buf)?;
        prefab.environment = Some(store.set(&EnvironmentPart {
            skybox: Some(store.set(&Image(buf))?),
            ..Default::default()
        })?);
    }
    let pres = store.set(&prefab)?;
    Packet::AnnouncePrefab(pres.clone()).write(&mut sock)?;
    sock.flush()?;
    let ob = if args.add {
        let ob = Object::new();
        Packet::Add(ob, pres.clone()).write(&mut sock)?;
        sock.flush()?;
        Some(ob)
    } else {
        None
    };
    if args.spin {
        let ob = ob.clone().unwrap();
        let mut sock2 = sock.try_clone().unwrap();
        thread::spawn(move || {
            let mut x = 0.;
            loop {
                Packet::Position(ob, Vec3A::ZERO, vec3a(x, x * 0.3, x * 0.1))
                    .write(&mut sock2)
                    .unwrap();
                sock2.flush().unwrap();
                x += 0.1;
                sleep(Duration::from_millis(50));
            }
        });
    }
    if args.push {
        store.iter(|d| {
            Packet::RespondResource(Data(d.to_vec()))
                .write(&mut sock)
                .unwrap();
        })?;
        sock.flush()?;
    } else {
        loop {
            let packet = Packet::read(&mut sock)?;
            match packet {
                Packet::RequestResource(hash) => {
                    if let Some(d) = store.get_raw(hash)? {
                        Packet::RespondResource(Data(d)).write(&mut sock)?;
                        sock.flush()?;
                    }
                }
                Packet::Add(ob_a, _) => {
                    if args.clear {
                        if Some(ob_a) != ob {
                            info!("removing object {ob_a}");
                            Packet::Remove(ob_a).write(&mut sock)?;
                            sock.flush()?;
                        }
                    }
                }
                _ => (),
            }
        }
    }
    Ok(())
}
fn load_texture(
    name: &str,
    store: &ResourceStore,
    path: &Path,
    buffers: &[gltf::buffer::Data],
    source: &Source,
    webp: bool,
) -> Result> {
    let mut image = match source {
        gltf::image::Source::View { view, mime_type } => {
            info!("{name} texture is embedded and of type {mime_type:?}");
            let buf =
                &buffers[view.buffer().index()].0[view.offset()..view.offset() + view.length()];
            Image(buf.to_vec())
        }
        gltf::image::Source::Uri {
            uri,
            mime_type: Some(mime_type),
        } => {
            info!("{name} texture is {uri:?} and of type {mime_type:?}");
            let path = path.join(uri);
            let mut buf = Vec::new();
            File::open(path)?.read_to_end(&mut buf)?;
            Image(buf)
        }
        _ => {
            bail!("texture is external and has no mime type")
        }
    };
    if webp {
        let mut image_out = Image(Vec::new());
        let len = image.0.len();
        ImageReader::new(Cursor::new(image.0))
            .with_guessed_format()?
            .decode()?
            .write_with_encoder(WebPEncoder::new_lossless(&mut image_out.0))?;
        info!("webp encode: {len} -> {}", image_out.0.len());
        image = image_out;
    }
    Ok(store.set(&image)?)
}