#![feature(slice_patterns)]

extern crate futures;
extern crate hyper;
extern crate rusqlite;
extern crate time;
extern crate mustache;
extern crate rustc_serialize;
extern crate tokio_core;
extern crate tokio_io;
extern crate unix_socket;

#[macro_use]
extern crate lazy_static;

pub mod errors;
pub mod model;
pub mod template;
#[macro_use]
pub mod util;

use futures::future::Future;
use hyper::Method;
use hyper::header::{ContentLength,ContentType};
use hyper::server::{Http, Request, Response, Service};

use std::io::Read;

struct CG {
    db: rusqlite::Connection,
}

impl Service for CG {
    type Request = Request;
    type Response = Response;
    type Error = hyper::Error;
    type Future = Box<Future<Item=Self::Response, Error=Self::Error>>;

    fn call(&self, req: Request) -> Self::Future {
        util::log_request(&req);
        match self.route(req) {
            Ok(pg) =>
                CG::respond(pg),
            Err(err) => {
                error!("Found error: {:?}", err);
                CG::respond_404(format!("error: {:?}", err))
            }
        }
    }
}

enum Page {
    HTML(String),
    PNG(Vec<u8>),
    StaticPNG(&'static [u8]),
}

impl CG {
    fn main() -> Result<(), errors::CGError> {
        match std::env::var("CG_UNIX_SOCK") {
            Ok(addr) => {
                std::fs::remove_file(&addr)?;
                let mut socket = unix_socket::UnixListener::bind(addr)?;
                let mut core = tokio_core::reactor::Core::new()?;
                let http: Http = Http::new();
                loop {
                    let db = rusqlite::Connection::open("cocktails.db")?;
                    core.run(
                        http.serve_connection(
                            tokio_io::io::AllowStdIo::new(socket.accept()?.0),
                            CG { db },
                        )
                    )?;
                }
            }

            Err(_) => {
                let addr = "127.0.0.1:3000".parse()?;
                let server = Http::new().bind(&addr, || {
                    let db = rusqlite::Connection::open("cocktails.db").unwrap();
                    Ok(CG { db })
                })?;
                println!("serving on {:?}", addr);
                Ok(server.run()?)
            }
        }
    }

    fn route(&self, req: Request) -> Result<Page, errors::RuntimeError> {
        let path: Vec<&str> = req.path().split("/").skip(1).collect();

        Ok(match (req.method(), path.as_slice()) {
            (&Method::Get, &[""]) => {
                let ck = model::get_cocktail_list(&self.db)?;
                Page::HTML(template::render_cocktails(ck)?)
            }

            (&Method::Get, &["favicon.ico"]) =>
                Page::HTML("icon".to_owned()),

            (&Method::Get, &["cocktail", name]) =>
                Page::HTML(format!("cocktail {}", name)),

            (&Method::Get, &["tagged", tag]) =>
                Page::HTML(format!("tag {}", tag)),

            (&Method::Get, &["static", "header-logo.png"]) =>
                Page::StaticPNG(template::HEADER_IMAGE),

            (&Method::Get, &["imgs", filename]) => {
                let mut img_path = std::path::PathBuf::from("imgs");
                img_path.push(filename);
                let mut f = std::fs::File::open(img_path.as_path())?;
                let mut vec = Vec::new();
                let _ = f.read_to_end(&mut vec)?;
                Page::PNG(vec)
            }

            (_, _) => {
                return Err(errors::RuntimeError::UnknownPath(
                    req.path().to_owned()));

            }
        })
    }

    fn respond(p: Page) -> <CG as Service>::Future {
        match p {
            Page::HTML(s) =>
                Box::new(futures::future::ok(
                    Response::new()
                        .with_header(ContentLength(s.len() as u64))
                        .with_header(ContentType::html())
                        .with_body(s))),
            Page::PNG(s) =>
                Box::new(futures::future::ok(
                    Response::new()
                        .with_header(ContentLength(s.len() as u64))
                        .with_header(ContentType::png())
                        .with_body(s))),
            Page::StaticPNG(s) =>
                Box::new(futures::future::ok(
                    Response::new()
                        .with_header(ContentLength(s.len() as u64))
                        .with_header(ContentType::png())
                        .with_body(s))),
        }
    }

    fn respond_404(s: String) -> <CG as Service>::Future {
        Box::new(futures::future::ok(
            Response::new()
                .with_status(hyper::StatusCode::NotFound)
                .with_header(ContentLength(s.len() as u64))
                .with_body(s)))
    }

}


fn main() {
    if let Err(err) = CG::main() {
        err.pretty();
        std::process::exit(99);
    }
}
