#[macro_use]
extern crate diesel;
#[macro_use]
extern crate diesel_migrations;
#[macro_use]
extern crate lazy_static;

use std::env;
use std::net::SocketAddr;
use std::path::PathBuf;

use actix_web::{middleware, App, HttpServer};
use anyhow::Result;
use clap::{App as ClapApp, Arg};
use log::{info, warn};
use std::sync::{Arc, RwLock};

mod addressing;
mod database;
mod filesystem;
mod hash;
mod jobs;
mod models;
mod routes;
mod schema;
mod util;

const VERSION: &str = env!("CARGO_PKG_VERSION");

fn main() -> Result<()> {
    let env = env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "info");
    env_logger::init_from_env(env);

    let app = ClapApp::new("upend")
        .version(VERSION)
        .author("Tomáš Mládek <t@mldk.cz>")
        .arg(Arg::with_name("DIRECTORY").required(true).index(1))
        .arg(
            Arg::with_name("BIND")
                .long("bind")
                .default_value("127.0.0.1:8093")
                .help("address and port to bind the Web interface on")
                .required(true),
        )
        .arg(
            Arg::with_name("NO_BROWSER")
                .long("no-browser")
                .help("Do not open web browser with the UI."),
        )
        .arg(
            Arg::with_name("NO_INITIAL_UPDATE")
                .long("no-initial-update")
                .help("Don't run a database update on start."),
        )
        .arg(
            Arg::with_name("REINITIALIZE")
                .long("reinitialize")
                .help("Delete and initialize database, if it exists already."),
        )
        .arg(
            Arg::with_name("VAULT_NAME")
                .takes_value(true)
                .long("name")
                .help("Name of the vault."),
        );

    let matches = app.get_matches();

    info!("Starting UpEnd {}...", VERSION);
    let sys = actix::System::new("upend");

    let job_container = Arc::new(RwLock::new(jobs::JobContainer::default()));

    let vault_path = PathBuf::from(matches.value_of("DIRECTORY").unwrap());

    let open_result = database::open_upend(&vault_path, matches.is_present("REINITIALIZE"))
        .expect("failed to open database!");

    let db_pool = open_result.pool;

    let bind: SocketAddr = matches
        .value_of("BIND")
        .unwrap()
        .parse()
        .expect("Incorrect bind format.");
    info!("Starting server at: {}", &bind);

    let state = routes::State {
        vault_name: matches.value_of("VAULT_NAME").map(|s| s.to_string()),
        directory: vault_path.clone(),
        db_pool: db_pool.clone(),
        job_container: job_container.clone(),
    };

    // Start HTTP server
    HttpServer::new(move || {
        App::new()
            .data(state.clone())
            .wrap(middleware::Logger::default().exclude("/api/jobs"))
            .service(routes::get_raw)
            .service(routes::get_query)
            .service(routes::get_object)
            .service(routes::put_object)
            .service(routes::delete_object)
            .service(routes::api_refresh)
            .service(routes::list_hier)
            .service(routes::latest_files)
            .service(routes::get_file)
            .service(routes::get_jobs)
            .service(routes::get_info)
            .service(
                actix_files::Files::new(
                    "/",
                    env::current_exe().unwrap().parent().unwrap().join("webui"),
                )
                .index_file("index.html"),
            )
    })
    .bind(&bind)?
    .run();

    if !matches.is_present("NO_INITIAL_UPDATE") {
        info!("Running initial update...");
        actix::spawn(filesystem::rescan_vault(db_pool, vault_path, job_container));
    }

    if !matches.is_present("NO_BROWSER") {
        let ui_result = webbrowser::open(&format!("http://localhost:{}", bind.port()));
        if ui_result.is_err() {
            warn!("Could not open UI in browser!");
        }
    }

    Ok(sys.run()?)
}
