mod controller;
mod fixtures;

use std::cell::RefCell;

use controller::Controller;
use fixtures::{Brightness, DMXFixture, MovingHead};

type BoxedController = JsBox<RefCell<Controller>>;
impl Finalize for Controller {}

use neon::prelude::*;

pub fn list_ports(mut cx: FunctionContext) -> JsResult<JsArray> {
    let array = cx.empty_array();

    if let Ok(ports) = serialport::available_ports() {
        for (i, port) in ports.iter().enumerate() {
            let port_name = cx.string(&port.port_name);
            array.set(&mut cx, i as u32, port_name)?;
        }
    }

    Ok(array)
}

pub fn open_output(mut cx: FunctionContext) -> JsResult<JsObject> {
    let path = cx.argument::<JsString>(0)?;
    let path = path.value(&mut cx);

    let obj = cx.empty_object();
    let value_obj = cx.empty_object();

    match Controller::new(path) {
        Ok(controller) => {
            let success_string = cx.string("success");
            obj.set(&mut cx, "type", success_string)?;

            let boxed_controller = cx.boxed(RefCell::new(controller));
            value_obj.set(&mut cx, "_rust_ptr", boxed_controller)?;

            let set_output_function = JsFunction::new(&mut cx, set_output)?;
            value_obj.set(&mut cx, "set", set_output_function)?;

            let close_function = JsFunction::new(&mut cx, close_output)?;
            value_obj.set(&mut cx, "close", close_function)?;

            obj.set(&mut cx, "value", value_obj)?;
        }
        Err(e) => {
            let success_string = cx.string("error");
            obj.set(&mut cx, "type", success_string)?;

            let error_message = cx.string(e.to_string());
            obj.set(&mut cx, "message", error_message)?;
        }
    }

    Ok(obj)
}

fn set_output(mut cx: FunctionContext) -> JsResult<JsObject> {
    let arg = cx.argument::<JsArray>(0)?;

    let mut moving_heads = Vec::with_capacity(4);

    for i in 0..arg.len(&mut cx) {
        // why isn't neon-serde maintained anymore? T_T

        let head = arg
            .get(&mut cx, i)?
            .downcast_or_throw::<JsObject, _>(&mut cx)?;

        moving_heads.push(MovingHead {
            start_address: head
                .get(&mut cx, "startAddress")?
                .downcast_or_throw::<JsNumber, _>(&mut cx)?
                .value(&mut cx) as usize,

            pan: head
                .get(&mut cx, "pan")?
                .downcast_or_throw::<JsNumber, _>(&mut cx)?
                .value(&mut cx),

            tilt: head
                .get(&mut cx, "tilt")?
                .downcast_or_throw::<JsNumber, _>(&mut cx)?
                .value(&mut cx),

            brightness: {
                let brightness = head
                    .get(&mut cx, "brightness")?
                    .downcast_or_throw::<JsObject, _>(&mut cx)?;

                match brightness
                    .get(&mut cx, "type")?
                    .downcast_or_throw::<JsString, _>(&mut cx)?
                    .value(&mut cx)
                    .as_str()
                {
                    "off" => Brightness::Off,
                    "dimmer" => Brightness::Dimmer(
                        brightness
                            .get(&mut cx, "value")?
                            .downcast_or_throw::<JsNumber, _>(&mut cx)?
                            .value(&mut cx),
                    ),
                    "strobe" => Brightness::Strobe(
                        brightness
                            .get(&mut cx, "value")?
                            .downcast_or_throw::<JsNumber, _>(&mut cx)?
                            .value(&mut cx),
                    ),
                    "switch" => Brightness::Switch,
                    s => {
                        let err = cx.string(format!("Invalid brightness type: {}", s));
                        return cx.throw(err);
                    }
                }
            },

            rgbw: {
                let rgbw = head
                    .get(&mut cx, "rgbw")?
                    .downcast_or_throw::<JsArray, _>(&mut cx)?;

                let mut vec = Vec::new();

                for i in 0..4 {
                    let v = rgbw
                        .get(&mut cx, i)?
                        .downcast_or_throw::<JsNumber, _>(&mut cx)?
                        .value(&mut cx) as u8;

                    vec.push(v);
                }

                (vec[0], vec[1], vec[2], vec[3])
            },

            speed: head
                .get(&mut cx, "speed")?
                .downcast_or_throw::<JsNumber, _>(&mut cx)?
                .value(&mut cx),

            reset: head
                .get(&mut cx, "reset")?
                .downcast_or_throw::<JsBoolean, _>(&mut cx)?
                .value(&mut cx),
        })
    }

    let this = cx.this();
    let boxed_controller = this
        .get(&mut cx, "_rust_ptr")?
        .downcast_or_throw::<BoxedController, _>(&mut cx)?;

    let boxed_controller = boxed_controller.borrow_mut();
    {
        let mut state = boxed_controller.state.lock().unwrap();

        for head in moving_heads {
            head.render(&mut state.data);
        }
    }

    let obj = cx.empty_object();

    let success_text = cx.string("success".to_string());
    obj.set(&mut cx, "type", success_text)?;

    Ok(obj)
}

fn close_output(mut cx: FunctionContext) -> JsResult<JsObject> {
    let this = cx.this();

    let boxed_controller = this
        .get(&mut cx, "_rust_ptr")?
        .downcast_or_throw::<BoxedController, _>(&mut cx)?;

    let obj = cx.empty_object();

    let success_string;

    match boxed_controller.borrow_mut().stop() {
        Ok(()) => {
            success_string = cx.string("success");
        }
        Err(e) => {
            success_string = cx.string("error");

            let error_message = cx.string(e.to_string());
            obj.set(&mut cx, "message", error_message)?;
        }
    }

    obj.set(&mut cx, "type", success_string)?;

    Ok(obj)
}
