use fastly::http::request::PollResult::{Done, Pending};
use fastly::{http::request::PendingRequest, Error, Request, Response};
use std::{
    thread::sleep,
    time::{Duration, Instant},
};

const NUM_ELEMENT: usize = 10;

// Pending request with order information
struct PendingRequestWithOrder {
    // The order that the request is sent
    order: usize,
    // Pending request
    pending_req: PendingRequest,
}

struct TimeInfo {
    start: Instant,
    previous: Instant,
}

#[fastly::main]
fn main(_req: Request) -> Result<Response, Error> {
    let now = Instant::now();
    let mut time_info = TimeInfo {
        start: now,
        previous: now,
    };

    let mut pending_reqs_vec = Vec::new();

    // Send async requests
    for order in 0..NUM_ELEMENT {
        let url = format!("https://http-me.glitch.me/body=response-{}", order + 1);
        let req = Request::get(url);
        let pending_req = req.send_async("origin_0")?;
        pending_reqs_vec.push(PendingRequestWithOrder { order, pending_req });
    }

    let mut response_slice: [String; NUM_ELEMENT] = Default::default();

    while !pending_reqs_vec.is_empty() {
        pending_reqs_vec =
            polling_pending_requests(pending_reqs_vec, &mut response_slice, &mut time_info);
    }

    let resp = response_slice.join(",");

    println!("Execution time: {} ms", time_info.start.elapsed().as_millis());

    Ok(Response::from_body(resp))
}

fn polling_pending_requests(
    pending_reqs_vec: Vec<PendingRequestWithOrder>,
    response_slice: &mut [String; NUM_ELEMENT],
    t: &mut TimeInfo,
) -> Vec<PendingRequestWithOrder> {
    let mut remaining = Vec::new();

    for p in pending_reqs_vec {
        match p.pending_req.poll() {
            Pending(r) => {
                // Put the pending request back to remaing Vec for next round of polling
                remaining.push(PendingRequestWithOrder {
                    order: p.order,
                    pending_req: r,
                });

                // When we find a pending request is not ready,
                // it is highly likely we do not need to rush for the next poll,
                // So, sleep for sometime
                sleep(Duration::from_millis(1));
            }
            Done(Ok(resp)) => {
                // We got a resonse, put the result in the corresponding result slice slot
                let result = resp.into_body_str();

                println!(
                    "# {} {} Since start: {} ms Since previous response {} ms",
                    p.order,
                    &result,
                    t.start.elapsed().as_millis(),
                    t.previous.elapsed().as_millis()
                );
                t.previous = Instant::now();

                response_slice[p.order] = result;
            }
            _ => (),
        }
    }

    remaining
}