(function() {
  var next_cache_index = 1;

  // Returns a promise that resolves to a newly created Cache object. The
  // returned Cache will be destroyed when |test| completes.
  function create_temporary_cache(test) {
    var uniquifier = String(++next_cache_index);
    var cache_name = self.location.pathname + '/' + uniquifier;

    test.add_cleanup(function() {
        self.caches.delete(cache_name);
      });

    return self.caches.delete(cache_name)
      .then(function() {
          return self.caches.open(cache_name);
        });
  }

  self.create_temporary_cache = create_temporary_cache;
})();

// Runs |test_function| with a temporary unique Cache passed in as the only
// argument. The function is run as a part of Promise chain owned by
// promise_test(). As such, it is expected to behave in a manner identical (with
// the exception of the argument) to a function passed into promise_test().
//
// E.g.:
//    cache_test(function(cache) {
//      // Do something with |cache|, which is a Cache object.
//    }, "Some Cache test");
function cache_test(test_function, description) {
  promise_test(function(test) {
      return create_temporary_cache(test)
        .then(test_function);
    }, description);
}

// A set of Request/Response pairs to be used with prepopulated_cache_test().
var simple_entries = [
  {
    name: 'a',
    request: new Request('http://example.com/a'),
    response: new Response('')
  },

  {
    name: 'b',
    request: new Request('http://example.com/b'),
    response: new Response('')
  },

  {
    name: 'a_with_query',
    request: new Request('http://example.com/a?q=r'),
    response: new Response('')
  },

  {
    name: 'A',
    request: new Request('http://example.com/A'),
    response: new Response('')
  },

  {
    name: 'a_https',
    request: new Request('https://example.com/a'),
    response: new Response('')
  },

  {
    name: 'a_org',
    request: new Request('http://example.org/a'),
    response: new Response('')
  },

  {
    name: 'cat',
    request: new Request('http://example.com/cat'),
    response: new Response('')
  },

  {
    name: 'catmandu',
    request: new Request('http://example.com/catmandu'),
    response: new Response('')
  },

  {
    name: 'cat_num_lives',
    request: new Request('http://example.com/cat?lives=9'),
    response: new Response('')
  },

  {
    name: 'cat_in_the_hat',
    request: new Request('http://example.com/cat/in/the/hat'),
    response: new Response('')
  },

  {
    name: 'non_2xx_response',
    request: new Request('http://example.com/non2xx'),
    response: new Response('', {status: 404, statusText: 'nope'})
  },

  {
    name: 'error_response',
    request: new Request('http://example.com/error'),
    response: Response.error()
  },
];

// A set of Request/Response pairs to be used with prepopulated_cache_test().
// These contain a mix of test cases that use Vary headers.
var vary_entries = [
  {
    name: 'vary_cookie_is_cookie',
    request: new Request('http://example.com/c',
                         {headers: {'Cookies': 'is-for-cookie'}}),
    response: new Response('',
                           {headers: {'Vary': 'Cookies'}})
  },

  {
    name: 'vary_cookie_is_good',
    request: new Request('http://example.com/c',
                         {headers: {'Cookies': 'is-good-enough-for-me'}}),
    response: new Response('',
                           {headers: {'Vary': 'Cookies'}})
  },

  {
    name: 'vary_cookie_absent',
    request: new Request('http://example.com/c'),
    response: new Response('',
                           {headers: {'Vary': 'Cookies'}})
  }
];

// Run |test_function| with a Cache object and a map of entries. Prior to the
// call, the Cache is populated by cache entries from |entries|. The latter is
// expected to be an Object mapping arbitrary keys to objects of the form
// {request: <Request object>, response: <Response object>}. There's no
// guarantee on the order in which entries will be added to the cache.
//
// |test_function| should return a Promise that can be used with promise_test.
function prepopulated_cache_test(entries, test_function, description) {
  cache_test(function(cache) {
      var p = Promise.resolve();
      var hash = {};
      return Promise.all(entries.map(function(entry) {
          hash[entry.name] = entry;
          return cache.put(entry.request.clone(),
                           entry.response.clone())
            .catch(function(e) {
                assert_unreached(
                  'Test setup failed for entry ' + entry.name + ': ' + e);
            });
        }))
        .then(function() {
            assert_equals(Object.keys(hash).length, entries.length);
        })
        .then(function() {
            return test_function(cache, hash);
        });
    }, description);
}

// Helper for testing with Headers objects. Compares Headers instances
// by serializing |expected| and |actual| to arrays and comparing.
function assert_header_equals(actual, expected, description) {
    assert_class_string(actual, "Headers", description);
    var header;
    var actual_headers = [];
    var expected_headers = [];
    for (header of actual)
        actual_headers.push(header[0] + ": " + header[1]);
    for (header of expected)
        expected_headers.push(header[0] + ": " + header[1]);
    assert_array_equals(actual_headers, expected_headers,
                        description + " Headers differ.");
}

// Helper for testing with Response objects. Compares simple
// attributes defined on the interfaces, as well as the headers. It
// does not compare the response bodies.
function assert_response_equals(actual, expected, description) {
    assert_class_string(actual, "Response", description);
    ["type", "url", "status", "ok", "statusText"].forEach(function(attribute) {
        assert_equals(actual[attribute], expected[attribute],
                      description + " Attributes differ: " + attribute + ".");
    });
    assert_header_equals(actual.headers, expected.headers, description);
}

// Assert that the two arrays |actual| and |expected| contain the same
// set of Responses as determined by assert_response_equals. The order
// is not significant.
//
// |expected| is assumed to not contain any duplicates.
function assert_response_array_equivalent(actual, expected, description) {
    assert_true(Array.isArray(actual), description);
    assert_equals(actual.length, expected.length, description);
    expected.forEach(function(expected_element) {
        // assert_response_in_array treats the first argument as being
        // 'actual', and the second as being 'expected array'. We are
        // switching them around because we want to be resilient
        // against the |actual| array containing duplicates.
        assert_response_in_array(expected_element, actual, description);
    });
}

// Asserts that two arrays |actual| and |expected| contain the same
// set of Responses as determined by assert_response_equals(). The
// corresponding elements must occupy corresponding indices in their
// respective arrays.
function assert_response_array_equals(actual, expected, description) {
    assert_true(Array.isArray(actual), description);
    assert_equals(actual.length, expected.length, description);
    actual.forEach(function(value, index) {
        assert_response_equals(value, expected[index],
                               description + " : object[" + index + "]");
    });
}

// Equivalent to assert_in_array, but uses assert_response_equals.
function assert_response_in_array(actual, expected_array, description) {
    assert_true(expected_array.some(function(element) {
        try {
            assert_response_equals(actual, element);
            return true;
        } catch (e) {
            return false;
        }
    }), description);
}
