<!DOCTYPE html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/webxr_test_constants.js"></script>
<script src="resources/webxr_util.js"></script>
<script src="resources/webxr_test_asserts.js"></script>

<script>

let fakeDeviceInitParams = TRACKED_IMMERSIVE_DEVICE;

let isValidViewport = function(viewport) {
  // Ensure the returned object is an XRViewport object
  assert_not_equals(viewport, null);
  assert_true(viewport instanceof XRViewport);

  // Ensure the viewport dimensions are valid
  assert_greater_than_equal(viewport.x, 0);
  assert_greater_than_equal(viewport.y, 0);
  assert_greater_than_equal(viewport.width, 1);
  assert_greater_than_equal(viewport.height, 1);
};

let containsViewport = function(outer, inner) {
  assert_greater_than_equal(inner.x, outer.x);
  assert_greater_than_equal(inner.y, outer.y);
  assert_less_than_equal(inner.x + inner.width, outer.x + outer.width);
  assert_less_than_equal(inner.y + inner.height, outer.y + outer.height);
};

let isSameViewport = function(a, b) {
  return a.x == b.x && a.y == b.y && a.width == b.width && a.height == b.height;
};

let assertSameViewport = function(a, b) {
  assert_equals(a.x, b.x, "viewport x should match");
  assert_equals(a.y, b.y, "viewport y should match");
  assert_equals(a.width, b.width, "viewport width should match");
  assert_equals(a.height, b.height, "viewport height should match");
};

let testFunction = function(name, firstScale, nextFrame, session, fakeDeviceController, t) {
  return session.requestReferenceSpace('viewer')
      .then((space) => new Promise((resolve) => {
    function onFrame(time, xrFrame1) {
      let debug = xr_debug.bind(this, name);
      debug('first frame');
      let layer = xrFrame1.session.renderState.baseLayer;

      let fullViewports = [];

      let views1 = xrFrame1.getViewerPose(space).views;

      for (view of views1) {
        let viewport1a = layer.getViewport(view);
        t.step(() => isValidViewport(viewport1a));
        fullViewports.push(viewport1a);
      }

      // Now request a changed viewport scale. This must not change the
      // viewports within this frame since they were already queried.
      // If the UA supports viewport scaling, the change applies on the
      // next frame. If it doesn't support viewport scaling, this call
      // has no effect.
      for (view of views1) {
        view.requestViewportScale(firstScale);
      }

      t.step(() => {
        for (let i = 0; i < views1.length; ++i) {
          let viewport1b = layer.getViewport(views1[i]);
          assertSameViewport(viewport1b, fullViewports[i]);
        }
      });

      if (nextFrame) {
        session.requestAnimationFrame((time, xrFrame2) =>
          nextFrame(name, t, session, space, layer, fullViewports, xrFrame2, resolve));
      } else {
        // test is done
        resolve();
      }
    }

    session.requestAnimationFrame(onFrame);
  }));
};

let testViewportValid = function(name, t, session, space, layer, fullViewports, xrFrame, resolve) {
  let debug = xr_debug.bind(this, name);
  debug('second frame');
  let views = xrFrame.getViewerPose(space).views;
  for (let i = 0; i < views.length; ++i) {
    let viewport = layer.getViewport(views[i]);
    t.step(() => isValidViewport(viewport));
    t.step(() => containsViewport(fullViewports[i], viewport));
  }
  resolve();
};

let testScaleAppliedNextFrame = function(name, t, session, space, layer, fullViewports, xrFrame, resolve) {
  let debug = xr_debug.bind(this, name);
  debug('second frame');
  let supportsScaling = false;
  let views = xrFrame.getViewerPose(space).views;
  for (let i = 0; i < views.length; ++i) {
    let viewport = layer.getViewport(views[i]);
    t.step(() => isValidViewport(viewport));
    t.step(() => containsViewport(fullViewports[i], viewport));
    if (!isSameViewport(fullViewports[i], viewport)) {
      supportsScaling = true;
    }
  }
  debug("supportsScaling=" + supportsScaling);
  t.step(() => {
    assert_implements_optional(supportsScaling, "requestViewportScale has no effect");
  });
  resolve();
};

let testScaleSameFrame = function(name, t, session, space, layer, fullViewports, xrFrame, resolve) {
  let debug = xr_debug.bind(this, name);
  debug('second frame');
  let supportsScaling = false;
  let views = xrFrame.getViewerPose(space).views;
  let viewports2 = [];
  for (let i = 0; i < views.length; ++i) {
    let viewport2 = layer.getViewport(views[i]);
    viewports2.push(viewport2);
    if (!isSameViewport(fullViewports[i], viewport2)) {
      supportsScaling = true;
    }
  }
  debug("supportsScaling=" + supportsScaling);
  if (!supportsScaling) {
    // End the test early.
    t.step(() => {
      assert_implements_optional(false, "requestViewportScale has no effect");
      resolve();
    });
  }

  session.requestAnimationFrame((time, xrFrame3) => {
    let views3 = xrFrame3.getViewerPose(space).views;
    // Apply a new viewport scale before requesting viewports,
    // this should take effect on the same frame.
    for (view of views3) {
      view.requestViewportScale(0.75);
    }
    for (let i = 0; i < views3.length; ++i) {
      let viewport3 = layer.getViewport(views3[i]);
      t.step(() => isValidViewport(viewport3));
      t.step(() => containsViewport(fullViewports[i], viewport3));
      t.step(() => containsViewport(viewport3, viewports2[i]));
      t.step(() => {
        // We don't know the exact expected size, but it should be in
        // between the half-size and full-size viewports.
        assert_false(isSameViewport(viewports2[i], viewport3));
        assert_false(isSameViewport(fullViewports[i], viewport3));
      });
    }
    resolve();
  });
};

let testRecommendedScale = function(name, t, session, space, layer, fullViewports, xrFrame, resolve) {
  let debug = xr_debug.bind(this, name);
  debug('second frame');
  let views = xrFrame.getViewerPose(space).views;
  let haveRecommendedScale = false;
  for (view of views) {
    let recommended = view.recommendedViewportScale;
    view.requestViewportScale(recommended);
    if (recommended !== null && recommended !== undefined) {
      haveRecommendedScale = true;
      t.step(() => {
        assert_greater_than(recommended, 0.0, "recommended scale invalid");
        assert_less_than_equal(recommended, 1.0, "recommended scale invalid");
      });
    }
  }
  t.step(() => {
    assert_implements_optional(haveRecommendedScale, "recommendedViewportScale not provided");
  });
  for (let i = 0; i < views.length; ++i) {
    let viewport = layer.getViewport(views[i]);
    t.step(() => isValidViewport(viewport));
    t.step(() => containsViewport(fullViewports[i], viewport));
  }
  resolve();
};

for (let mode of ['inline', 'immersive-vr']) {
  xr_session_promise_test(
    "requestViewportScale valid viewport for " + mode + " session",
    testFunction.bind(this, "valid viewport (0.5) " + mode, 0.5, testViewportValid),
    fakeDeviceInitParams,
    mode);
  xr_session_promise_test(
    "requestViewportScale valid viewport w/ null scale for " + mode + " session",
    testFunction.bind(this, "valid viewport (null) " + mode, null, testViewportValid),
    fakeDeviceInitParams,
    mode);
  xr_session_promise_test(
    "requestViewportScale valid viewport w/ undefined scale for " + mode + " session",
    testFunction.bind(this, "valid viewport (undefined) " + mode, null, testViewportValid),
    fakeDeviceInitParams,
    mode);
  xr_session_promise_test(
    "requestViewportScale valid viewport w/ very small scale for " + mode + " session",
    testFunction.bind(this, "valid viewport (tiny) " + mode, 1e-6, testViewportValid),
    fakeDeviceInitParams,
    mode);
  xr_session_promise_test(
    "requestViewportScale applied next frame for " + mode + " session",
    testFunction.bind(this, "scale applied next frame " + mode, 0.5, testScaleAppliedNextFrame),
    fakeDeviceInitParams,
    mode);
  xr_session_promise_test(
    "requestViewportScale same frame for " + mode + " session",
    testFunction.bind(this, "same frame " + mode, 0.5, testScaleSameFrame),
    fakeDeviceInitParams,
    mode);
  xr_session_promise_test(
    "recommendedViewportScale for " + mode + " session",
    testFunction.bind(this, "recommendedViewportScale " + mode, 0.5, testRecommendedScale),
    fakeDeviceInitParams,
    mode);
}

</script>
