// 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.

// This is intended for permanent JS behavior changes for mocking out
// non-deterministic behavior. For temporary suppressions, please refer to
// v8_suppressions.js.
// This file is loaded before each correctness test cases and won't get
// minimized.


// This will be overridden in the test cases. The override can be minimized.
var prettyPrinted = function prettyPrinted(msg) { return msg; };

// Mock Math.random.
(function() {
  let index = 1;
  Math.random = function() {
      const x = Math.sin(index++) * 10000;
      return x - Math.floor(x);
  }
})();

// Mock Math.pow. Work around an optimization for -0.5.
(function() {
  const origMathPow = Math.pow;
  Math.pow = function(a, b) {
    if (b === -0.5) {
      return 0;
    } else {
      return origMathPow(a, b);
    }
  }
})();


// Mock Date.
(function() {
  let index = 0;
  let mockDate = 1477662728696;
  const mockDateNow = function() {
    index = (index + 1) % 10;
    mockDate = mockDate + index + 1;
    return mockDate;
  }

  const origDate = Date;
  const construct = Reflect.construct;
  const constructDate = function(args) {
    let result;
    if (args.length) {
      result = construct(origDate, args);
    } else {
      result = new origDate(mockDateNow());
    }
    result.constructor = function(...args) { return constructDate(args); }
    Object.defineProperty(
        result, "constructor", { configurable: false, writable: false });
    return result;
  }

  origDate.prototype.constructor = function(...args) {
    return constructDate(args);
  };

  var handler = {
    apply: function(target, thisArg, args) {
      return constructDate(args);
    },
    construct: function(target, args, newTarget) {
      return constructDate(args);
    },
    get: function(target, property, receiver) {
      if (property == "now") {
        return mockDateNow;
      }
      if (property == "prototype") {
        return origDate.prototype;
      }
    },
  }

  Date = new Proxy(Date, handler);
})();

// Mock performance methods.
performance.now = function() { return 1.2; };
performance.measureMemory = function() { return []; };

// Mock readline so that test cases don't hang.
readline = function() { return "foo"; };

// Mock stack traces.
Error.prepareStackTrace = function(error, structuredStackTrace) {
  return "";
};
Object.defineProperty(
    Error, 'prepareStackTrace', { configurable: false, writable: false });

// Mock buffer access in float typed arrays because of varying NaN patterns.
(function() {
  const origIsNaN = isNaN;
  const deNaNify = function(value) { return origIsNaN(value) ? 1 : value; };
  const mock = function(type) {

    // Remove NaN values from parameters to "set" function.
    const set = type.prototype.set;
    type.prototype.set = function(array, offset) {
      if (Array.isArray(array)) {
        array = array.map(deNaNify);
      }
      set.apply(this, [array, offset]);
    };

    const handler = {
      // Remove NaN values from parameters to constructor.
      construct: function(target, args) {
        for (let i = 0; i < args.length; i++) {
          if (args[i] != null &&
              typeof args[i][Symbol.iterator] === 'function') {
            // Consume iterators.
            args[i] = Array.from(args[i]);
          }
          if (Array.isArray(args[i])) {
            args[i] = args[i].map(deNaNify);
          }
        }

        const obj = new (
            Function.prototype.bind.call(type, null, ...args));
        return new Proxy(obj, {
          get: function(x, prop) {
            if (typeof x[prop] == "function")
              return x[prop].bind(obj);
            return x[prop];
          },
          // Remove NaN values that get assigned.
          set: function(target, prop, value, receiver) {
            target[prop] = deNaNify(value);
            return value;
          }
        });
      },
    };
    return new Proxy(type, handler);
  }

  Float32Array = mock(Float32Array);
  Float64Array = mock(Float64Array);
})();

// Mock buffer access via DataViews because of varying NaN patterns.
(function() {
  const origIsNaN = isNaN;
  const deNaNify = function(value) { return origIsNaN(value) ? 1 : value; };
  const origSetFloat32 = DataView.prototype.setFloat32;
  DataView.prototype.setFloat32 = function(offset, value, ...rest) {
    origSetFloat32.call(this, offset, deNaNify(value), ...rest);
  };
  const origSetFloat64 = DataView.prototype.setFloat64;
  DataView.prototype.setFloat64 = function(offset, value, ...rest) {
    origSetFloat64.call(this, offset, deNaNify(value), ...rest);
  };
})();

// Mock Worker.
(function() {
  let index = 0;
  // TODO(machenbach): Randomize this for each test case, but keep stable
  // during comparison. Also data and random above.
  const workerMessages = [
    undefined, 0, -1, "", "foo", 42, [], {}, [0], {"x": 0}
  ];
  Worker = function(code){
    try {
      print(prettyPrinted(eval(code)));
    } catch(e) {
      print(prettyPrinted(e));
    }
    this.getMessage = function(){
      index = (index + 1) % 10;
      return workerMessages[index];
    }
    this.postMessage = function(msg){
      print(prettyPrinted(msg));
    }
  };
})();

// Mock Realm.
Realm.eval = function(realm, code) { return eval(code) };

// Mock the nondeterministic parts of WeakRef and FinalizationRegistry.
WeakRef.prototype.deref = function() { };
FinalizationRegistry = function(callback) { };
FinalizationRegistry.prototype.register = function(target, holdings) { };
FinalizationRegistry.prototype.unregister = function(unregisterToken) { };
FinalizationRegistry.prototype.cleanupSome = function() { };
FinalizationRegistry.prototype[Symbol.toStringTag] = "FinalizationRegistry";

// Mock the nondeterministic Atomics.waitAsync.
Atomics.waitAsync = function() {
  // Return a mock "Promise" whose "then" function will call the callback
  // immediately.
  return {'value': {'then': function (f) { f(); }}};
}
