import {NavigationState, NavigationError, navigation_objectify, navigation_deobjectify} from "../js/navigation_state.mjs"
import {NavigationStateDiff} from "../js/navigation_state_diff.mjs"
import {assert, assert_equal} from "/assert.mjs"

//TODO: maybe allow pairs reordering
function diff_and_apply(state1, state2, correct_diff_pairs) {
	let diff = NavigationStateDiff.from_states(state1, state2)
	if (correct_diff_pairs !== null) {
		assert_equal(0, 2, diff.as_string_pairs(), correct_diff_pairs)
	}
	let patched = diff.patch(state1)
	//console.log(navigation_objectify(state1.root_scope()), navigation_objectify(state2.root_scope()), patched, diff.as_string_pairs())
	assert_equal(0, 2, navigation_objectify(patched), navigation_objectify(state2.root_scope()))
}


function random_state() {
	let ret = NavigationState.create().editable();
	for (let i = 0; i < 100; i++) {
		ret.set(Array.from({length: Math.floor(Math.random() * 4) + 1}, () => ["a", "b", "c"][Math.floor(Math.random() * 3)]), ["a", "b", "c"][Math.floor(Math.random() * 3)])
	}
	return ret.freeze()
}

export async function runTests() {
	try {
		console.group("NavigationStateDiff")

		console.group("No change")
		diff_and_apply(NavigationState.create(), NavigationState.create(), [])
		console.groupEnd()

		console.group("Set new values")
		diff_and_apply(NavigationState.create(), NavigationState.create({ a: "1" }), [["a", "1"]])
		diff_and_apply(NavigationState.create(), NavigationState.create({ a: "1", b: "2" }), [["a", "1"], ["b", "2"]])
		diff_and_apply(NavigationState.create(), NavigationState.create({ a: { b: "1" } }), [["a.b", "1"]])
		diff_and_apply(NavigationState.create(), NavigationState.create({ a: "~" }), [["a", "%7E"]])
		diff_and_apply(NavigationState.create(), NavigationState.create({ "~": "~" }), [["%7E", "%7E"]])
		diff_and_apply(NavigationState.create(), NavigationState.create({ ".a~=": "&" }), [["%2Ea%7E%3D", "%26"]])
		diff_and_apply(NavigationState.create(), NavigationState.create({ ".": { ".": "." } }), [["%2E.%2E", "."]])
		diff_and_apply(NavigationState.create(), NavigationState.create().scope(".").set(".", "."), [["%2E.%2E", "."]])
		diff_and_apply(NavigationState.create(), NavigationState.create({ "--": "--" }), [["%2D-", "--"]])
		diff_and_apply(NavigationState.create(), NavigationState.create().private_merge({ a: "1" }), [["~.a", "1"]])
		diff_and_apply(NavigationState.create(), NavigationState.create().private_merge({ a: { b: "1" } }), [["~.a.b", "1"]])
		diff_and_apply(NavigationState.create(), NavigationState.create().scope("b").private_merge({ a: "1" }), [["b.~.a", "1"]])
		diff_and_apply(NavigationState.create(), NavigationState.create({ a: "1" }).private_merge({ a: "1" }), [["a", "1"], ["~.a", "1"]])
		diff_and_apply(NavigationState.create(), NavigationState.create().scope(".").private_set(".", "."), [["%2E.~.%2E", "."]])
		diff_and_apply(NavigationState.create(), NavigationState.create().scope(".").private_set([".", "b"], "."), [["%2E.~.%2E.b", "."]])
		console.groupEnd()

		console.group("Remove existing values")
		diff_and_apply(NavigationState.create({ a: "1" }), NavigationState.create(), [["a", "~"]])
		diff_and_apply(NavigationState.create({ a: "1", b: "2" }), NavigationState.create(), [["a", "~"], ["b", "~"]])
		diff_and_apply(NavigationState.create({ a: "1", b: "2" }), NavigationState.create({ b: "2" }), [["a", "~"]])
		diff_and_apply(NavigationState.create({ a: { b: "1", c: "2" } }), NavigationState.create({ a: { c: "2" } }), [["a.b", "~"]])
		diff_and_apply(NavigationState.create().private_merge({ a: "1" }), NavigationState.create(), [["~.a", "~"]])
		diff_and_apply(NavigationState.create().private_merge({ a: "1", b: "2" }), NavigationState.create(), [["~.a", "~"], ["~.b", "~"]])
		diff_and_apply(NavigationState.create().private_merge({ a: "1", b: "2" }), NavigationState.create().private_merge({ b: "2" }), [["~.a", "~"]])
		diff_and_apply(NavigationState.create().private_merge({ a: { b: "1", c: "2" } }), NavigationState.create().private_merge({ a: { c: "2" } }), [["~.a.b", "~"]])
		diff_and_apply(NavigationState.create().scope("x").set("z", "l").private_merge({ a: "1" }), NavigationState.create({x: { z: "l" }}), [["x.~.a", "~"]])
		diff_and_apply(NavigationState.create().scope("x").set("z", "l").private_merge({ a: "1", b: "2" }), NavigationState.create({x: { z: "l" }}), [["x.~.a", "~"], ["x.~.b", "~"]])
		diff_and_apply(NavigationState.create().scope("x").set("z", "l").private_merge({ a: "1", b: "2" }), NavigationState.create({x: { z: "l" }}).scope("x").private_merge({ b: "2" }), [["x.~.a", "~"]])
		diff_and_apply(NavigationState.create().scope("x").set("z", "l").private_merge({ a: { b: "1", c: "2" } }), NavigationState.create({x: { z: "l" }}).scope("x").private_merge({ a: { c: "2" } }), [["x.~.a.b", "~"]])
		console.groupEnd()

		console.group("Replace existing values")
		diff_and_apply(NavigationState.create({ a: "2" }), NavigationState.create({ a: "1" }), [["a", "1"]])
		diff_and_apply(NavigationState.create({ a: { c: "2" } }), NavigationState.create({ a: "1" }), [["a", "1"]])
		diff_and_apply(NavigationState.create({ a: "1" }), NavigationState.create({ a: { c: "2" } }), [["a.c", "2"]])
		diff_and_apply(NavigationState.create({ b: "1" }), NavigationState.create({ a: "1" }), [["b", "~"], ["a", "1"]])
		diff_and_apply(NavigationState.create().private_merge({ a: "2" }), NavigationState.create().private_merge({ a: "1" }), [["~.a", "1"]])
		diff_and_apply(NavigationState.create().private_merge({ a: { c: "2" } }), NavigationState.create().private_merge({ a: "1" }), [["~.a", "1"]])
		diff_and_apply(NavigationState.create().private_merge({ a: "1" }), NavigationState.create().private_merge({ a: { c: "2" } }), [["~.a.c", "2"]])
		diff_and_apply(NavigationState.create().private_merge({ b: "1" }), NavigationState.create().private_merge({ a: "1" }), [["~.b", "~"], ["~.a", "1"]])
		console.groupEnd()

		console.group("Check is_private")
		assert(0, 1, NavigationStateDiff.from_states(NavigationState.create(), NavigationState.create({ "~": "~" })).is_private === false)
		assert(0, 1, NavigationStateDiff.from_states(NavigationState.create(), NavigationState.create().private_merge({ a: "1" })).is_private === true)
		assert(0, 1, NavigationStateDiff.from_states(NavigationState.create().private_merge({ a: "1" }), NavigationState.create()).is_private === true)
		console.groupEnd()

		console.group("Check from_state_and_addresses")
		let state = NavigationState.create().merge({ d: { e: "3", f: "4" }, g: "h" }).private_merge({ a: { b: "1", c: "2" } })
		assert_equal(0, 1, NavigationStateDiff.exposed_diff(state, []).as_string_pairs(), [])
		assert_equal(0, 1, NavigationStateDiff.exposed_diff(state, ["g"]).as_string_pairs(), [["g","h"]])
		assert_equal(0, 1, NavigationStateDiff.exposed_diff(state, ["d.e"]).as_string_pairs(), [["d.e","3"]])
		assert_equal(0, 1, NavigationStateDiff.exposed_diff(state, ["d.e", "g"]).as_string_pairs(), [["d.e","3"],["g","h"]])
		assert_equal(0, 1, NavigationStateDiff.exposed_diff(state, ["d"]).as_string_pairs(), [])
		assert_equal(0, 1, NavigationStateDiff.exposed_diff(state, ["~.a.b"]).as_string_pairs(), [])
		console.groupEnd()

		console.group("Random")
		for (let i = 0; i < 100; i++) {
			diff_and_apply(random_state(), random_state(), null)
		}
		console.groupEnd()

		//TODO: error handling, broken patches

		console.groupEnd()

		document.getElementById("state_diff_result").innerHTML = "OK"
	} catch (exception) {
		document.getElementById("state_diff_result").innerHTML = "FAIL"
		throw  exception
	}
}
