extern crate angular_units as angle;

use {
    std::num::NonZeroU32,
    std::sync::{Arc, RwLock, Mutex},
    std::rc::Rc,
    winit::event::{Event, WindowEvent},
    winit::event_loop::{ControlFlow, EventLoop},
    prisma::{Rgb,Hsv,FromColor, Lerp},
    cgmath::{Point2, Vector2},
    std::time::Duration,
    angle::Turns
};

mod util;
mod fixture;
mod setup;
mod inputs;
mod view;
mod stripe_driver;
mod jack;
mod waveform;
mod controller;
mod fpsstat;

mod patterns;
mod scene_library;

use crate::{
    view::ColorGrid,
    fixture::Fixture,
    setup::LightingSetup,
    stripe_driver::StripeDriver,
    util::get_angle,

    scene_library::SceneLibrary,
    waveform::Waveform,
    inputs::Inputs
};

#[async_std::main]
async fn main() {
    let event_loop = EventLoop::new().unwrap();

    let window = event_loop.create_window(
        winit::window::Window::default_attributes()
            .with_title("Fragmental Light Controller")
    ).expect("create window");
    let window = Arc::new(window);
    let context = softbuffer::Context::new(window.clone()).unwrap();
    let mut surface = Arc::new(Mutex::new(softbuffer::Surface::new(&context, window.clone()).unwrap()));

    let dim = Arc::new(Mutex::new((1 as u32,1 as u32)));

    let socket = Arc::new(RwLock::new(std::net::UdpSocket::bind("0.0.0.0:4210").expect("failed to bind UDP socket")));
    socket.write().unwrap().set_read_timeout(Some(std::time::Duration::from_millis(1)));
    socket.write().unwrap().set_write_timeout(Some(std::time::Duration::from_millis(1)));

    let mut inputs = Arc::new(RwLock::new(Inputs::default()));

    let scene_library = Arc::new(RwLock::new(SceneLibrary::new()));

    let mut lighting_setup = LightingSetup::new(
        vec![
            Fixture::new_matrix(),
//                .with_driver( Box::new(MatrixTcpDriver::new("ip:port")) ),

            Fixture::new_stripe(false)
                .with_driver( Box::new(StripeDriver::new("192.168.0.114:4210", socket.clone())) )
                .offset(Vector2::new(-0.5, 0.0)),

            Fixture::new_stripe(true)
                .with_driver( Box::new(StripeDriver::new("192.168.0.113:4210", socket.clone())) )
                .offset(Vector2::new(-0.4, 0.0)),

            Fixture::new_stripe(true)
                .with_driver( Box::new(StripeDriver::new("192.168.0.112:4210", socket.clone())) )
            .offset(Vector2::new(0.4, 0.0)),

            Fixture::new_stripe(false)
                .with_driver( Box::new(StripeDriver::new("192.168.0.111:4210", socket.clone())))
                .offset(Vector2::new(0.5, 0.0))
        ],

        scene_library.clone()
    );

    let mut controller = controller::Controller::new( scene_library, inputs.clone() );

    {
    let inputs = controller.inputs.read().unwrap();
    lighting_setup.advance(&(*inputs) );
    for i in 0 .. lighting_setup.fixtures.len() {
        lighting_setup.sync_fixture(i);
    }
    }

    event_loop.run(move |event, elwt| {
        let tcur = std::time::Instant::now();

        elwt.set_control_flow(
            ControlFlow::WaitUntil(
                tcur + Duration::from_millis(25)
            ));

        // update animation
        let active = {
            let mut inputs = inputs.write().unwrap();
            inputs.t = tcur - controller.tbegin;
            inputs.transition_time = tcur - controller.transition_begin;    
            lighting_setup.advance( &inputs );
            inputs.active
        };

        // refresh fixture outputs
        if active {
            // sync
            let mut rbuf = [0 as u8; 8];

            while let Ok((n, src_addr)) =
                socket.write().unwrap().recv_from(&mut rbuf)
            {
                if src_addr == "192.168.0.111:4210".parse().expect("parse socketaddr") {
                        lighting_setup.sync_fixture(1);
                    }
                if src_addr == "192.168.0.112:4210".parse().expect("parse socketaddr") {
                        lighting_setup.sync_fixture(2);
                }
                if src_addr == "192.168.0.113:4210".parse().expect("parse socketaddr") {
                        lighting_setup.sync_fixture(3);
                    }
                if src_addr == "192.168.0.114:4210".parse().expect("parse socketaddr") {
                        lighting_setup.sync_fixture(4);
            }
            }

            for i in 1 ..5 {
                lighting_setup.update_outputs(i);
            }
        }

        match event {
            Event::WindowEvent { window_id, event: WindowEvent::RedrawRequested } if window_id == window.id() => {
                let (width, height) = {
                    let size = window.inner_size();
                    (size.width, size.height)
                };

                *dim.lock().unwrap() = (width, height);    
            }
            Event::WindowEvent {
                event: WindowEvent::CloseRequested,
                window_id,
            } if window_id == window.id() => {
                elwt.exit();
            }

            winit::event::Event::WindowEvent{
                window_id: _,
                event: winit::event::WindowEvent::KeyboardInput{ device_id, event, is_synthetic }
            } => {
                controller.handle_key(
                    event, &mut lighting_setup
                );

                eprintln!("{}", termion::clear::All);
                eprintln!("----------");
                controller.display();
                inputs.read().unwrap().display();
                eprintln!("----------");
                lighting_setup.display();
                eprintln!("----------");                
            }

            _ => {}
        }

        let (width, height) = *dim.lock().unwrap();
        surface.lock().unwrap()
                    .resize(
                        NonZeroU32::new(width).unwrap(),
                        NonZeroU32::new(height).unwrap(),
                    )
                    .unwrap();

        if let Ok(mut buf) = surface.lock().unwrap().buffer_mut() {
            lighting_setup.draw_preview( &mut buf, width, height );
            buf.present().unwrap();
        }
    }).unwrap();
}