pub mod command;
pub mod mode;
pub mod sites;
pub mod utils;
pub mod vcp;
pub mod rendering;


use std::io::Cursor;
use js_sys::Uint8Array;
use crate::mode::Mode;

use crate::rendering::scope::{Preferences, ScopeState, WgpuState};

use log::{debug, error, info};


use wasm_bindgen::prelude::*;
use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement};
use wgpu::SurfaceError;
use winit::dpi::PhysicalSize;
use winit::event::WindowEvent::KeyboardInput;
use winit::event::{ElementState, Event, WindowEvent};
use winit::event_loop::{EventLoopBuilder, EventLoopProxy, EventLoopWindowTarget};
use winit::keyboard::{Key, NamedKey};

use winit::platform::web::{EventLoopExtWebSys, WindowBuilderExtWebSys, WindowExtWebSys};
use winit::window::WindowBuilder;
use nexrad2::Nexrad2Chunk;
use crate::command::{exec, should_newline};

#[wasm_bindgen]
extern "C" {
    fn rescaleCanvas(canvas: &HtmlCanvasElement, ctx: &CanvasRenderingContext2d);
    fn loadar2(c: &str);
}

#[derive(Debug, Clone, PartialEq)]
pub enum RenderMessage {
    Resize { new_w: u32, new_h: u32 },
    NewFileUploadedByUser,
    NewDataChunkAvailable { chunk: Nexrad2Chunk }
}

#[wasm_bindgen]
pub struct AbiProxy(EventLoopProxy<RenderMessage>);

#[wasm_bindgen]
pub async fn __nxrd_browser_init(w: u32, h: u32) -> AbiProxy {
    wasm_logger::init(wasm_logger::Config::new(log::Level::Info));
    utils::set_panic_hook();

    info!("wgpu setup");

    let event_loop = EventLoopBuilder::<RenderMessage>::with_user_event()
        .build()
        .expect("event loop creation failed");
    let window = WindowBuilder::new()
        .with_prevent_default(false)
        .build(&event_loop)
        .unwrap();

    window.set_min_inner_size(Some(PhysicalSize::new(w, h)));

    web_sys::window()
        .and_then(|win| win.document())
        .and_then(|doc| {
            let dst = doc.get_element_by_id("canvas-container")?;
            // If you see an error here, your IDE is not compiling for webassembly
            let canvas = web_sys::Element::from(window.canvas().unwrap());
            dst.append_child(&canvas).ok()?;
            Some(())
        })
        .expect("Couldn't append canvas to document body.");

    let canvas = window.canvas().unwrap();
    canvas.set_width(w);
    canvas.set_height(h);

    let mut scope_state = ScopeState::new(window, PhysicalSize::new(w, h)).await;

    let event_proxy: EventLoopProxy<RenderMessage> = event_loop.create_proxy();

    // If you see an error here, your IDE is not compiling for webassembly
    event_loop.spawn(
        move |event: Event<_>, window: &EventLoopWindowTarget<_>| match event {
            Event::UserEvent(e) => match e {
                RenderMessage::Resize { new_w, new_h } => {
                    scope_state
                        .reconfigure(PhysicalSize::new(new_w, new_h));
                },
                RenderMessage::NewFileUploadedByUser => {
                    scope_state.new_data_available = true;
                },
                RenderMessage::NewDataChunkAvailable { chunk } => {
                    scope_state.new_data_available = false;
                    scope_state.scope_mode = Mode::Reflectivity;
                    scope_state.ar2 = Some(chunk);
                    scope_state.command_buf_response_mode = true;
                    scope_state.command_buf = "NEW DATA LOADED SUCCESSFULLY".to_string();
                    scope_state.selected_elevation = 1;
                    scope_state.recalculate_scope_display();
                }
            },
            Event::WindowEvent {
                ref event,
                window_id: _,
            } => match event {
                KeyboardInput { event, .. } => {
                    if event.state == ElementState::Pressed {

                        if event.logical_key == Key::Named(NamedKey::Escape) {
                            scope_state.command_buf = String::new();
                            scope_state.command_buf_response_mode = false;
                            return;
                        }

                        if scope_state.command_buf_response_mode && (event.logical_key == Key::Named(NamedKey::Backspace) || matches!(event.logical_key, Key::Character(_))) {
                            scope_state.command_buf = String::new();
                            scope_state.command_buf_response_mode = false;
                        }

                        if event.logical_key == Key::Named(NamedKey::Enter) {
                            let command = scope_state.command_buf.clone();
                            scope_state.command_buf = String::new();
                            exec(&mut scope_state, command.replace("\n", " "));
                            return;
                        }

                        if event.logical_key == Key::Named(NamedKey::Space) {
                            if should_newline(&mut scope_state) {
                                scope_state.command_buf += "\n";
                            } else {
                                scope_state.command_buf += " ";
                            }
                        }

                        if event.logical_key == Key::Named(NamedKey::Backspace) && scope_state.command_buf.len() != 0 {
                            scope_state.command_buf = scope_state.command_buf[0..scope_state.command_buf.len()-1].to_string();
                            return;
                        }

                        match &event.logical_key {
                            Key::Character(c) => {
                                scope_state.command_buf += &c.to_uppercase();
                            },
                            _ => ()
                        }
                    }
                    debug!("{:?}", event.physical_key);

                }
                WindowEvent::CloseRequested => {
                    window.exit();
                }
                WindowEvent::Resized(physical_size) => {
                    scope_state.reconfigure(*physical_size);
                }
                WindowEvent::RedrawRequested => {
                    scope_state.update();
                    match scope_state.render() {
                        Ok(_) => {}
                        Err(SurfaceError::Lost) => {
                            scope_state.reconfigure(scope_state.wgpu.size)
                        }
                        Err(SurfaceError::OutOfMemory) => {
                            error!("out of memory!");
                            window.exit();
                        }
                        Err(e) => error!("transient rendering error: {}", e),
                    }
                }
                _ => {}
            },
            Event::AboutToWait => {
                scope_state.window().request_redraw();
            }
            _ => {}
        },
    );

    info!("nexrad-browser initialized successfully");

    AbiProxy(event_proxy)
}

#[wasm_bindgen]
pub fn resize_abi(w: u32, h: u32, proxy: &mut AbiProxy) {
    proxy
        .0
        .send_event(RenderMessage::Resize { new_w: w, new_h: h })
        .unwrap();
}

/*
#[wasm_bindgen]
pub fn render_abi(state: &mut AbiScopeState) {
    old_rendering::render(&mut state.0).unwrap()
}

 */

#[wasm_bindgen]
pub fn load_ar2(buf: &JsValue, proxy: &mut AbiProxy) {
    let array = Uint8Array::new(buf);
    let rvec = array.to_vec();
    let mut cursor = Cursor::new(rvec);
    let loaded = nexrad2::parse_nx2_chunk(&mut cursor).unwrap();
    info!("new data chunk loaded");

    proxy.0.send_event(RenderMessage::NewDataChunkAvailable { chunk: loaded }).unwrap();
}

#[wasm_bindgen]
pub fn new_file_available(proxy: &mut AbiProxy) {
    proxy.0.send_event(RenderMessage::NewFileUploadedByUser).unwrap();
}

/*

#[wasm_bindgen]
pub fn keydown(state: &mut AbiScopeState, key: String) {
    if state.0.command_buf_response_mode && (key == "Backspace" || key.len() == 1) {
        info!("resetting, key {} buf {}", key, state.0.command_buf);
        state.0.command_buf = String::new();
        state.0.command_buf_response_mode = false;
    }

    if key == "Escape" {
        state.0.command_buf = String::new();
    } else if key == "Enter" {
        let cmd = state.0.command_buf.clone();
        state.0.command_buf = String::new();
        exec(&mut state.0, cmd.replace('\n', " "));
    } else if key == " " && should_newline(&mut state.0) {
        state.0.command_buf += "\n";
    } else if key == "Backspace" {
        if state.0.command_buf.is_empty() { return; }
        state.0.command_buf = state.0.command_buf[0..state.0.command_buf.len()-1].to_string();
    } else if key.len() == 1 {
        state.0.command_buf += &key.to_uppercase();
    }
}

 */
