// Copyright 2016 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// A general-purpose engine for sending a sequence of protocol commands.
// The clients provide requests and response handlers, while the engine catches
// errors and makes sure that once there's nothing to do completeTest() is called.
// @param step is an object with command, params and callback fields
function runRequestSeries(step)
{
  processStep(step);

  function processStep(s)
  {
    try {
      processStepOrFail(s);
    } catch (e) {
      InspectorTest.log(e.stack);
      InspectorTest.completeTest();
    }
  }

  function processStepOrFail(s)
  {
    if (!s) {
      InspectorTest.completeTest();
      return;
    }
    if (!s.command) {
      // A simple loopback step.
      var next = s.callback();
      processStep(next);
      return;
    }

    var innerCallback = function(response)
    {
      if ("error" in response) {
        InspectorTest.log(response.error.message);
        InspectorTest.completeTest();
        return;
      }
      var next;
      try {
        next = s.callback(response.result);
      } catch (e) {
        InspectorTest.log(e.stack);
        InspectorTest.completeTest();
        return;
      }
      processStep(next);
    }
    var command = s.command.split(".");
    Protocol[command[0]][command[1]](s.params).then(innerCallback);
  }
}

var firstStep = { callback: callbackStart5 };

runRequestSeries(firstStep);

// 'Object5' section -- check properties of '5' wrapped as object  (has an internal property).

function callbackStart5()
{
  // Create an wrapper object with additional property.
  var expression = "(function(){var r = Object(5); r.foo = 'cat';return r;})()";

  return { command: "Runtime.evaluate", params: {expression: expression}, callback: callbackEval5 };
}
function callbackEval5(result)
{
  var id = result.result.objectId;
  if (id === undefined)
    throw new Error("objectId is expected");
  return {
    command: "Runtime.getProperties", params: {objectId: id, ownProperties: true}, callback: callbackProperties5
  };
}
function callbackProperties5(result)
{
  logGetPropertiesResult("Object(5)", result);
  return { callback: callbackStartNotOwn };
}


// 'Not own' section -- check all properties of the object, including ones from it prototype chain.

function callbackStartNotOwn()
{
  // Create an wrapper object with additional property.
  var expression = "({ a: 2, set b(_) {}, get b() {return 5;}, __proto__: { a: 3, c: 4, get d() {return 6;} }})";

  return { command: "Runtime.evaluate", params: {expression: expression}, callback: callbackEvalNotOwn };
}
function callbackEvalNotOwn(result)
{
  var id = result.result.objectId;
  if (id === undefined)
    throw new Error("objectId is expected");
  return {
    command: "Runtime.getProperties", params: {objectId: id, ownProperties: false}, callback: callbackPropertiesNotOwn
  };
}
function callbackPropertiesNotOwn(result)
{
  logGetPropertiesResult("Not own properties", result);
  return { callback: callbackStartAccessorsOnly };
}


// 'Accessors only' section -- check only accessor properties of the object.

function callbackStartAccessorsOnly()
{
  // Create an wrapper object with additional property.
  var expression = "({ a: 2, set b(_) {}, get b() {return 5;}, c: 'c', set d(_){} })";

  return { command: "Runtime.evaluate", params: {expression: expression}, callback: callbackEvalAccessorsOnly };
}
function callbackEvalAccessorsOnly(result)
{
  var id = result.result.objectId;
  if (id === undefined)
    throw new Error("objectId is expected");
  return {
    command: "Runtime.getProperties", params: {objectId: id, ownProperties: true, accessorPropertiesOnly: true}, callback: callbackPropertiesAccessorsOnly
  };
}
function callbackPropertiesAccessorsOnly(result)
{
  logGetPropertiesResult("Accessor only properties", result);
  return { callback: callbackStartArray };
}


// 'Array' section -- check properties of an array.

function callbackStartArray()
{
  var expression = "['red', 'green', 'blue']";
  return { command: "Runtime.evaluate", params: {expression: expression}, callback: callbackEvalArray };
}
function callbackEvalArray(result)
{
  var id = result.result.objectId;
  if (id === undefined)
    throw new Error("objectId is expected");
  return {
    command: "Runtime.getProperties", params: {objectId: id, ownProperties: true}, callback: callbackPropertiesArray
  };
}
function callbackPropertiesArray(result)
{
  logGetPropertiesResult("array", result);
  return { callback: callbackStartBound };
}


// 'Bound' section -- check properties of a bound function (has a bunch of internal properties).

function callbackStartBound()
{
  var expression = "Number.bind({}, 5)";
  return { command: "Runtime.evaluate", params: {expression: expression}, callback: callbackEvalBound };
}
function callbackEvalBound(result)
{
  var id = result.result.objectId;
  if (id === undefined)
    throw new Error("objectId is expected");
  return {
    command: "Runtime.getProperties", params: {objectId: id, ownProperties: true}, callback: callbackPropertiesBound
  };
}
function callbackPropertiesBound(result)
{
  logGetPropertiesResult("Bound function", result);
  return; // End of test
}

// A helper function that dumps object properties and internal properties in sorted order.
function logGetPropertiesResult(title, protocolResult)
{
  function hasGetterSetter(property, fieldName)
  {
    var v = property[fieldName];
    if (!v)
      return false;
    return v.type !== "undefined"
  }

  InspectorTest.log("Properties of " + title);
  var propertyArray = protocolResult.result;
  propertyArray.sort(NamedThingComparator);
  for (var i = 0; i < propertyArray.length; i++) {
    var p = propertyArray[i];
    var v = p.value;
    var own = p.isOwn ? "own" : "inherited";
    if (v)
      InspectorTest.log("  " + p.name + " " + own + " " + v.type + " " + v.value);
    else
      InspectorTest.log("  " + p.name + " " + own + " no value" +
        (hasGetterSetter(p, "get") ? ", getter" : "") + (hasGetterSetter(p, "set") ? ", setter" : ""));
  }
  var internalPropertyArray = protocolResult.internalProperties;
  if (internalPropertyArray) {
    InspectorTest.log("Internal properties");
    internalPropertyArray.sort(NamedThingComparator);
    for (var i = 0; i < internalPropertyArray.length; i++) {
      var p = internalPropertyArray[i];
      var v = p.value;
      InspectorTest.log("  " + p.name + " " + v.type + " " + v.value);
    }
  }

  function NamedThingComparator(o1, o2)
  {
    return o1.name === o2.name ? 0 : (o1.name < o2.name ? -1 : 1);
  }
}