extern crate glium;
extern crate glob;
extern crate png;

use std::env;
use std::io;
use std::fs::File;
use std::borrow::Cow;
use std::path;
use std::error::Error;

use glium::{Surface, Rect, BlitTarget};
use glium::texture::{RawImage2d, ClientFormat};
use glium::glutin::{self, Event, VirtualKeyCode, dpi};
use glium::backend::glutin::Display;

/// Load the image using `png`
fn load_image(path: &path::PathBuf) -> io::Result<RawImage2d<'static, u8>> {
    use png::ColorType::*;
    let decoder = png::Decoder::new(File::open(path)?);
    let (info, mut reader) = decoder.read_info()?;
    let mut img_data = vec![0; info.buffer_size()];
    reader.next_frame(&mut img_data)?;

    let (data, format) = match info.color_type {
        RGB => (img_data, ClientFormat::U8U8U8),
        RGBA => (img_data, ClientFormat::U8U8U8U8),
        Grayscale => (
            {
                let mut vec = Vec::with_capacity(img_data.len()*3);
                for g in img_data {
                    vec.extend([g, g, g].iter().cloned())
                }
                vec
            },
            ClientFormat::U8U8U8
        ),
        GrayscaleAlpha => (
            {
                let mut vec = Vec::with_capacity(img_data.len()*3);
                for ga in img_data.chunks(2) {
                    let g = ga[0]; let a = ga[1];
                    vec.extend([g, g, g, a].iter().cloned())
                }
                vec
            },
            ClientFormat::U8U8U8U8
        ),
        _ => unreachable!("uncovered color type")
    };

    Ok(RawImage2d {
        data: Cow::Owned(data),
        width: info.width,
        height: info.height,
        format: format
    })
}

fn main_loop(files: Vec<path::PathBuf>) -> io::Result<()> {
    use glium::glutin::{KeyboardInput, WindowEvent};

    let mut files = files.iter();
    let image = load_image(files.next().unwrap())?;

    let mut events_loop = glutin::EventsLoop::new();
    let window = glutin::WindowBuilder::new();
    let context = glutin::ContextBuilder::new()
        .with_vsync(true);

    let display = Display::new(window, context, &events_loop)
        .map_err(|err| io::Error::new(
            io::ErrorKind::Other,
            err.description()
        ))?;
    // building the display, ie. the main object
    resize_window(&display, &image);
    let mut opengl_texture = glium::Texture2d::new(&display, image).unwrap();

    let mut stop = false;
    let mut res = Ok(());
    'main: loop {
        let frame = display.draw();
        fill_v_flipped(&opengl_texture.as_surface(), &frame, glium::uniforms::MagnifySamplerFilter::Linear);
        frame.finish().unwrap();

        // polling and handling the events received by the window
        events_loop.poll_events(|event| {
            if stop {return;}
            match event {
                Event::WindowEvent {event: WindowEvent::CloseRequested, ..} => {
                    stop = true;
                    return;
                }
                Event::WindowEvent {
                    event: WindowEvent::KeyboardInput {
                        input: KeyboardInput {
                            state: glutin::ElementState::Pressed,
                            virtual_keycode: code,
                            ..
                        },
                        ..
                    },
                    ..
                } => match code {
                    Some(VirtualKeyCode::Escape) => {
                        stop = true;
                        return;
                    }
                    Some(VirtualKeyCode::Right) => {
                        match files.next() {
                            Some(path) => {
                                let image = match load_image(path) {
                                    Ok(image) => image,
                                    Err(err) => {
                                        stop = true;
                                        res = Err(err);
                                        return;
                                    }
                                };
                                resize_window(&display, &image);
                                opengl_texture = glium::Texture2d::new(&display, image).unwrap();
                            },
                            None => {
                                stop = true;
                                return;
                            }
                        }
                    },
                    _ => ()
                },
                _ => ()
            }
        });

        if stop {break 'main;}
    }
    res
}

fn fill_v_flipped<S1, S2>(src: &S1, target: &S2, filter: glium::uniforms::MagnifySamplerFilter)
where S1: Surface, S2: Surface {
    let src_dim = src.get_dimensions();
    let src_rect = Rect { left: 0, bottom: 0, width: src_dim.0 as u32, height: src_dim.1 as u32 };
    let target_dim = target.get_dimensions();
    let target_rect = BlitTarget { left: 0, bottom: target_dim.1, width: target_dim.0 as i32, height: -(target_dim.1 as i32) };
    src.blit_color(&src_rect, target, &target_rect, filter);
}

fn resize_window(display: &Display, image: &RawImage2d<'static, u8>) {
    let mut width = image.width;
    let mut height = image.height;
    if width < 50 && height < 50 {
        width *= 10;
        height *= 10;
    } else if width < 5 && height < 5 {
        width *= 10;
        height *= 10;
    }
    display.gl_window().window().set_inner_size(dpi::LogicalSize::new(f64::from(width), f64::from(height)));
}

fn main() {
    let args: Vec<String> = env::args().collect();
    if args.len() < 2 {
        println!("Usage: show files [...]");
    } else {
        let mut files = vec![];
        for file in args.iter().skip(1) {
            match if file.contains("*") {
                (|| -> io::Result<_> {
                    for entry in glob::glob(&file).map_err(|err| {
                        io::Error::new(io::ErrorKind::Other, err.msg)
                    })? {
                        files.push(entry.map_err(|_| {
                            io::Error::new(io::ErrorKind::Other, "glob error")
                        })?)
                    }
                    Ok(())
                })()
            } else {
                files.push(path::PathBuf::from(file));
                Ok(())
            } {
                Ok(_) => (),
                Err(err) => {
                    println!("{}: {}", file, err);
                    break
                }
            }

        }
        // "tests/pngsuite/pngsuite.png"
        match main_loop(files) {
            Ok(_) => (),
            Err(err) => println!("Error: {}", err)
        }
    }
}
