// Debugger.prototype.findScripts can filter scripts by start and end line and column.
load(libdir + "asserts.js");

function assertThrowsTypeError(query) {
    assertThrowsInstanceOf(() => dbg.findScripts(query), TypeError);
}
function assertFound(query, scriptWrapper) {
    assertEq(dbg.findScripts(query).includes(scriptWrapper), true, `Script not found, but should be (query: ${JSON.stringify(query)})`);
}
function assertNotFound(query, scriptWrapper) {
    assertEq(dbg.findScripts(query).includes(scriptWrapper), false, `Script found but should not be (query: ${JSON.stringify(query)})`);
}

var g = newGlobal({newCompartment: true});
var dbg = new Debugger();
var gw = dbg.addDebuggee(g);

var scriptname = scriptdir + 'Debugger-findScripts-32-script';
g.load(scriptname);

var gfw = gw.makeDebuggeeValue(g.f);
var ggw = gw.makeDebuggeeValue(g.f());
var ghw = gw.makeDebuggeeValue(g.h);
var gjw = gw.makeDebuggeeValue(g.j);

// actors/source.js uses {start: ..., end:...} for the query
// {url:scriptName, start:{ line: 3, column: 0}, end:{line: 3, column: Infinity}}
// NOTE: 'start.line' and 'end.line' are 1-origin, like 'line'
// NOTE: 'start.column' and 'end.column' are 1-origin

// 'start' correct types
assertThrowsTypeError({url:scriptname, start:3, end: {line: 8}});
assertThrowsTypeError({url:scriptname, start:"hi", end: {line: 8}});
assertThrowsTypeError({url:scriptname, start: {line: .34}, end: {line: 8}});
assertThrowsTypeError({url:scriptname, start: {line: -1}, end: {line: 8}});
assertThrowsTypeError({url:scriptname, start: {line: {}}, end: {line: 8}});
// 'end' correct types
assertThrowsTypeError({url:scriptname, start: {line: 8}, end:3});
assertThrowsTypeError({url:scriptname, start: {line: 8}, end:"hi"});
assertThrowsTypeError({url:scriptname, start: {line: 8}, end: {line: .34}});
assertThrowsTypeError({url:scriptname, start: {line: 8}, end: {line: -1}});
assertThrowsTypeError({url:scriptname, start: {line: 8}, end: {line: {}}});
// 'start' requires 'end'
assertThrowsInstanceOf(() => dbg.findScripts({url:scriptname, start: {line: 8}}), Error);
assertThrowsInstanceOf(() => dbg.findScripts({url:scriptname, line: 3, start: {line: 8}}), Error);
// 'end' requires 'start'
assertThrowsInstanceOf(() => dbg.findScripts({url:scriptname, end: {line: 8}}), Error);
assertThrowsInstanceOf(() => dbg.findScripts({url:scriptname, line: 4, end: {line: 8}}), Error);
// 'start'/'end' takes priority over 'line'
assertNotFound({url:scriptname, line: 8, start: {line: 3}, end: {line: 3}}, gfw.script);
assertFound({url:scriptname, line: 3, start: {line: 8}, end: {line: 8}}, gfw.script);

// 'start'/'end' requires 'displayURL'/'url'/'source' like 'line'
assertThrowsTypeError({start: {line: 8}, end: {line: 8}});
assertFound({source:gfw.script.source, start: {line: 8}, end: {line: 8}}, gfw.script);
// won't TypeError is the important thing here
assertNotFound({displayURL:"f.js", start: {line: 8}, end: {line: 8}}, gfw.script);

// 'start' and 'end'
// 'start' after,  'end' after the function
assertNotFound({url:scriptname, start: {line: 14}, end: {line: 16}}, gfw.script);
// 'start' in,     'end' after
assertFound({url:scriptname, start: {line: 8}, end: {line: 16}}, gfw.script);
// 'start' in,     'end' in
assertFound({url:scriptname, start: {line: 8}, end: {line: 11}}, gfw.script);
// 'start' before, 'end' after
assertFound({url:scriptname, start: {line: 6}, end: {line: 13}}, gfw.script);
// 'start' before, 'end' in
assertFound({url:scriptname, start: {line: 6}, end: {line: 10}}, gfw.script);
// 'start' before, 'end' before
assertNotFound({url:scriptname, start: {line: 4}, end: {line: 6}}, gfw.script);
// 'start.line' and 'end.line' are inclusive
assertFound({url:scriptname, start: {line: 6}, end: {line: 7}}, gfw.script);
assertFound({url:scriptname, start: {line: 12}, end: {line: 12}}, gfw.script);
assertFound({url:scriptname, start: {line: 20}, end: {line: 20}}, gjw.script);
// 'start' after 'end'
assertThrowsInstanceOf(() => dbg.findScripts({url:scriptname, start: {line: 20}, end: {line: 7}}), Error);

// innermost filter
assertFound({url:scriptname, innermost: true, start: {line: 20}, end: {line: 20}}, gjw.script);
assertFound({url:scriptname, innermost: true, start: {line: 6}, end: {line: 9}}, ggw.script);
assertFound({url:scriptname, innermost: true, start: {line: 6}, end: {line: 13}}, ggw.script);
assertFound({url:scriptname, innermost: true, start: {line: 11}, end: {line: 13}}, ggw.script);

// Specifying a line range outside of all functions screens out all function scripts.
assertNotFound({url:scriptname, start: {line: 4}, end: {line: 6}}, gfw.script);
assertNotFound({url:scriptname, start: {line: 4}, end: {line: 6}}, ggw.script);
assertNotFound({url:scriptname, start: {line: 4}, end: {line: 6}}, ghw.script);

// A line range within a nested function selects all enclosing functions' scripts.
assertFound({url:scriptname, start: {line: 10}, end: {line: 11}}, gfw.script);
assertFound({url:scriptname, start: {line: 10}, end: {line: 11}}, ggw.script);
assertNotFound({url:scriptname, start: {line: 10}, end: {line: 11}}, ghw.script);

// A line range outside a non-nested function does not select that function.
assertNotFound({url:scriptname, start: {line: 7}, end: {line: 8}}, ggw.script);

// columns

// 'start.column' correct types
assertThrowsTypeError({url:scriptname, start: {line: 6, column: "hi"}, end: {line: 10}});
assertThrowsTypeError({url:scriptname, start: {line: 6, column: {}}, end: {line: 10}});
// 'start.column' correct range
assertThrowsInstanceOf(() => dbg.findScripts({url:scriptname, start: {line: 6, column: .34}, end: {line: 10}}), RangeError);
assertThrowsInstanceOf(() => dbg.findScripts({url:scriptname, start: {line: 6, column: -11}, end: {line: 10}}), RangeError);
assertThrowsInstanceOf(() => dbg.findScripts({url:scriptname, start: {line: 6, column: 0}, end: {line: 10}}), RangeError);
 // Columns are limited to 31 bits; see JS::ColumnNumberOneOriginLimit
const COLUMN_LIMIT = Math.pow(2,31) / 2 - 1;
assertThrowsInstanceOf(() => dbg.findScripts({url:scriptname, start: {line: 6, column: COLUMN_LIMIT + 1}, end: {line: 10}}), RangeError);
assertFound({url:scriptname, start: {line: 6, column: COLUMN_LIMIT}, end: {line: 10}}, gfw.script);
// 'end.column' correct types
assertThrowsTypeError({url:scriptname, start: {line: 6}, end: {line: 10, column: "hi"}});
assertThrowsTypeError({url:scriptname, start: {line: 6}, end: {line: 10, column: {}}});
// 'end.column' correct range
assertThrowsInstanceOf(() => dbg.findScripts({url:scriptname, start: {line: 6}, end: {line: 10, column: .34}}), RangeError);
assertThrowsInstanceOf(() => dbg.findScripts({url:scriptname, start: {line: 6}, end: {line: 10, column: -11}}), RangeError);
assertThrowsInstanceOf(() => dbg.findScripts({url:scriptname, start: {line: 6}, end: {line: 10, column: 0}}), RangeError);
assertThrowsInstanceOf(() => dbg.findScripts({url:scriptname, start: {line: 6}, end: {line: 10, column: COLUMN_LIMIT + 1}}), RangeError);
assertFound({url:scriptname, start: {line: 6}, end: {line: 10, column: COLUMN_LIMIT}}, gfw.script);

// 'start.column'
assertFound({url:scriptname, start: {line: 19, column: 10}, end: {line: 20}}, gjw.script);
assertFound({url:scriptname, start: {line: 19, column: 11}, end: {line: 20}}, gjw.script);
assertFound({url:scriptname, start: {line: 19, column: 24}, end: {line: 20}}, gjw.script);
assertFound({url:scriptname, start: {line: 19, column: 33}, end: {line: 20}}, gjw.script);

assertFound({url:scriptname, start: {line: 20, column: 10}, end: {line: 20}}, gjw.script);
assertFound({url:scriptname, start: {line: 20, column: 11}, end: {line: 20}}, gjw.script);
assertFound({url:scriptname, start: {line: 20, column: 24}, end: {line: 20}}, gjw.script);
// 'start.column' is past the end of the function
assertNotFound({url:scriptname, start: {line: 20, column: 33}, end: {line: 20}}, gjw.script);
assertFound({url:scriptname, start: {line: 20, column: 32}, end: {line: 20}}, gjw.script);
assertNotFound({url:scriptname, start: {line: 11, column: 5}, end: {line: 13}}, ggw.script);
assertFound({url:scriptname, start: {line: 11, column: 4}, end: {line: 13}}, ggw.script);

assertFound({url:scriptname, start: {line: 20, column: 10}, end: {line: 21}}, gjw.script);
assertFound({url:scriptname, start: {line: 20, column: 11}, end: {line: 21}}, gjw.script);
assertFound({url:scriptname, start: {line: 20, column: 24}, end: {line: 21}}, gjw.script);

// 'end.column' only considered when script.startLine == endLine
assertFound({url:scriptname, start: {line: 6}, end: {line: 10, column: 5}}, gfw.script);
assertFound({url:scriptname, start: {line: 7}, end: {line: 10, column: 5}}, gfw.script);
assertFound({url:scriptname, start: {line: 6}, end: {line: 7, column: 11}}, gfw.script);
assertNotFound({url:scriptname, start: {line: 6}, end: {line: 7, column: 5}}, gfw.script);
assertNotFound({url:scriptname, start: {line: 19}, end: {line: 20, column: 10}}, gjw.script);
assertFound({url:scriptname, start: {line: 19}, end: {line: 20, column: 11}}, gjw.script);

// both 'start.column' and 'end.column'
assertFound({url:scriptname, start: {line: 20, column: 11}, end: {line: 20, column: 23}}, gjw.script);
assertNotFound({url:scriptname, start: {line: 20, column: 1}, end: {line: 20, column: 10}}, gjw.script);
assertNotFound({url:scriptname, start: {line: 20, column: 33}, end: {line: 20, column: 35}}, gjw.script);
// 'start.column' > 'end.column'
assertNotFound({url:scriptname, start: {line: 20, column: 7}, end: {line: 20, column: 5}}, gfw.script);
