//! Terrain layer reception, wind state, and PBR terrain material capabilities.

#![allow(clippy::missing_errors_doc)]
#![allow(clippy::must_use_candidate)]
#![allow(clippy::needless_pass_by_value)]
#![allow(clippy::unnecessary_wraps)] // Mapped constructors have fixed fallible signatures.

use crate::agent_manager::EventRegistry;
use crate::packet_catalog::{GeneratedPacket, PacketType};
use crate::terrain_codec::{TerrainCompressor, TerrainPatch, TerrainPatchGroupHeader};
use crate::{BitPack, Error, GridClient, Simulator, TerrainPatchLayerType};
use futures_channel::oneshot;
use futures_util::future::{Either, select};
use futures_util::pin_mut;
use libremetaverse_structured_data::{OSD, OSDMap, OSDParser};
use libremetaverse_types::Vector2;
use libremetaverse_types::compat::{CancellationToken, EventHandler, Subscription, Uri};
use std::collections::HashMap;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};

const MAX_LAYER_BYTES: usize = 16 * 1024 * 1024;

fn mutex<T>(value: &Mutex<T>) -> std::sync::MutexGuard<'_, T> {
    value
        .lock()
        .unwrap_or_else(std::sync::PoisonError::into_inner)
}

#[derive(Clone)]
pub struct LandPatchReceivedEventArgs {
    simulator: Simulator,
    x: i32,
    y: i32,
    patch_size: i32,
    height_map: Vec<f32>,
}
impl LandPatchReceivedEventArgs {
    pub fn new(
        simulator: Simulator,
        x: i32,
        y: i32,
        patch_size: i32,
        height_map: Vec<f32>,
    ) -> Result<Self, Error> {
        if height_map.len()
            != usize::try_from(patch_size.checked_mul(patch_size).ok_or(Error::Argument)?)
                .map_err(|_| Error::Argument)?
        {
            return Err(Error::Argument);
        }
        Ok(Self {
            simulator,
            x,
            y,
            patch_size,
            height_map,
        })
    }
    pub fn simulator(&self) -> Simulator {
        self.simulator.clone()
    }
    pub fn x(&self) -> i32 {
        self.x
    }
    pub fn y(&self) -> i32 {
        self.y
    }
    pub fn patch_size(&self) -> i32 {
        self.patch_size
    }
    pub fn height_map(&self) -> Vec<f32> {
        self.height_map.clone()
    }
}

pub(crate) struct TerrainManagerInner {
    client: Arc<GridClient>,
    events: EventRegistry<LandPatchReceivedEventArgs>,
    raw_subscription: Mutex<Option<Subscription>>,
    disposed: AtomicBool,
}

#[derive(Clone)]
pub struct TerrainManager {
    pub(crate) inner: Arc<TerrainManagerInner>,
}
impl TerrainManager {
    pub const TERRAIN_MATERIAL_SLOT_COUNT: i32 = 4;
    pub fn new(client: GridClient) -> Result<Self, Error> {
        Self::native_new(Arc::new(client))
    }
    pub(crate) fn native_new(client: Arc<GridClient>) -> Result<Self, Error> {
        if let Some(inner) = client.cached_terrain_manager_inner() {
            return Ok(Self { inner });
        }
        let inner = Arc::new(TerrainManagerInner {
            client: Arc::clone(&client),
            events: EventRegistry::default(),
            raw_subscription: Mutex::new(None),
            disposed: AtomicBool::new(false),
        });
        let weak = Arc::downgrade(&inner);
        *mutex(&inner.raw_subscription) = Some(client.network().subscribe_raw_packet(Arc::new(
            move |event| {
                if let Some(inner) = weak.upgrade() {
                    inner.handle_layer(event);
                }
            },
        )));
        Ok(Self {
            inner: client.install_terrain_manager_inner(inner),
        })
    }
    pub fn subscribe_land_patch_received(
        &self,
        handler: EventHandler<LandPatchReceivedEventArgs>,
    ) -> Subscription {
        self.inner.events.subscribe(handler)
    }
    pub fn dispose(&self) -> Result<(), Error> {
        self.inner.disposed.store(true, Ordering::Release);
        mutex(&self.inner.raw_subscription).take();
        Ok(())
    }

    async fn capability(
        &self,
        name: &str,
        token: &CancellationToken,
    ) -> Result<Option<Uri>, Error> {
        if self.inner.disposed.load(Ordering::Acquire) {
            return Err(Error::InvalidOperation);
        }
        let Some(caps) = self
            .inner
            .client
            .network()
            .current_sim()
            .and_then(|sim| sim.native_caps())
        else {
            return Ok(None);
        };
        if let Some(uri) = caps.capability_uri(name.into())? {
            return Ok(Some(uri));
        }
        if caps.seed_request_finished() {
            return Ok(None);
        }
        let (sender, receiver) = oneshot::channel();
        let sender = Arc::new(Mutex::new(Some(sender)));
        let wake = Arc::clone(&sender);
        let _subscription = caps.subscribe_capabilities_received(Some(Arc::new(move |_| {
            if let Some(sender) = mutex(&wake).take() {
                let _ = sender.send(());
            }
        })));
        if let Some(uri) = caps.capability_uri(name.into())? {
            return Ok(Some(uri));
        }
        let cancellation = token.cancelled();
        pin_mut!(receiver, cancellation);
        match select(receiver, cancellation).await {
            Either::Left(_) => caps.capability_uri(name.into()),
            Either::Right(_) => Err(Error::Cancelled),
        }
    }
    pub async fn get_terrain_material_overrides(
        &self,
        cancellation_token: Option<CancellationToken>,
    ) -> Result<Option<Vec<Option<crate::assets::AssetMaterial>>>, Error> {
        let token = cancellation_token.unwrap_or_default();
        let Some(uri) = self.capability("ModifyRegion", &token).await? else {
            return Ok(None);
        };
        let (response, bytes) = self
            .inner
            .client
            .native_http_caps_client()
            .get(uri, token, None)
            .await?;
        if !(200..300).contains(&response.status_code) || bytes.len() > MAX_LAYER_BYTES {
            return Ok(None);
        }
        let OSD::Map(map) = OSDParser::deserialize_with_bytes(bytes)? else {
            return Ok(None);
        };
        if !map
            .get("success")
            .and_then(|value| value.as_boolean().ok())
            .unwrap_or(false)
        {
            return Ok(None);
        }
        let Some(OSD::Array(overrides)) = map.get("overrides") else {
            return Ok(None);
        };
        if overrides.len() < 4 || overrides.len() > 1024 {
            return Ok(None);
        }
        let mut result = Vec::with_capacity(4);
        for value in overrides.iter().take(4) {
            match value {
                OSD::Map(values) if !values.is_empty() => {
                    result.push(Some(crate::assets::AssetMaterial::from_override_osd(
                        OSDMap::new_with_dictionary(values.clone())?,
                    )?));
                }
                _ => result.push(None),
            }
        }
        Ok(Some(result))
    }
    pub async fn set_terrain_material_overrides(
        &self,
        overrides: Vec<Option<crate::assets::AssetMaterial>>,
        cancellation_token: Option<CancellationToken>,
    ) -> Result<bool, Error> {
        if overrides.len() != 4 {
            return Err(Error::Argument);
        }
        let token = cancellation_token.unwrap_or_default();
        let Some(uri) = self.capability("ModifyRegion", &token).await? else {
            return Ok(false);
        };
        let mut wire = Vec::with_capacity(4);
        for material in overrides {
            wire.push(match material {
                Some(value) => OSD::Map(value.to_override_osd()?.snapshot()),
                None => OSD::Map(HashMap::new()),
            });
        }
        let body = OSDParser::serialize_llsd_xml_bytes(OSD::Map(HashMap::from([(
            "overrides".into(),
            OSD::Array(wire),
        )])))?;
        if body.len() > MAX_LAYER_BYTES {
            return Err(Error::Argument);
        }
        let (response, bytes) = self
            .inner
            .client
            .native_http_caps_client()
            .post_with_uri_string_bytes_cancellation_token_i_progress(
                uri,
                "application/llsd+xml".into(),
                body,
                token,
                None,
            )
            .await?;
        if !(200..300).contains(&response.status_code) || bytes.len() > MAX_LAYER_BYTES {
            return Ok(false);
        }
        let OSD::Map(map) = OSDParser::deserialize_with_bytes(bytes)? else {
            return Ok(false);
        };
        Ok(map
            .get("success")
            .and_then(|value| value.as_boolean().ok())
            .unwrap_or(false))
    }
}

impl TerrainManagerInner {
    fn handle_layer(&self, event: crate::network_manager::RawPacketReceivedEventArgs) {
        if self.disposed.load(Ordering::Acquire)
            || event.packet_type != PacketType::LayerData
            || event.data.len() > MAX_LAYER_BYTES
        {
            return;
        }
        let mut position = 0;
        let Ok(packet) =
            crate::packets::LayerDataPacket::new_from_bytes(&event.data, &mut position)
        else {
            return;
        };
        let _ = self.decode_layer(packet, event.simulator);
    }
    fn decode_layer(
        &self,
        packet: crate::packets::LayerDataPacket,
        simulator: Simulator,
    ) -> Result<(), Error> {
        if packet.layer_data.data.len() > MAX_LAYER_BYTES {
            return Err(Error::Argument);
        }
        let mut bits = BitPack::new(packet.layer_data.data, 0)?;
        let group = TerrainPatchGroupHeader {
            stride: bits.unpack_bits(16)?,
            patch_size: bits.unpack_bits(8)?,
            type_: TerrainPatchLayerType(
                u8::try_from(bits.unpack_bits(8)?).map_err(|_| Error::Argument)?,
            ),
        };
        if group.patch_size != 16 {
            return Err(Error::Argument);
        }
        match packet.layer_id.type_ {
            76 | 77 => self.decode_land(&mut bits, group, simulator, packet.layer_id.type_ == 77),
            55 | 57 => Self::decode_wind(&mut bits, group, simulator, packet.layer_id.type_ == 57),
            56 | 58 | 87 => Ok(()),
            _ => Err(Error::Argument),
        }
    }
    fn decode_land(
        &self,
        bits: &mut BitPack,
        group: TerrainPatchGroupHeader,
        simulator: Simulator,
        large: bool,
    ) -> Result<(), Error> {
        let mut count = 0usize;
        loop {
            let header = TerrainCompressor::decode_header(bits, large)?;
            if header.quant_w_bits == TerrainCompressor::END_OF_PATCHES {
                break;
            }
            if !large && (!(0..16).contains(&header.x()) || !(0..16).contains(&header.y())) {
                return Err(Error::Argument);
            }
            count += 1;
            if count > 4096 {
                return Err(Error::Argument);
            }
            let mut coefficients = vec![0; 1024];
            TerrainCompressor::decode_values(&mut coefficients, bits, header, group.patch_size)?;
            let heights = TerrainCompressor::decompress_patch(coefficients, header, group)?;
            simulator.native_store_terrain_patch(
                TerrainPatch {
                    data: heights.clone(),
                    x: header.x(),
                    y: header.y(),
                },
                large,
            )?;
            self.events.emit(LandPatchReceivedEventArgs::new(
                simulator.clone(),
                header.x(),
                header.y(),
                group.patch_size,
                heights,
            )?);
        }
        Ok(())
    }
    fn decode_wind(
        bits: &mut BitPack,
        mut group: TerrainPatchGroupHeader,
        simulator: Simulator,
        large: bool,
    ) -> Result<(), Error> {
        group.stride = group.patch_size;
        let mut axes = Vec::with_capacity(2);
        for _ in 0..2 {
            let header = TerrainCompressor::decode_header(bits, large)?;
            if header.quant_w_bits == TerrainCompressor::END_OF_PATCHES {
                return Err(Error::Argument);
            }
            let mut coefficients = vec![0; 1024];
            TerrainCompressor::decode_values(&mut coefficients, bits, header, group.patch_size)?;
            axes.push(TerrainCompressor::decompress_patch(
                coefficients,
                header,
                group,
            )?);
        }
        simulator.native_store_wind(
            axes[0]
                .iter()
                .zip(&axes[1])
                .take(256)
                .map(|(x, y)| Vector2 { x: *x, y: *y })
                .collect(),
        );
        Ok(())
    }
}

#[cfg(test)]
#[allow(clippy::cast_precision_loss, clippy::float_cmp)] // Bounded terrain fixtures intentionally use f32.
mod tests {
    use super::*;

    #[test]
    fn land_event_validates_patch_dimensions() {
        let client = GridClient::new().unwrap();
        let sim = Simulator::new(client, "127.0.0.1:13".parse().unwrap(), 0, None, None).unwrap();
        assert!(LandPatchReceivedEventArgs::new(sim, 0, 0, 16, vec![0.0; 255]).is_err());
    }

    #[test]
    fn layer_packet_updates_terrain_before_emitting_the_correlated_patch() {
        let client = GridClient::new().unwrap();
        let sim = Simulator::new(
            client.clone(),
            "127.0.0.1:13".parse().unwrap(),
            0,
            None,
            None,
        )
        .unwrap();
        let manager = TerrainManager::new(client).unwrap();
        let observed = Arc::new(Mutex::new(None));
        let captured = Arc::clone(&observed);
        let _subscription = manager.subscribe_land_patch_received(Arc::new(move |event| {
            let mut height = 0.0;
            let stored = event
                .simulator()
                .terrain_height_at_point(event.x() * 16, event.y() * 16, &mut height)
                .unwrap();
            *mutex(&captured) = Some((event.x(), event.y(), stored, height));
        }));
        let source: Vec<f32> = (0..256).map(|index| 32.0 + index as f32 / 64.0).collect();
        let packet = TerrainCompressor::create_land_packet_with_single_array_int32_int32(
            source.clone(),
            5,
            9,
        )
        .unwrap();

        manager.inner.decode_layer(packet, sim).unwrap();

        let observed = mutex(&observed).expect("land event");
        assert_eq!((observed.0, observed.1, observed.2), (5, 9, true));
        assert!((observed.3 - source[0]).abs() < 0.2);
    }

    #[test]
    fn large_region_packets_can_extend_stale_declared_dimensions() {
        let client = GridClient::new().unwrap();
        let sim = Simulator::new(
            client,
            "127.0.0.1:13".parse().unwrap(),
            0,
            Some(512),
            Some(256),
        )
        .unwrap();
        sim.native_store_terrain_patch(
            TerrainPatch {
                data: vec![41.5; 256],
                x: 31,
                y: 15,
            },
            true,
        )
        .unwrap();
        let mut height = 0.0;
        assert!(sim.terrain_height_at_point(511, 255, &mut height).unwrap());
        assert_eq!(height, 41.5);
        sim.native_store_terrain_patch(
            TerrainPatch {
                data: vec![42.5; 256],
                x: 0,
                y: 16,
            },
            true,
        )
        .unwrap();
        assert!(sim.terrain_height_at_point(0, 256, &mut height).unwrap());
        assert_eq!(height, 42.5);
    }
}
