function parse_triples(graph, base, errors) {
    var triples = [];
    var lines = graph.split(/\n/);
    for (var i = 0; i < lines.length; ++i) {
        if (! lines[i].match(/\S/))
            continue;
        var m = lines[i].match(/^\s*(\S+:\S+|<[^>]*>)\s+(\S+:\S+|<[^>]*>)\s+(\S+:\S+|<[^>]*>|"([^\\"]|\\.)*"(?:@\w+)?)\s*\.\s*$/);
        if (m)
            triples.push(canonicalise([m[1], m[2], m[3]], base));
        else
            errors.push('INVALID LINE: '+lines[i]);
    }
    triples.sort(compare_triples);
    return triples;
}

function canonicalise(triple, base) {
    var s = triple[0];
    var p = triple[1];
    var o = triple[2];
    if (s == '<>')
        s = '<'+base+'>';
    // Treat all blank nodes as equivalent (TODO: should be stricter)
    if (s.substr(0, 2) == '_:')
        s = '_';
    if (o.substr(0, 2) == '_:')
        o = '_';
    return [s, p, o];
}

function compare_triples(a, b) {
    if (a[0] < b[0]) return -1;
    if (a[0] > b[0]) return 1;
    if (a[1] < b[1]) return -1;
    if (a[1] > b[1]) return 1;
    if (a[2] < b[2]) return -1;
    if (a[2] > b[2]) return 1;
    return 0;
}

function compare_graphs(graph1, graph2, e1, e2, base) {
    var t1 = parse_triples(graph1, base, e1);
    var t2 = parse_triples(graph2, base, e2);
    if (t1.length != t2.length) return false;
    for (var i = 0; i < t1.length; ++i)
        if (compare_triples(t1[i], t2[i]) != 0)
            return false;
    return true;
}

function test_finished(id) {
    var iframe = document.getElementById('iframe-'+id);
    var iframe_output = iframe.contentWindow.document.getElementById('output').textContent;
    var expected = document.getElementById('expected-'+id).textContent;
    var status;
    if (iframe_output.match(/FAIL:/)) {
        status = false;
    } else {
        var err1 = [], err2 = [];
        var status = compare_graphs(iframe_output, expected, err1, err2, iframe.src);
        if (err1.length)
            iframe.parentNode.appendChild(document.createTextNode('Parse errors: '+err1.join('; \n')));
    }
    iframe.className += (status ? ' pass' : ' fail');
}

function compare_test(out_ids, exp_id) {
    var expected = document.getElementById(exp_id).textContent;
    for (var i = 0; i < out_ids.length; ++i) {
        var out = document.getElementById(out_ids[i]);
        if (! out) continue;
        var output = out.textContent;
        var status;
        if (output.match(/FAIL:/)) {
            status = false;
        } else {
            var err1 = [], err2 = [];
            status = compare_graphs(output, expected, err1, err2, '{BASE}');
            if (err1.length)
                out.parentNode.appendChild(document.createTextNode('Parse errors: '+err1.join('; \n')));
        }
        out.parentNode.className += (status ? ' pass' : ' fail');
    }
}

function generate_output(ids) {
    var output = '';
    for (var i = 0; i < ids.length; ++i) {
        var iframe = document.getElementById('iframe-'+ids[i]);
        var iframe_output = iframe.contentWindow.document.getElementById('output').textContent;
        iframe_output = iframe_output.replace(new RegExp('<' + iframe.src, 'g'), '<{BASE}');
        output += '*** '+ids[i]+'\n'+iframe_output+'\n';
    }
    document.getElementById('output').value = output;
}