Sparqlpuppy wants to be a SOLID server when it grows up
// sparqlpuppy - an LDP/SOLID Server
// Copyright (C) 2020 Azure <azure@fox.blue>
//
// This program is free software: you can redistribute it and/or
// modify it under the terms of the GNU Affero General Public License
// as published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public
// License along with this program.  If not, see
// <https://www.gnu.org/licenses/>.

use crate::files;
use crate::{SETTINGS, STORE};
use async_std::{fs, io::ErrorKind, net::SocketAddr, path::Path, path::PathBuf};
use fehler::{throw, throws};
use http_types::{ensure, Error};
use itertools::iproduct;
use oxigraph::store::rocksdb::RocksDbStore;

#[derive(Debug)]
pub struct Config {
    /// Path where uploaded files are stored
    pub file_path: PathBuf,
    /// Path to the RDF tuplestore
    pub db_path: PathBuf,
    /// Address to bind the socket to
    pub addr: SocketAddr,
}

#[throws]
fn configure() {
    let mut settings = config::Config::default();
    settings.set_default("file_path", "/srv/sparqlpuppy/files/")?;
    settings.set_default("db_path", "/srv/sparqlpuppy/db/")?;
    settings.set_default("addr", "127.0.0.1:8080")?;
    settings.merge(config::Environment::with_prefix("SPARQLPUPPY"))?;

    SETTINGS
        .set(Config {
            file_path: settings.get_str("file_path")?.into(),
            db_path: settings.get_str("db_path")?.into(),
            addr: settings.get_str("addr")?.parse()?,
        })
        .unwrap()
}

#[throws]
async fn mkhier(path: impl AsRef<Path>) {
    let m = fs::metadata(&path).await;
    match m {
        Err(e) => {
            if e.kind() == ErrorKind::NotFound {
                fs::create_dir_all(&path).await?
            } else {
                throw!(e)
            }
        }
        Ok(m) => ensure!(
            m.is_dir(),
            "{:?} exists but is not a directory",
            path.as_ref()
        ),
    }
}

#[throws]
async fn create_filehier() {
    let settings = SETTINGS.get().unwrap();
    let hexits = [
        Path::new("0"),
        Path::new("1"),
        Path::new("2"),
        Path::new("3"),
        Path::new("4"),
        Path::new("5"),
        Path::new("6"),
        Path::new("7"),
        Path::new("8"),
        Path::new("9"),
        Path::new("a"),
        Path::new("b"),
        Path::new("c"),
        Path::new("d"),
        Path::new("e"),
        Path::new("f"),
    ];
    let base = settings.file_path.as_path();
    for (i, j, k) in iproduct!(&hexits, &hexits, &hexits) {
        let path: PathBuf = [base, i, j, k].iter().collect();
        mkhier(path).await?
    }
    let path: PathBuf = [base, Path::new("ephem")].iter().collect();
    fs::remove_dir_all(&path).await?;
    mkhier(path).await?;
}

#[throws]
pub async fn setup() {
    configure()?;
    create_filehier().await?;
    let settings = SETTINGS.get().unwrap();
    ensure!(
        STORE.set(RocksDbStore::open(&settings.db_path)?).is_ok(),
        "Can't happen: Unable to store database handle."
    );
}