#![allow(dead_code)]
use anyhow::{anyhow, Result};
use json::JsonValue;
use std::fmt::Display;
use std::sync::mpsc;
use std::sync::mpsc::Sender;
use std::thread;
use std::thread::JoinHandle;
use std::time::Instant;

pub mod challenges;

const USER_NAME: &str = "Jonah";

pub fn compute_async<
    T1: Display + Send + 'static,
    T2: Display + Send + 'static,
    R: Send + 'static
>(
    function: impl FnMut(Sender<(T1, T2)>) -> R + Send + 'static
) -> R {
    compute_async_with_name(USER_NAME, function)
}


pub fn compute_async_with_name<
    T1: Display + Send + 'static,
    T2: Display + Send + 'static,
    R: Send + 'static
>(
    name: &'static str,
    mut function: impl FnMut(Sender<(T1, T2)>) -> R + Send + 'static
) -> R {
    let (tx, jh) = spawn_submission_thread(name);
    let result = function(tx);
    jh.join().unwrap();
    result
}

pub fn spawn_submission_thread<
    T1: Display + Send + 'static,
    T2: Display + Send + 'static
>(
    name: &'static str
) -> (Sender<(T1, T2)>, JoinHandle<()>) {
    let (tx, rx) = mpsc::channel::<(T1, T2)>();
    let handle = thread::spawn(move || {
        let mut t = Instant::now();
        for (n, x) in rx {
            let t2 = Instant::now();
            println!("\nn = {n}\nx = {x}\nduration = {}", t2.duration_since(t).as_millis());
            t = t2;

            match submit_with_name(name, n, x) {
                Ok(_) => {}
                Err(err) => eprintln!("{:?}", err),
            };
        }
    });
    (tx, handle)
}

pub fn submit<
    T1: Display + Send,
    T2: Display + Send
>(
    n: T1, x: T2
) -> Result<Option<String>> {
    submit_with_name(USER_NAME, n, x)
}

pub fn submit_with_name<
    T1: Display + Send,
    T2: Display + Send
>(
    name: &str, n: T1, x: T2
) -> Result<Option<String>> {
    let name = percent_encoding::percent_encode(name.as_bytes(), percent_encoding::NON_ALPHANUMERIC);
    let url = format!("https://button.qedaka.de/server.php?name={}&n={n}&x={x}", name);
    let body = reqwest::blocking::get(url)?.text()?;
    match json::parse(&body)? {
        JsonValue::Object(obj) => {
            if let Some(error) = obj.get("error") {
                Err(anyhow!("{:?}", error))
            } else if let Some(flag) = obj.get("flag") {
                Ok(Some(flag.to_string()))
            } else {
                Ok(None)
            }
        }
        json => Err(anyhow!("Unexpected JSON response {:?}", json)),
    }
}
