use crate::server::{InnerServerState, ServerState, SurfaceRole};
use crate::{X11Selection, XConnection};

use ab_glyph::{Font, FontRef, Glyph, PxScaleFont, ScaleFont};
use hecs::{CommandBuffer, Entity, World};
use log::{error, warn};
use smithay_client_toolkit::registry::SimpleGlobal;
use smithay_client_toolkit::shm::slot::SlotPool;
use std::borrow::Cow;
use std::sync::LazyLock;
use tiny_skia::{Color, Paint, PathBuilder, Pixmap, Stroke, Transform};
use tiny_skia::{ColorU8, Rect};
use wayland_client::Proxy;
use wayland_client::protocol::wl_seat::WlSeat;
use wayland_client::protocol::wl_shm;
use wayland_client::protocol::wl_subsurface::WlSubsurface;
use wayland_client::protocol::wl_surface::WlSurface;
use wayland_protocols::wp::viewporter::client::wp_viewport::WpViewport;
use wayland_protocols::xdg::decoration::zv1::client::zxdg_toplevel_decoration_v1::ZxdgToplevelDecorationV1;
use wayland_protocols::xdg::shell::client::xdg_toplevel::XdgToplevel;
use xcb::x;

#[derive(Debug)]
pub struct DecorationsData {
    pub wl: Option<ZxdgToplevelDecorationV1>,
    // Boxed to avoid making ToplevelData so much bigger than PopupData
    pub satellite: Option<Box<DecorationsDataSatellite>>,
}

pub struct DecorationMarker {
    pub parent: Entity,
}

#[derive(Debug)]
pub struct DecorationsDataSatellite {
    surface: WlSurface,
    subsurface: WlSubsurface,
    pool: Entity,
    viewport: WpViewport,
    scale: f32,
    pixmap: Pixmap,
    x_data: DecorationsBox,
    title: Option<String>,
    title_rect: Rect,
    should_draw: bool,
    remove_buffer: bool,
}

impl Drop for DecorationsDataSatellite {
    fn drop(&mut self) {
        self.subsurface.destroy();
        self.surface.destroy();
        self.viewport.destroy();
    }
}

impl DecorationsDataSatellite {
    pub const TITLEBAR_HEIGHT: i32 = 25;

    pub fn try_new(
        state: &InnerServerState<impl X11Selection>,
        parent: &WlSurface,
        title: Option<&str>,
    ) -> Option<(Box<Self>, Option<CommandBuffer>)> {
        let mut new_pool = None;
        let mut query = state.world.query::<&SlotPool>();
        let pool_entity = if let Some((pool_entity, _)) = query.into_iter().next() {
            pool_entity
        } else {
            new_pool = Some(
                SlotPool::new(1, &SimpleGlobal::from_bound(state.shm.clone()))
                    .inspect_err(|e| {
                        warn!("Couldn't create slot pool for decorations: {e:?}");
                    })
                    .ok()?,
            );
            state.world.reserve_entity()
        };

        let surface = state.compositor.create_surface(
            &state.qh,
            DecorationMarker {
                parent: parent.data().copied().unwrap(),
            },
        );
        let subsurface = {
            state
                .subcompositor
                .get_subsurface(&surface, parent, &state.qh, ())
        };
        subsurface.set_position(0, -Self::TITLEBAR_HEIGHT);
        let viewport = state.viewporter.get_viewport(&surface, &state.qh, ());

        Some((
            Self {
                surface,
                subsurface,
                pool: pool_entity,
                viewport,
                x_data: DecorationsBox::default(),
                pixmap: Pixmap::new(1, 1).unwrap(),
                scale: 1.0,
                title: title.map(str::to_string),
                title_rect: Rect::from_ltrb(0.0, 0.0, 0.0, 0.0).unwrap(),
                should_draw: true,
                remove_buffer: false,
            }
            .into(),
            new_pool.map(|p| {
                let mut buf = CommandBuffer::new();
                buf.insert_one(pool_entity, p);
                buf
            }),
        ))
    }

    fn pool<'a>(&self, world: &'a World) -> hecs::RefMut<'a, SlotPool> {
        world.get::<&mut SlotPool>(self.pool).unwrap()
    }

    fn update_buffer(&mut self, world: &World) {
        let mut pool = self.pool(world);
        let (buffer, data) = match pool.create_buffer(
            self.pixmap.width() as i32,
            self.pixmap.height() as i32,
            self.pixmap.width() as i32 * 4,
            wl_shm::Format::Xrgb8888,
        ) {
            Ok(b) => b,
            Err(err) => {
                error!("Failed to create buffer for decorations: {err:?}");
                return;
            }
        };

        draw_pixmap_to_buffer(&self.pixmap, data);
        buffer.attach_to(&self.surface).unwrap();
        self.surface.commit();
    }

    #[must_use]
    pub fn will_draw_decorations(&self, width: i32) -> bool {
        width > 0 && self.should_draw
    }

    pub fn draw_decorations(&mut self, world: &World, width: i32, parent_scale_factor: f32) {
        if !self.will_draw_decorations(width) {
            if self.remove_buffer {
                self.surface.attach(None, 0, 0);
                self.surface.commit();
                self.remove_buffer = false;
            }
            return;
        }

        self.scale = parent_scale_factor;
        let mut drawn_width = (width as f32 * self.scale).ceil() as i32;
        let drawn_height = (Self::TITLEBAR_HEIGHT as f32 * self.scale).ceil() as i32;

        let x = x_pixmap(drawn_height as u32, self.scale, self.x_data.hovered);
        if x.width() > drawn_width as u32 {
            drawn_width = x.width() as i32;
        }

        let title = self.title.as_ref().and_then(|t| {
            let width = (drawn_width as u32).saturating_sub(x.width());
            if width > 0 {
                title_pixmap(t, width, drawn_height as u32, self.scale)
            } else {
                None
            }
        });

        // Draw the bar and its components
        let mut bar = Pixmap::new(drawn_width as u32, drawn_height as u32).unwrap();
        bar.fill(Color::WHITE);

        if let Some(title) = title {
            bar.draw_pixmap(
                0,
                0,
                title.as_ref(),
                &Default::default(),
                Transform::identity(),
                None,
            );
            self.title_rect =
                Rect::from_xywh(0.0, 0.0, title.width() as f32, title.height() as f32).unwrap();
        }

        bar.draw_pixmap(
            (bar.width() - x.width()) as i32,
            0,
            x.as_ref(),
            &Default::default(),
            Transform::identity(),
            None,
        );
        self.x_data = DecorationsBox {
            rect: Rect::from_ltrb(
                width as f32 - Self::TITLEBAR_HEIGHT as f32,
                0.0,
                width as f32,
                Self::TITLEBAR_HEIGHT as f32,
            )
            .unwrap(),
            hovered: false,
        };

        self.pixmap = bar;
        self.viewport.set_destination(width, Self::TITLEBAR_HEIGHT);
        self.update_buffer(world);
    }

    fn redraw_x_pixmap(&mut self, world: &World) {
        let x = x_pixmap(self.pixmap.height(), self.scale, self.x_data.hovered);

        self.pixmap.draw_pixmap(
            (self.pixmap.width() - x.width()) as i32,
            0,
            x.as_ref(),
            &Default::default(),
            Transform::identity(),
            None,
        );

        self.surface.damage_buffer(
            (self.pixmap.width() - x.width()) as i32,
            0,
            x.width() as i32,
            x.height() as i32,
        );
        self.update_buffer(world);
    }

    pub fn set_title(&mut self, world: &World, title: &str) {
        self.title = Some(title.to_string());
        if !self.should_draw {
            return;
        }

        // Don't draw title if there's not enough space
        let title_pixmap = title_pixmap(
            title,
            self.pixmap.width() - self.x_data.rect.width() as u32,
            self.pixmap.height(),
            self.scale,
        );

        let new_title_rect = title_pixmap
            .as_ref()
            .map(|p| Rect::from_xywh(0.0, 0.0, p.width() as f32, p.height() as f32).unwrap())
            .unwrap_or_else(|| Rect::from_ltrb(0.0, 0.0, 0.0, 0.0).unwrap());

        let last_title_rect = std::mem::replace(&mut self.title_rect, new_title_rect);

        // Clear last title with white
        let mut paint = Paint::default();
        paint.set_color(Color::WHITE);
        self.pixmap
            .fill_rect(last_title_rect, &paint, Transform::identity(), None);

        if let Some(p) = title_pixmap.as_ref() {
            self.pixmap.draw_pixmap(
                0,
                0,
                p.as_ref(),
                &Default::default(),
                Transform::identity(),
                None,
            );
        }

        let damaged_width = last_title_rect
            .width()
            .max(title_pixmap.map(|p| p.width() as f32).unwrap_or(0.0));

        self.surface
            .damage_buffer(0, 0, damaged_width as i32, last_title_rect.height() as i32);
        self.update_buffer(world);
    }

    pub fn handle_fullscreen(&mut self, fullscreen: bool) {
        if self.should_draw == fullscreen {
            self.should_draw = !fullscreen;
            self.remove_buffer = fullscreen;
        }
    }

    fn handle_motion(&mut self, world: &World, x: f64, y: f64) {
        if self.x_data.check_hovered(x as f32, y as f32) {
            self.redraw_x_pixmap(world);
        }
    }

    fn handle_leave(&mut self, world: &World) {
        if self.x_data.hovered {
            self.x_data.hovered = false;
            self.redraw_x_pixmap(world);
        }
    }

    /// Returns true if the toplevel should be closed
    fn handle_click(&self, toplevel: &XdgToplevel, seat: &WlSeat, serial: u32) -> bool {
        if self.x_data.hovered {
            true
        } else {
            toplevel._move(seat, serial);
            false
        }
    }
}

#[derive(Debug)]
struct DecorationsBox {
    rect: Rect,
    hovered: bool,
}

impl Default for DecorationsBox {
    fn default() -> Self {
        Self {
            rect: Rect::from_xywh(0.0, 0.0, 0.0, 0.0).unwrap(),
            hovered: false,
        }
    }
}

impl DecorationsBox {
    /// Returns true if hover state changed.
    fn check_hovered(&mut self, x: f32, y: f32) -> bool {
        let old_hovered = self.hovered;
        self.hovered = (self.rect.left()..=self.rect.right()).contains(&x)
            && (self.rect.top()..=self.rect.bottom()).contains(&y);

        old_hovered != self.hovered
    }
}

fn draw_pixmap_to_buffer(pixmap: &Pixmap, buffer: &mut [u8]) {
    // TODO: support big endian?
    for (data, pixel) in buffer.chunks_exact_mut(4).zip(pixmap.pixels()) {
        data[0] = pixel.blue();
        data[1] = pixel.green();
        data[2] = pixel.red();
        data[3] = pixel.alpha();
    }
}

fn x_pixmap(bar_height: u32, scale: f32, hovered: bool) -> Pixmap {
    let mut x = Pixmap::new(bar_height, bar_height).unwrap();
    if hovered {
        x.fill(Color::from_rgba(1.0, 0.0, 0.0, 0.8).unwrap());
    } else {
        x.fill(Color::WHITE);
    }
    let size = x.width() as f32;
    let margin = 8.4 * scale;

    let mut line = PathBuilder::new();
    line.move_to(margin, margin);
    line.line_to(size - margin, size - margin);
    line.move_to(size - margin, margin);
    line.line_to(margin, size - margin);
    let line = line.finish().unwrap();
    x.stroke_path(
        &line,
        &Default::default(),
        &Stroke {
            width: scale + 0.5,
            ..Default::default()
        },
        Default::default(),
        None,
    );

    x
}

fn title_pixmap(title: &str, max_width: u32, height: u32, scale: f32) -> Option<Pixmap> {
    if title.is_empty() {
        return None;
    }

    let (glyphs, font) = layout_title_glyphs(title, max_width, height, scale);

    let width = glyphs
        .last()
        .map(|g| g.position.x + font.h_advance(g.id))?
        .ceil() as u32;

    let mut pixmap = Pixmap::new(width, height).unwrap();
    let data = pixmap.pixels_mut();

    for glyph in glyphs {
        if let Some(og) = font.outline_glyph(glyph) {
            let bounds = og.px_bounds();
            og.draw(|x, y, coverage| {
                let pixel_idx =
                    ((bounds.min.x as u32 + x) + (bounds.min.y as u32 + y) * width) as usize;

                data[pixel_idx] =
                    ColorU8::from_rgba(0, 0, 0, (coverage * 255.0) as u8).premultiply();
            });
        }
    }

    Some(pixmap)
}

static FONT_DATA: LazyLock<Cow<'static, [u8]>> = LazyLock::new(|| {
    #[cfg(feature = "fontconfig")]
    {
        let fc = fontconfig::Fontconfig::new().expect("Failed to initialize fontconfig.");
        let font = fc
            .find("opensans", None)
            .expect("Failed to load Open Sans Regular.");
        let data = std::fs::read(font.path).expect("Failed to read font from disk.");
        Cow::Owned(data)
    }
    #[cfg(not(feature = "fontconfig"))]
    {
        Cow::Borrowed(include_bytes!("../../OpenSans-Regular.ttf"))
    }
});

static FONT: LazyLock<FontRef<'_>> =
    LazyLock::new(|| FontRef::try_from_slice(FONT_DATA.as_ref()).unwrap());

fn layout_title_glyphs(
    text: &str,
    max_width: u32,
    height: u32,
    scale: f32,
) -> (Vec<Glyph>, PxScaleFont<&FontRef<'_>>) {
    const TEXT_SIZE: f32 = 10.0;
    const TEXT_MARGIN: f32 = 11.0;

    let mut ret = Vec::<Glyph>::new();

    let px_scale = FONT.pt_to_px_scale(TEXT_SIZE * scale).unwrap();
    let font = FONT.as_scaled(px_scale);
    for c in text.chars() {
        let mut glyph = font.scaled_glyph(c);
        // This centers the glyphs vertically
        glyph.position.y = (height as f32 / 2.0) - font.descent();
        if let Some(previous) = ret.last() {
            glyph.position.x = previous.position.x
                + font.h_advance(previous.id)
                + font.kern(glyph.id, previous.id);
        } else {
            glyph.position.x = TEXT_MARGIN * scale;
        }
        if (glyph.position.x + font.h_advance(glyph.id)).ceil() as u32 > max_width {
            break;
        }

        ret.push(glyph);
    }

    (ret, font)
}

fn get_decoration(
    world: &World,
    parent: Entity,
) -> Option<hecs::RefMut<'_, Box<DecorationsDataSatellite>>> {
    let role = world.get::<&mut SurfaceRole>(parent).ok()?;
    Some(hecs::RefMut::map(role, |role| {
        let SurfaceRole::Toplevel(Some(toplevel)) = &mut *role else {
            unreachable!()
        };

        toplevel.decoration.satellite.as_mut().unwrap()
    }))
}

pub fn handle_pointer_leave(state: &InnerServerState<impl X11Selection>, parent: Entity) {
    if let Some(mut decoration) = get_decoration(&state.world, parent) {
        decoration.handle_leave(&state.world);
    }
}

pub fn handle_pointer_motion(
    state: &InnerServerState<impl X11Selection>,
    parent: Entity,
    surface_x: f64,
    surface_y: f64,
) {
    if let Some(mut decoration) = get_decoration(&state.world, parent) {
        decoration.handle_motion(&state.world, surface_x, surface_y);
    }
}

pub fn handle_pointer_click(
    state: &mut ServerState<impl XConnection>,
    parent: Entity,
    seat: &WlSeat,
    serial: u32,
) {
    let Ok(mut role) = state.world.get::<&mut SurfaceRole>(parent) else {
        return;
    };
    let SurfaceRole::Toplevel(Some(toplevel)) = &mut *role else {
        unreachable!()
    };

    if toplevel
        .decoration
        .satellite
        .as_mut()
        .unwrap()
        .handle_click(&toplevel.toplevel, seat, serial)
    {
        let window = *state.world.get::<&x::Window>(parent).unwrap();
        drop(role);
        state.close_x_window(window);
    }
}
