import json

def build_stash_key(session_id, test_num):
    return "%s_%s" % (session_id, test_num)

def main(request, response):
    """Helper handler for Beacon tests.

    It handles two forms of requests:

    STORE:
        A URL with a query string of the form 'cmd=store&sid=<token>&tidx=<test_index>&tid=<test_name>'.

        Stores the receipt of a sendBeacon() request along with its validation result, returning HTTP 200 OK.

        Parameters:
            tidx - the integer index of the test.
            tid - a friendly identifier or name for the test, used when returning results.

    STAT:
        A URL with a query string of the form 'cmd=stat&sid=<token>&tidx_min=<min_test_index>&tidx_max=<max_test_index>'.

        Retrieves the results of test with indices [min_test_index, max_test_index] and returns them as
        a JSON array and HTTP 200 OK status code. Due to the eventual read-once nature of the stash, results for a given test
        are only guaranteed to be returned once, though they may be returned multiple times.

        Parameters:
            tidx_min - the lower-bounding integer test index.
            tidx_max - the upper-bounding integer test index.

        Example response body:
            [{"id": "Test1", error: null}, {"id": "Test2", error: "some validation details"}]

    Common parameters:
        cmd - the command, 'store' or 'stat'.
        sid - session id used to provide isolation to a test run comprising multiple sendBeacon()
              tests.
    """

    session_id = request.GET.first("sid");
    command = request.GET.first("cmd").lower();

    # Workaround to circumvent the limitation that cache keys
    # can only be UUID's.
    def wrap_key(key, path):
        return (str(path), str(key))
    request.server.stash._wrap_key = wrap_key

    # Append CORS headers if needed.
    if "origin" in request.GET:
        response.headers.set("Access-Control-Allow-Origin", request.GET.first("origin"))
    if "credentials" in request.GET:
        response.headers.set("Access-Control-Allow-Credentials", request.GET.first("credentials"))

    # Handle the 'store' and 'stat' commands.
    if command == "store":
        # The test id is just used to make the results more human-readable.
        test_id = request.GET.first("tid")
        # The test index is used to build a predictable stash key, together
        # with the unique session id, in order to retrieve a range of results
        # later knowing the index range.
        test_idx = request.GET.first("tidx")
        test_data = { "id": test_id, "error": None }

        # Only store the actual POST requests, not any preflight/OPTIONS requests we may get.
        if request.method == "POST":
            test_data_key = build_stash_key(session_id, test_idx)

            payload = ""
            if "Content-Type" in request.headers and \
               "form-data" in request.headers["Content-Type"]:
                if "payload" in request.POST:
                    # The payload was sent as a FormData.
                    payload = request.POST.first("payload")
                else:
                    # A FormData was sent with an empty payload.
                    pass
            else:
                # The payload was sent as either a string, Blob, or BufferSource.
                payload = request.body

            payload_parts = filter(None, payload.split(":"))
            if len(payload_parts) > 0:
                payload_size = int(payload_parts[0])

                # Confirm the payload size sent matches with the number of characters sent.
                if payload_size != len(payload_parts[1]):
                    test_data["error"] = "expected %d characters but got %d" % (payload_size, len(payload_parts[1]))
                else:
                    # Confirm the payload contains the correct characters.
                    for i in range(0, payload_size):
                        if payload_parts[1][i] != "*":
                            test_data["error"] = "expected '*' at index %d but got '%s''" % (i, payload_parts[1][i])
                            break

            # Store the result in the stash so that it can be retrieved
            # later with a 'stat' command.
            request.server.stash.put(test_data_key, test_data)
        elif request.method == "OPTIONS":
            # If we expect a preflight, then add the cors headers we expect, otherwise log an error as we shouldn't
            # send a preflight for all requests.
            if "preflightExpected" in request.GET:
                response.headers.set("Access-Control-Allow-Headers", "content-type")
                response.headers.set("Access-Control-Allow-Methods", "POST")
            else:
                test_data_key = build_stash_key(session_id, test_idx)
                test_data["error"] = "Preflight not expected."
                request.server.stash.put(test_data_key, test_data)
    elif command == "stat":
        test_idx_min = int(request.GET.first("tidx_min"))
        test_idx_max = int(request.GET.first("tidx_max"))

        # For each result that has come in, append it to the response.
        results = []
        for test_idx in range(test_idx_min, test_idx_max+1): # +1 because end is exclusive
            test_data_key = build_stash_key(session_id, test_idx)
            test_data = request.server.stash.take(test_data_key)
            if test_data:
                results.append(test_data)

        response.headers.set("Content-Type", "text/plain")
        response.content = json.dumps(results)
    else:
        response.status = 400 # BadRequest