use glib::Object;
use gtk::{prelude::*, subclass::prelude::*};
use std::cell::RefCell;

const WIDTH: usize = 601;
const HEIGHT: usize = 601;
const CELL_SIZE: usize = 10;

#[derive(Debug, Default)]
pub struct SandViewPrivate {
    area: RefCell<SandArea>,
}

#[glib::object_subclass]
impl ObjectSubclass for SandViewPrivate {
    const NAME: &'static str = "SandView";
    type Type = SandView;
    type ParentType = gtk::DrawingArea;
}

impl ObjectImpl for SandViewPrivate {}
impl WidgetImpl for SandViewPrivate {}
impl DrawingAreaImpl for SandViewPrivate {}

glib::wrapper! {
    pub struct SandView(ObjectSubclass<SandViewPrivate>) @extends gtk::DrawingArea, gtk::Widget;
}

impl Default for SandView {
    fn default() -> Self {
        let s: Self = Object::builder().build();
        s.set_width_request(WIDTH as i32);
        s.set_height_request(HEIGHT as i32);

        s.set_draw_func({
            let s = s.clone();
            move |_, context, width, height| {
                println!("{} {}", width, height);
                context.set_source_rgb(0., 0., 0.);
                let _ = context.paint();
                context.set_source_rgb(0.1, 0.1, 0.1);
                for x in (0..width).step_by(CELL_SIZE) {
                    context.move_to(x as f64, 0.);
                    context.line_to(x as f64, HEIGHT as f64);
                }
                for y in (0..height).step_by(CELL_SIZE) {
                    context.move_to(0., y as f64);
                    context.line_to(WIDTH as f64, y as f64);
                }
                let _ = context.stroke();

                let area = s.imp().area.borrow();
                for x in 0..area.width {
                    for y in 0..area.height {
                        if area.grain(x, y) {
                            context.set_source_rgb(0.8, 0.8, 0.8);
                        } else {
                            context.set_source_rgb(0., 0., 0.);
                        }
                        context.rectangle(
                            (x * CELL_SIZE + 1) as f64,
                            (y * CELL_SIZE + 1) as f64,
                            (CELL_SIZE - 2) as f64,
                            (CELL_SIZE - 2) as f64,
                        );
                        let _ = context.fill();
                    }
                }
            }
        });

        s
    }
}

impl SandView {
    fn set_area(&self, area: SandArea) {
        *self.imp().area.borrow_mut() = area;
    }
}

#[derive(Clone, Debug)]
struct SandArea {
    width: usize,
    height: usize,
    grains: Vec<bool>,
}

impl Default for SandArea {
    fn default() -> Self {
        let width = WIDTH / CELL_SIZE;
        let height = HEIGHT / CELL_SIZE;
        Self {
            width,
            height,
            grains: vec![false; width * height],
        }
    }
}

impl SandArea {
    pub fn add_grain(&mut self, x: usize, y: usize) {
        let addr = self.addr(x, y);
        self.grains[addr] = true;
    }

    pub fn grain(&self, x: usize, y: usize) -> bool {
        self.grains[self.addr(x, y)]
    }

    pub fn tick(self) -> Self {
        unimplemented!()
    }

    fn addr(&self, x: usize, y: usize) -> usize {
        y * self.width + x
    }
}

fn main() {
    let app = gtk::Application::builder()
        .application_id("com.luminescent-dreams.falling-sand")
        .build();

    app.connect_activate(move |app| {
        let window = gtk::ApplicationWindow::new(app);
        window.present();

        let view = SandView::default();
        let mut sand_area = SandArea::default();
        sand_area.add_grain(20, 20);
        view.set_area(sand_area.clone());

        window.set_child(Some(&view));
    });

    app.run();
}