mod compositor;
mod layer_shell;
mod output;
mod pointer;
mod registry;
mod seat;
mod shm;

use crate::oneko::Oneko;
use anyhow::{Result, Context};
use smithay_client_toolkit::{
    compositor::CompositorState,
    delegate_compositor, delegate_layer, delegate_output, delegate_pointer, delegate_registry, delegate_seat, delegate_shm,
    output::OutputState,
    reexports::{
        calloop::EventLoop,
        calloop_wayland_source::WaylandSource,
        client::{
            Connection,
            globals::registry_queue_init,
            protocol::{
                wl_pointer::WlPointer,
                wl_shm::Format,
                wl_surface::WlSurface,
            },
            QueueHandle
        }
    },
    registry::RegistryState,
    seat::SeatState,
    shell::{
        wlr_layer::{
            Anchor,
            Layer as LayerDepth,
            LayerShell,
            LayerSurface
        },
        WaylandSurface
    },
    shm::{
        slot::{
            Buffer,
            SlotPool
        },
        Shm
    }
};
use tracing::trace;

pub struct Layer {
    oneko: Oneko,

    registry_state: RegistryState,
    seat_state: SeatState,
    output_state: OutputState,

    buffer: Option<Buffer>,
    pool: SlotPool,
    shm: Shm,
    layer_surface: LayerSurface,
    surface: WlSurface,
    configured_surface: bool,
    width: u32,
    height: u32,

    pointer: Option<WlPointer>,
    pointer_position: Option<(f64, f64)>,

    // used to capture the event loop outside of the struct
    // bad idea(?) see `src/layer/compositor.rs` and `src/layer/layer_shell.rs` qh not being used for `request_draw`
    // maybe it's fine, but for now: TODO
    qh: QueueHandle<Self>,
    exit: bool,

    error: Option<anyhow::Error>
}

impl Layer {
    pub fn new() -> Result<(Self, EventLoop<'static, Self>)> {
        let connection = Connection::connect_to_env()?;

        let (globals, event_queue) = registry_queue_init(&connection)?;
        let qh = event_queue.handle();
        let event_loop: EventLoop<Layer> = EventLoop::try_new()
            .context("failed to initialize the event loop")?;

        let wayland_source = WaylandSource::new(connection.clone(), event_queue);
        wayland_source
            .insert(event_loop.handle())
            .context("failed to insert wayland into the event loop")?;

        let mut oneko = Oneko::new();
        oneko
            .insert(event_loop.handle())
            // TODO: use ? instead of expect
            // not possible right now because of thread safety yada yada
            .expect("failed to insert oneko into the event loop");

        let shm = Shm::bind(&globals, &qh)
            .context("wl_shm is not available")?;
        let layer_shell = LayerShell::bind(&globals, &qh)
            .context("zwlr_layer_shell_v1 is not available")?;
        let compositor = CompositorState::bind(&globals, &qh)
            .context("wl_compositor is not available")?;

        let pool = SlotPool::new(
            // 4 bytes per pixel, later resized
            // we can just set it to the absolute minimum
            4 as usize,
            &shm
        )
            .context("failed to create a new slot pool")?;

        let surface = compositor.create_surface(&qh);
        let layer_surface = layer_shell.create_layer_surface(&qh, surface.clone(), LayerDepth::Overlay, Some("oneko"), None);

        layer_surface.set_size(0, 0);
        layer_surface.set_anchor(Anchor::all());
        layer_surface.set_margin(0, 0, 0, 0);
        layer_surface.set_exclusive_zone(-1);
        layer_surface.commit();

        Ok((Self {
            oneko: oneko,

            registry_state: RegistryState::new(&globals),
            seat_state: SeatState::new(&globals, &qh),
            output_state: OutputState::new(&globals, &qh),

            buffer: None,
            pool: pool,
            shm: shm,
            layer_surface: layer_surface,
            surface: surface,
            configured_surface: false,
            // these can be set to 0
            // set by the configure event
            width: 0,
            height: 0,

            pointer: None,
            pointer_position: None,

            qh: qh,
            exit: false,

            error: None
        }, event_loop))
    }

    fn paint(&mut self) {
        trace!("repainting surface fully");

        let width = self.width;
        let height = self.height;
        let stride = width * 4;

        self.pool
            .resize((stride * height * 4) as usize)
            .expect("failed to resize the pool");

        const FORMAT: Format = Format::Argb8888;
        let mut buffer = self.buffer.take().unwrap_or_else(|| {
            self.pool
                .create_buffer(width as i32, height as i32, stride as i32, FORMAT)
                .expect("failed to create a buffer")
                .0
        });

        let canvas = match self.pool.canvas(&buffer) {
            Some(canvas) => canvas,
            None => {
                // this is a rare case
                // if compositor hasn't released the buffer yet, double buffer it
                let (second_buffer, canvas) = self.pool
                    .create_buffer(width as i32, height as i32, stride as i32, FORMAT)
                    .expect("failed to create a buffer");
                buffer = second_buffer;
                canvas
            }
        };

        self.oneko.render(
            canvas,
            (width, height)
        );

        // buffer damaging is not needed
        // this is done in update

        buffer
            .attach_to(&self.surface)
            .expect("failed to attach the buffer to the surface");

        self.layer_surface.commit();
    }

    // this repaints the surface by calling a frame event
    // this is seen by the compositor, which then our loop calls the draw function!
    pub fn request_repaint(&mut self) {
        trace!("requesting repaint");

        let qh = &self.qh;

        self.surface.frame(&qh, self.surface.clone());
        self.layer_surface.commit();
    }

    pub fn update(&mut self) -> bool {
        self.oneko.update(
            // return to center if pointer is not set
            self.pointer_position.unwrap_or((self.width as f64 / 2.0, self.height as f64 / 2.0)),
            (self.width, self.height),
            &self.surface
        )
    }

    pub fn asked_exit(&mut self) -> bool {
        self.exit
    }

    pub fn take_error(&mut self) -> Option<anyhow::Error> {
        self.error.take()
    }
}

delegate_compositor!(Layer);
delegate_output!(Layer);
delegate_shm!(Layer);
delegate_seat!(Layer);
delegate_layer!(Layer);
delegate_registry!(Layer);
delegate_pointer!(Layer);
