Jfb

use rand::{
    prng::XorShiftRng,
    {Rng, SeedableRng},
};
use wasm_bindgen::prelude::*;
use web_sys as web;

#[wasm_bindgen(start)]
pub fn start() {
    let non_keyed = web::window()
        .unwrap()
        .location()
        .pathname()
        .unwrap()
        .contains("non-keyed");
    draco::start(
        Jfb::new(!non_keyed),
        draco::select("main").expect("<main>").into(),
    );
}

pub struct Jfb {
    rows: Vec<Row>,
    next_id: u32,
    selected_id: Option<u32>,
    rng: XorShiftRng,
    keyed: bool,
}

#[derive(Clone, Hash)]
struct Row {
    id: u32,
    label: String,
}

impl Row {
    fn new<R: Rng>(id: u32, rng: &mut R) -> Row {
        let label = format!(
            "{} {} {}",
            rng.choose(ADJECTIVES).unwrap(),
            rng.choose(COLORS).unwrap(),
            rng.choose(NOUNS).unwrap()
        );

        Row { id, label }
    }

    fn view(&self, is_selected: bool) -> draco::VNode<Message> {
        use draco::html as h;
        draco::Lazy::new((self.clone(), is_selected), |(row, is_selected)| {
            let id = row.id;
            h::tr()
                .class(if *is_selected { "danger" } else { "" })
                .with((
                    h::td().class("col-md-1").with(row.id),
                    h::td()
                        .class("col-md-4")
                        .on("click", move |_| Message::Select(id))
                        .with(h::a().with(row.label.clone())),
                    h::td().class("col-md-1").with(
                        h::a()
                            .class("remove")
                            .on("click", move |_| Message::Remove(id))
                            .with(
                                h::span()
                                    .class("glyphicon glyphicon-remove")
                                    .attribute("aria-hidden", "true"),
                            ),
                    ),
                    h::td().class("col-md-6"),
                ))
                .into()
        })
        .into()
    }
}

#[derive(Clone)]
pub enum Message {
    Create(u32),
    Append(u32),
    UpdateEvery(u32),
    Clear,
    Swap,
    Remove(u32),
    Select(u32),
}

impl Jfb {
    pub fn new(keyed: bool) -> Self {
        Jfb {
            rows: Vec::new(),
            next_id: 1,
            selected_id: None,
            rng: XorShiftRng::from_seed([0; 16]),
            keyed,
        }
    }

    fn buttons() -> impl Iterator<Item = draco::VNode<Message>> {
        use draco::html as h;

        struct Button {
            id: &'static str,
            message: Message,
            description: &'static str,
        }

        static BUTTONS: &[Button] = &[
            Button {
                id: "run",
                description: "Create 1,000 rows",
                message: Message::Create(1000),
            },
            Button {
                id: "runlots",
                description: "Create 10,000 rows",
                message: Message::Create(10000),
            },
            Button {
                id: "add",
                description: "Append 1,000 rows",
                message: Message::Append(1000),
            },
            Button {
                id: "update",
                description: "Update every 10th row",
                message: Message::UpdateEvery(10),
            },
            Button {
                id: "clear",
                description: "Clear",
                message: Message::Clear,
            },
            Button {
                id: "swaprows",
                description: "Swap Rows",
                message: Message::Swap,
            },
        ];

        BUTTONS.iter().map(|button| {
            h::div()
                .class("col-sm-6 smallpad")
                .with(
                    h::button()
                        .id(button.id)
                        .class("btn btn-primary btn-block")
                        .type_("button")
                        .on("click", move |_| button.message.clone())
                        .with(button.description),
                )
                .into()
        })
    }
}

impl draco::Application for Jfb {
    type Message = Message;

    fn update(&mut self, message: Self::Message, mailbox: &draco::Mailbox<Self::Message>) {
        let Jfb {
            next_id,
            rng,
            rows,
            selected_id,
            ..
        } = self;
        match message {
            Message::Create(amount) => {
                rows.clear();
                mailbox.send(Message::Append(amount));
            }
            Message::Append(amount) => {
                rows.extend((0..amount).map(|index| Row::new(*next_id + index, rng)));
                *next_id += amount;
            }
            Message::UpdateEvery(step) => {
                for index in (0..rows.len()).step_by(step as usize) {
                    rows[index].label += " !!!";
                }
            }
            Message::Clear => {
                rows.clear();
            }
            Message::Swap => {
                if rows.len() > 998 {
                    rows.swap(1, 998);
                }
            }
            Message::Remove(id) => {
                rows.retain(|row| row.id != id);
            }
            Message::Select(id) => {
                if *selected_id == Some(id) {
                    *selected_id = None;
                } else {
                    *selected_id = Some(id);
                }
            }
        }
    }

    fn view(&self) -> draco::VNode<Message> {
        use draco::html as h;

        h::div()
            .class("container")
            .with((
                h::div()
                    .class("jumbotron")
                    .with(h::div().class("row").with((
                        h::div().class("col-md-6").with(h::h1().with("Draco")),
                        h::div().class("col-md-6").append(Self::buttons()),
                    ))),
                h::table()
                    .class("table table-hover table-striped test-data")
                    .with({
                        let vnode: draco::VNode<Message> = if self.keyed {
                            draco::html::keyed::tbody()
                                .id("tbody")
                                .append(self.rows.iter().map(|row| {
                                    (row.id as u64, row.view(self.selected_id == Some(row.id)))
                                }))
                                .into()
                        } else {
                            h::tbody()
                                .id("tbody")
                                .append(
                                    self.rows
                                        .iter()
                                        .map(|row| row.view(self.selected_id == Some(row.id))),
                                )
                                .into()
                        };
                        vnode
                    }),
                h::span()
                    .class("preloadicon glyphicon glyphicon-remove")
                    .attribute("aria-hidden", "true"),
            ))
            .into()
    }
}

static ADJECTIVES: &[&str] = &[
    "pretty",
    "large",
    "big",
    "small",
    "tall",
    "short",
    "long",
    "handsome",
    "plain",
    "quaint",
    "clean",
    "elegant",
    "easy",
    "angry",
    "crazy",
    "helpful",
    "mushy",
    "odd",
    "unsightly",
    "adorable",
    "important",
    "inexpensive",
    "cheap",
    "expensive",
    "fancy",
];

static COLORS: &[&str] = &[
    "red", "yellow", "blue", "green", "pink", "brown", "purple", "brown", "white", "black",
    "orange",
];

static NOUNS: &[&str] = &[
    "table", "chair", "house", "bbq", "desk", "car", "pony", "cookie", "sandwich", "burger",
    "pizza", "mouse", "keyboard",
];