// SPDX-License-Identifier: MPL-2.0

use std::io::{BufWriter, Write};
use std::os::unix::net::UnixStream;
use std::sync::Arc;

use anyhow::{Result, bail};
use sctk::reexports::client::protocol::wl_shm;
use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::shm::multi::MultiPool;
use smithay::backend::renderer::{BufferType, buffer_type};
use smithay::reexports::wayland_server::{self, Client};
use smithay::wayland::compositor::BufferAssignment;
use smithay::wayland::shm::{BufferData, with_buffer_contents};

use crate::xdg_shell_wrapper::client_state::WrapperClientCompositorState;

/// utility function which maps a value [0, 1] -> [0, 1] using the smootherstep
/// function
pub fn smootherstep(t: f32) -> f32 {
    (6.0 * t.powi(5) - 15.0 * t.powi(4) + 10.0 * t.powi(3)).clamp(0.0, 1.0)
}

/// helper function for inserting a wrapped applet client
pub fn get_client_sock(display: &mut wayland_server::DisplayHandle) -> (Client, UnixStream) {
    let (display_sock, client_sock) = UnixStream::pair().unwrap();

    (
        display
            .insert_client(
                display_sock,
                Arc::new(WrapperClientCompositorState { compositor_state: Default::default() }),
            )
            .unwrap(),
        client_sock,
    )
}

pub(crate) fn write_and_attach_buffer(
    buffer_assignment: &BufferAssignment,
    cursor_surface: &WlSurface,
    multipool_ctr: usize,
    multipool: &mut MultiPool<(WlSurface, usize)>,
) -> Result<()> {
    if let BufferAssignment::NewBuffer(source_buffer) = buffer_assignment {
        if let Some(BufferType::Shm) = buffer_type(source_buffer) {
            with_buffer_contents(
                source_buffer,
                move |from: *const u8, length: usize, buffer_metadata: BufferData| {
                    if let Ok(format) = wl_shm::Format::try_from(buffer_metadata.format as u32) {
                        let BufferData { offset, width, height, stride, .. } = buffer_metadata;
                        let (_, buff, to) = if let Some(res) = multipool.get(
                            width,
                            stride,
                            height,
                            &(cursor_surface.clone(), multipool_ctr),
                            format,
                        ) {
                            res
                        } else if let Ok(res) = multipool.create_buffer(
                            width,
                            stride,
                            height,
                            &(cursor_surface.clone(), multipool_ctr),
                            format,
                        ) {
                            res
                        } else {
                            bail!("Failed to create buffer");
                        };
                        let mut writer = BufWriter::new(to);
                        let from: &[u8] = unsafe { std::slice::from_raw_parts(from, length) };
                        let offset: usize = offset.try_into()?;
                        let height: usize = height.try_into()?;
                        let stride: usize = stride.try_into()?;

                        writer.write_all(&from[offset..(offset + height * stride)])?;
                        writer.flush()?;

                        cursor_surface.attach(Some(buff), 0, 0);
                        cursor_surface.damage(0, 0, i32::MAX, i32::MAX);
                        cursor_surface.commit();

                        Ok(())
                    } else {
                        bail!("Unsupported format!")
                    }
                },
            )?
        } else {
            bail!("Not an shm buffer")
        }
    } else {
        cursor_surface.attach(None, 0, 0);
        cursor_surface.damage(0, 0, i32::MAX, i32::MAX);
        cursor_surface.commit();
        bail!("Missing new buffer.")
    }
}
