use js_sys::Float32Array;
use log::{info, warn};
use std::cell::RefCell;
use std::sync::Arc;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use web_sys::{window, HtmlCanvasElement, WebGl2RenderingContext as GL2};
use yew::prelude::*;
use yew_router::prelude::*;

mod pages;
use crate::pages::about::About;
use crate::pages::blog::entries::Entries;
use crate::pages::blog::entry::Entry;
use crate::pages::blog::authors::Authors;
use crate::pages::blog::author::Author;
use crate::pages::projects::Projects;
use pages::not_found::PageNotFound;

#[derive(Routable, PartialEq, Eq, Clone, Debug)]
pub enum Route {
  #[at("/")]
  About,

  #[at("/blog/entries/:id")]
  Entry { id: u8 },
  #[at("/blog/entries")]
  Entries,
  #[at("/blog/authors/:id")]
  Author { id: u8 },
  #[at("/blog/authors")]
  Authors,

  #[at("/projects")]
  Projects,

  #[not_found]
  #[at("/404")]
  NotFound,
}

pub struct App {
  node_ref: NodeRef,
  navbar_active: bool,
}

pub enum Msg {
  ToggleNavbar,
}

impl Component for App {
  type Message = Msg;
  type Properties = ();

  fn create(_ctx: &Context<Self>) -> Self {
    Self { node_ref: NodeRef::default(), navbar_active: false }
  }

  fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
    match msg {
      Msg::ToggleNavbar => {
        self.navbar_active = !self.navbar_active;
        true
      }
    }
  }

  fn view(&self, _ctx: &Context<Self>) -> Html {
    html! {
      <>
        <canvas style="
          background-color: black !important;
          position: fixed !important;
          left: 0 !important;
          top: 0 !important;
          z-index: 0 !important;
        " ref={self.node_ref.clone()}/>
        <div style="
          display: flex !important;
          flex-direction: column !important;
          flex-wrap: wrap !important;
          height: 100vh !important;
        ">
          <BrowserRouter>
            <main style="
                z-index: 1 !important;
                display: flex !important;
                flex-direction: row-reverse !important;
                flex-wrap: wrap !important;
                overflow: visible !important;
                left: 0 !important;
                margin-top: calc(var(--bulma-navbar-height)) !important;
                margin-bottom: calc(var(--bulma-navbar-height) * 2) !important;
                height: calc(100vh - (var(--bulma-navbar-height) * 2)) !important;
                width: 100% !important;
                align-items: center !important;
              "><Switch<Route> render={switch} />
            </main>
            <header style="
              z-index: 1 !important;
              display: flex;
              flex-direction: column;
              flex-wrap: wrap;
            ">{ self.view_header(_ctx) }</header>
          </BrowserRouter>
          <footer class="footer" style="
            --bulma-footer-background-color: #1E1E2E80 !important;
            z-index: 1 !important;
            display: flex !important;
            flex-direction: column !important;
            flex-wrap: wrap !important;
            position: fixed !important;
            left: 0px !important;
            bottom: 0px !important;
            margin-bottom: 0 !important;
            width: 100% !important;
            padding: 0 !important;
          ">{ self.view_footer() }</footer>
        </div>
      </>
    }
  }

  fn rendered(&mut self, _ctx: &Context<Self>, first_render: bool) {
    if first_render { self.view_canvas(); }
  }
}

fn switch(routes: Route) -> Html {
  match routes {
    Route::About => { html! { <About /> } }

    Route::Entries => { html! { <Entries /> } }
    Route::Entry { id } => { html! { <Entry seed={id as u8} /> } }
    Route::Authors => { html! { <Authors /> } }
    Route::Author { id } => { html! { <Author seed={id} /> } }

    Route::Projects => { html! { <Projects /> } }
    Route::NotFound => { html! { <PageNotFound /> } }
  }
}

impl App {
  fn view_header(&self, _ctx: &Context<Self>) -> Html {
    let active_class = if self.navbar_active { "is-active" } else { "" };
    html! {
      <nav class="navbar" style="
          z-index: 1 !important;
          position: fixed !important;
          top: 0px !important;
          left: 0px !important;
          width: 100% !important;
          padding: 0 !important;
          --bulma-navbar-background-color: #1E1E2E80 !important;
          backdrop-filter: blur(4px) !important;
      ">
        <div class="navbar-brand">
          <button class={classes!("navbar-burger", "burger", active_class)} aria-label="menu" aria-expanded="false" onclick={_ctx.link().callback(|_| Msg::ToggleNavbar)}>
            <span aria-hidden="true"></span>
            <span aria-hidden="true"></span>
            <span aria-hidden="true"></span>
            <span aria-hidden="true"></span>
          </button>
        </div>
        <div class={classes!("navbar-menu", active_class)}>
          <div class="navbar-start">
            <Link<Route> classes={classes!("navbar-item")} to={Route::About}>{r"about"}</Link<Route>>
            <Link<Route> classes={classes!("navbar-item")} to={Route::Entries}>{r"blog"}</Link<Route>>
            <Link<Route> classes={classes!("navbar-item")} to={Route::Projects}>{r"projects"}</Link<Route>>
          </div>
        </div>
      </nav>
    }
  }

  fn view_footer(&self) -> Html {
    html! {
      <>
        <div class="is-flex is-flex-direction-column is-flex-wrap-wrap" style="
            backdrop-filter: blur(4px);
            width: 100vw !important;
            text-align: left !important;
            vertical-align: middle !important;
            height: 40px !important; max-height: 40px !important;
            image-rendering: pixelated !important;
          ">
          <div class="is-align-content-flex-start is-flex is-flex-shrink-0 is-flex-direction-column is-align-content-center is-flex-wrap-wrap ml-0 mt-0 mr-0 mb-0" style="width: 100% !important; min-height: 40px !important; height: 40px !important; max-height: 40px !important; overflow-y: hidden !important; overflow-x: auto !important;">
            <a class="mt-1 mb-1 mr-1 ml-0" style="
              width: 88px !important;
              height: 31px !important
            " href="https://i0ur.ing/"><img loading="eager" alt="servfail" class="image" src="iouring.png" /></a>
            <a class="mt-1 mb-1 mr-1 ml-0" style="
              width: 88px !important;
              height: 31px !important
            " href="https://webassembly.org/"><img loading="eager" alt="wasm" class="image" src="wasm.png" /></a>
            <a class="mt-1 mb-1 mr-1 ml-0" style="
              width: 88px !important;
              height: 31px !important
            " href="https://beta.servfail.network/"><img loading="eager" alt="servfail" class="image" src="servfail.png" /></a>
            <a class="mt-1 mb-1 mr-1 ml-0" style="
              width: 88px !important;
              height: 31px !important
            " href="https://www.dataforest.net/en/"><img loading="eager" alt="dataforest" class="image" src="dataforest.png" /></a>
            <a class="mt-1 mb-1 mr-1 ml-0" style="
              width: 88px !important;
              height: 31px !important
            " href="https://fedoraproject.org/"><img loading="eager" alt="fedora" class="image" src="fedora.png" /></a>
            <a class="mt-1 mb-1 mr-1 ml-0" style="
              width: 88px !important;
              height: 31px !important
            " href="https://artixlinux.org/"><img loading="eager" alt="artix" class="image" src="artix.png" /></a>
            <a class="mt-1 mb-1 mr-1 ml-0" style="
              width: 88px !important;
              height: 31px !important
            " href="https://nixos.org/"><img loading="eager" alt="nixos" class="image" src="nixos.png" /></a>
            <a class="mt-1 mb-1 mr-1 ml-0" style="
              width: 88px !important;
              height: 31px !important
            " href="https://chimera-linux.org/"><img loading="eager" alt="void" class="image" src="chimera.png" /></a>
            <a class="mt-1 mb-1 mr-1 ml-0" style="
              width: 88px !important;
              height: 31px !important
            " href="https://voidlinux.org/"><img loading="eager" alt="void" class="image" src="void.png" /></a>
            <a class="mt-1 mb-1 mr-1 ml-0" style="
              width: 88px !important;
              height: 31px !important
            " href="https://www.rust-lang.org/"><img loading="eager" alt="rust" class="image" src="rust.png" /></a>
            <a class="mt-1 mb-1 mr-1 ml-0" style="
              width: 88px !important;
              height: 31px !important
            " href="https://dotnet.microsoft.com/en-us/"><img loading="eager" alt="csharp" class="image" src="csharp.png" /></a>
            <a class="mt-1 mb-1 mr-1 ml-0" style="
              width: 88px !important;
              height: 31px !important
            " href="https://kotlinlang.org/"><img loading="eager" alt="kotlin" class="image" src="kotlin.png" /></a>
            <a class="mt-1 mb-1 mr-1 ml-0" style="
              width: 88px !important;
              height: 31px !important
            " href="https://www.java.com/"><img loading="eager" alt="java" class="image" src="java.png" /></a>
            <a class="mt-1 mb-1 mr-1 ml-0" style="
              width: 88px !important;
              height: 31px !important
            "><img loading="eager" alt="aqueer" class="image" src="aqueer.png" /></a>
          </div>
        </div>
      </>
    }
  }

  fn view_canvas(&self) {
    let canvas = self.node_ref.cast::<HtmlCanvasElement>().unwrap();

    let gl = canvas
        .get_context("webgl2")
        .unwrap_or(canvas.get_context("webgl").unwrap())
        .unwrap().dyn_into::<GL2>().unwrap();

    let vertex_buffer = gl.create_buffer().unwrap();
    gl.bind_buffer(GL2::ARRAY_BUFFER, Some(&vertex_buffer));
    gl.buffer_data_with_array_buffer_view(GL2::ARRAY_BUFFER, &Float32Array::from(vec![
      -1.0, 1.0, 0.0,
      -1.0, -1.0, 0.0,
      1.0, -1.0, 0.0,
      1.0, 1.0, 0.0
    ].as_slice()), GL2::STATIC_DRAW);

    let color_buffer = gl.create_buffer().unwrap();
    gl.bind_buffer(GL2::ARRAY_BUFFER, Some(&color_buffer));
    gl.buffer_data_with_array_buffer_view(GL2::ARRAY_BUFFER, &Float32Array::from(vec![
      1.0, 0.0, 0.0, 1.0,
      0.0, 1.0, 0.0, 1.0,
      0.0, 0.0, 1.0, 1.0,
      1.0, 1.0, 0.0, 1.0
    ].as_slice()), GL2::STATIC_DRAW);

    let element_buffer = gl.create_buffer().unwrap();
    gl.bind_buffer(GL2::ELEMENT_ARRAY_BUFFER, Some(&element_buffer));
    let element_indices = vec![3, 2, 1, 3, 1, 0];
    gl.buffer_data_with_array_buffer_view(
      GL2::ELEMENT_ARRAY_BUFFER,
      &js_sys::Uint16Array::from(element_indices.as_slice()),
      GL2::STATIC_DRAW,
    );

    let vertex_shader_code = include_str!("../public/shaders/es3/bg.vert");
    // info!("{}", vertex_shader_code);
    let vertex_shader = gl.create_shader(GL2::VERTEX_SHADER).unwrap();
    gl.shader_source(&vertex_shader, vertex_shader_code);
    gl.compile_shader(&vertex_shader);
    let vertex_shader_log = gl.get_shader_info_log(&vertex_shader);
    if !vertex_shader_log.as_ref().unwrap().is_empty() {
      info!("{}", vertex_shader_log.as_ref().unwrap());
    }

    let fragment_shader_code = include_str!("../public/shaders/es3/bg.frag");
    let fragment_shader = gl.create_shader(GL2::FRAGMENT_SHADER).unwrap();
    gl.shader_source(&fragment_shader, fragment_shader_code);
    gl.compile_shader(&fragment_shader);
    let fragment_shader_log = gl.get_shader_info_log(&fragment_shader);
    if !fragment_shader_log.as_ref().unwrap().is_empty() {
      info!("{}", vertex_shader_log.as_ref().unwrap());
    }

    let shader_program = gl.create_program().unwrap();

    gl.attach_shader(&shader_program, &vertex_shader);
    gl.attach_shader(&shader_program, &fragment_shader);

    gl.link_program(&shader_program);
    if !gl.get_program_parameter(&shader_program, GL2::LINK_STATUS).as_bool().unwrap() {
      warn!("shader couldn't be linked!");
    }
    gl.use_program(Some(&shader_program));

    let position = gl.get_attrib_location(&shader_program, "position") as u32;
    gl.enable_vertex_attrib_array(position);
    gl.bind_buffer(GL2::ARRAY_BUFFER, Some(&vertex_buffer));
    gl.vertex_attrib_pointer_with_i32(position, 3, GL2::FLOAT, false, 0, 0);

    let color = gl.get_attrib_location(&shader_program, "color") as u32;
    gl.enable_vertex_attrib_array(color);
    gl.bind_buffer(GL2::ARRAY_BUFFER, Some(&color_buffer));
    gl.vertex_attrib_pointer_with_i32(color, 4, GL2::FLOAT, false, 0, 0);

    gl.bind_buffer(GL2::ELEMENT_ARRAY_BUFFER, Some(&element_buffer));

    gl.enable(GL2::DEPTH_TEST);
    gl.clear_color(0.0, 0.0, 0.0, 1.0);
    gl.clear(GL2::COLOR_BUFFER_BIT | GL2::DEPTH_BUFFER_BIT);

    let default_matrix = vec![1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0];
    let unim_matrix = gl.get_uniform_location(&shader_program, "model");
    let unim_time = gl.get_uniform_location(&shader_program, "time");
    let unim_res = gl.get_uniform_location(&shader_program, "resolution");
    let unim_overlay_time = gl.get_uniform_location(&shader_program, "layer_snow");

    let mut timestamp = 0.0;
    gl.uniform1f(unim_time.as_ref(), timestamp / 2000.0);
    gl.draw_elements_with_i32(GL2::TRIANGLES, element_indices.len() as i32, GL2::UNSIGNED_SHORT, 0);

    fn request_animation_frame(f: &Closure<dyn FnMut()>) {
      window().unwrap().request_animation_frame(f.as_ref().unchecked_ref())
          .expect("should register `requestAnimationFrame` OK");
    }

    let cb = Arc::new(RefCell::new(None));
    *cb.borrow_mut() = Some(Closure::wrap(Box::new({
      let cb = cb.clone();
      move || {
        timestamp += 20.0;

        canvas.set_width(window().unwrap().inner_width().unwrap().as_f64().unwrap() as u32);
        canvas.set_height(window().unwrap().inner_height().unwrap().as_f64().unwrap() as u32);

        gl.uniform_matrix4fv_with_f32_array(unim_matrix.as_ref(), false, default_matrix.as_slice());

        gl.uniform1f(unim_time.as_ref(), timestamp / 2000.0);
        gl.uniform2fv_with_f32_array(unim_res.as_ref(), vec![canvas.width() as f32, canvas.height() as f32].as_slice());

        gl.uniform1i(unim_overlay_time.as_ref(), 0); // TODO: ... | add is xmas check

        gl.viewport(0, 0, canvas.width() as i32, canvas.height() as i32);
        gl.clear(GL2::COLOR_BUFFER_BIT | GL2::DEPTH_BUFFER_BIT);
        gl.draw_elements_with_i32(GL2::TRIANGLES, element_indices.len() as i32, GL2::UNSIGNED_SHORT, 0);

        request_animation_frame(cb.borrow().as_ref().unwrap());
      }
    }) as Box<dyn FnMut()>));

    request_animation_frame(cb.borrow().as_ref().unwrap());
  }
}

fn main() {
  wasm_logger::init(wasm_logger::Config::new(log::Level::Trace));
  yew::Renderer::<App>::new().render();
}
