/*
*/

var Tests = {
    
    STORE_URI: 'store',

    _httpWithNext: function(func) {
        var c = new HTTPClient({
            onComplete: function(c) { 
                try { 
                    log.debug3('HEADERS: '+c.req.getAllResponseHeaders());
                    log.debug3(c.req.responseText);
                    func(c); 
                } 
                catch(e) { }
                Tests.next();
            }
        });
        c.setDefeatCache(true);
        return c
    },
    
    /*********************************************************************
        Simple test of an assertion.
    *********************************************************************/
    testAssert: function() {
        Tests.assert(true, "This should be true.");
        return Tests.next();
    },
 
    /*********************************************************************
        Check the DB index.
    *********************************************************************/
    testGetIndex: function() {
        var c = Tests._httpWithNext(function(c) { Tests._testGetIndex2(c) })
        c.GET(Tests.STORE_URI);
    },
    _testGetIndex2: function(c) {
        var doc = c.req.responseXML;
        Tests.assert(doc != null, 
                     'The XML response should be defined');
        
        var uls = doc.getElementsByTagName('ul');
        Tests.assert(uls.length > 0,
                     'There should be 1 or more list sets '+
                     '(unless the DB is empty).');

        var lis = doc.getElementsByTagName('li');
        if (lis.length == 0)
            log.warn("\tThere's nothing listed in the index.");
        else
            log.debug("\tDocuments found: " + lis.length);
    },

    /*********************************************************************
        New document creation.
    *********************************************************************/
    testPutNew: function() {
        var xml = '<test><foo>bar</foo></test>';
        var c = Tests._httpWithNext(function(c) { Tests._testPutNew2(c) })
        c.PUT(Tests.STORE_URI, null, xml);
    },
    _testPutNew2: function(c) {
        Tests.assert(c.req.status == 201,
                     'New document should have been created. ('+
                     c.req.status+' '+c.req.statusText+')');
    
        Tests._loc = null;    
        var loc = c.req.getResponseHeader('Location');
        log.debug("\tNew document location:");
        log.debug("\t  "+loc);
        Tests.assert(loc != '',
                     'The created document should have a new Location URI.');
        Tests._loc = loc;
    },
    
    /*********************************************************************
        New document verification.
    *********************************************************************/
    testGetNew: function() {
        Tests.assert(Tests._loc != null,
                     'A previous test should have come up with a location');
        var c = Tests._httpWithNext(function(c) { Tests._testGetNew2(c) });
        c.GET(Tests._loc);
    },
    _testGetNew2: function(c) {
        Tests.assert(c.req.status == 200,
                     'Expected HTTP status 200, got ' + c.req.status);

        var doc = c.req.responseXML;
        Tests.assert(doc != null, 
                     'The XML response should be defined');
        
        var tests = doc.getElementsByTagName('test');
        Tests.assert(tests.length == 1,
                     'Expected to find 1 node named "test", found '+
                     tests.length);

        // TODO: Find out why <test/> isn't the root node on MSIE6
    },
    
    /*********************************************************************
        Existing document update.
    *********************************************************************/
    testUpdateWithinDoc: function() {
        Tests.assert(Tests._loc != null,
                     'A previous test should have come up with a location');
        var c = Tests._httpWithNext(function(c) 
            { Tests._testUpdateWithinDoc2(c) })
        c.PUT(Tests._loc+'/foo', null, '<foo>baz</foo>');
    },
    _testUpdateWithinDoc2: function(c) {
        Tests.assert(c.req.status == 200,
                     'Expected HTTP status 200, got ' + c.req.status);

        var doc = c.req.responseXML;
        Tests.assert(doc != null, 
                     'The XML response should be defined');
        
        var root = doc.firstChild;
        Tests.assert(root.tagName == 'foo',
                     'The document root should be "foo"');

        Tests.assert(root.firstChild.nodeValue == 'baz',
                     'The text contained by the root node should '+
                     'now be "baz"');
    },

    /*********************************************************************
        Delete within document.
    *********************************************************************/
    testDeleteWithinDoc: function() {
        Tests.assert(Tests._loc != null,
                     'A previous test should have come up with a location');
        var c = Tests._httpWithNext(function(c) 
            { Tests._testDeleteWithinDoc2(c) })
        c.DELETE(Tests._loc+'/foo');
    },
    _testDeleteWithinDoc2: function(c) {
        Tests.assert(c.req.status == 410,
                     'HTTP status 410 != ' + c.req.status);
    },

    testVerifyDeleteWithinDoc: function() {
        Tests.assert(Tests._loc != null,
                     'A previous test should have come up with a location');
        var c = Tests._httpWithNext(function(c) 
            { Tests._testVerifyDeleteWithinDoc2(c) })
        c.GET(Tests._loc+'/foo');
    },
    _testVerifyDeleteWithinDoc2: function(c) {
        Tests.assert(c.req.status == 404,
                     'HTTP status 404 != ' + c.req.status);
    },

    /*********************************************************************
        Create within document.
        TODO: This isn't implemented in the server yet, so fails.
    *********************************************************************/
    testCreateWithinDoc: function() {
        Tests.assert(Tests._loc != null,
                     'A previous test should have come up with a location');
        var c = Tests._httpWithNext(function(c) 
            { Tests._testCreateWithinDoc2(c) })
        c.PUT(Tests._loc+'/quux', null, '<quux>xyzzy</quux>');
    },
    _testCreateWithinDoc2: function(c) {
        Tests.assert(c.req.status == 201 && c.req.statusText == 'Created',
                     'New element should have been created.');
    },
    testVerifyCreateWithinDoc: function() {
        Tests.assert(Tests._loc != null,
                     'A previous test should have come up with a location');
        var c = Tests._httpWithNext(function(c) 
            { Tests._testVerifyCreateWithinDoc2(c) })
        c.GET(Tests._loc+'/quux');
    },
    _testVerifyCreateWithinDoc2: function(c) {
        Tests.assert(c.req.status == 200,
                     'New element should have been found');
    },
    
    /*********************************************************************
        Delete whole document.
    *********************************************************************/
    testDeleteWholeDoc: function() {
        Tests.assert(Tests._loc != null,
                     'A previous test should have come up with a location');
        var c = Tests._httpWithNext(function(c) 
            { Tests._testDeleteWholeDoc2(c) })
        c.DELETE(Tests._loc);
    },
    _testDeleteWholeDoc2: function(c) {
        Tests.assert(c.req.status == 410,
                     'Expected HTTP status 410, got ' + c.req.status);
    },

    testVerifyDeleteWholeDoc: function() {
        Tests.assert(Tests._loc != null,
                     'A previous test should have come up with a location');
        var c = Tests._httpWithNext(function(c) 
            { Tests._testVerifyDeleteWholeDoc2(c) })
        c.GET(Tests._loc);
    },
    _testVerifyDeleteWholeDoc2: function(c) {
        Tests.assert(c.req.status == 404,
                     'Expected HTTP status 404, got ' + c.req.status);
    },

    /*********************************************************************
        Create named document
    *********************************************************************/
    testCreateNamed: function() {
        // Generate a semi-random URI for the new doc.
        Tests._named_uri = 
            Tests.STORE_URI + '/TestDoc' + Math.round(Math.random()*100000)
        log.debug("\tNAMED URI: " + Tests._named_uri);
        
        var xml = ' \
            <test xmlns="http://www.decafbad.com/2005/07/test"> \
                <foo>bar</foo> \
                <baz> \
                    <quux id="123">123 xyzzy</quux> \
                    <quux id="456">456 xyzzy</quux> \
                    <quux id="789">789 xyzzy</quux> \
                    <quux id="abc">abc xyzzy</quux> \
                    <quux id="def">def xyzzy</quux> \
                </baz> \
            </test> \
        ';
        var c = Tests._httpWithNext(function(c) 
            { Tests._testCreateNamed2(c) })
        c.PUT(Tests._named_uri, null, xml);
    },
    _testCreateNamed2: function(c) {
        Tests.assert(c.req.status == 201,
                     'New document should have been created. ('+
                     c.req.status+' '+c.req.statusText+')');
    
        var loc = c.req.getResponseHeader('Location');
        log.debug("\tNew document location:");
        log.debug("\t  "+loc);
        Tests.assert(loc != '',
                     'The created document should have a new Location URI.');
        Tests._loc = loc;
    },

    /*********************************************************************
        Create named document
    *********************************************************************/
    testGetId: function() {
        Tests.assert(Tests._named_uri != null,
                     'A previous test should have come up with a location');
        var c = Tests._httpWithNext(function(c) { Tests._testGetId2(c) })
        Tests._id_uri = Tests._named_uri+'/test:baz/test:quux[@id="789"]'
        log.debug("\tID URI: "+Tests._id_uri);
        c.GET(Tests._id_uri);
    },
    _testGetId2: function(c) {
        Tests.assert(c.req.status == 200,
                     'Expected HTTP status 200, got ' + c.req.status);

        var doc = c.req.responseXML;
        Tests.assert(doc != null, 
                     'The XML response should be defined');
        
        var root = doc.firstChild;
        Tests.assert(root.tagName == 'quux',
                     'The document root should be "quux"');
        Tests.assert(root.getAttribute('id') == '789',
                     'ID attribute should be 789');
        Tests.assert(root.firstChild.nodeValue == '789 xyzzy',
                     'Text data value should be 789 xyzzy');
    },
    
    /*********************************************************************
        Update ID within named document
    *********************************************************************/
    testUpdateId: function() {
        var c = Tests._httpWithNext(function(c) { Tests._testUpdateId2(c) })
        c.PUT(Tests._id_uri, null, '<quux id="789">new value</quux>');
    },
    _testUpdateId2: function(c) {
        Tests.assert(c.req.status == 200,
                     'HTTP status 200 != ' + c.req.status);
    },
    testVerifyId: function() {
        var c = Tests._httpWithNext(function(c) { Tests._testVerifyId2(c) })
        c.GET(Tests._id_uri);
    },
    _testVerifyId2: function(c) {
        Tests.assert(c.req.status == 200,
                     'HTTP status 200 != ' + c.req.status);

        var doc = c.req.responseXML;
        Tests.assert(doc != null, 
                     'The XML response should be defined');
        
        var root = doc.firstChild;
        Tests.assert(root.tagName == 'quux',
                     'The document root should be "quux"');
        Tests.assert(root.getAttribute('id') == '789',
                     'ID attribute should be 789');
        Tests.assert(root.firstChild.nodeValue == 'new value',
                     'Text data value should be "new value"');
    },
    
    /*********************************************************************
        Delete named document.
    *********************************************************************/
    testDeleteNamedDoc: function() {
        var c = Tests._httpWithNext(function(c) 
            { Tests._testDeleteNamedDoc2(c) })
        c.DELETE(Tests._id_uri);
    },
    _testDeleteNamedDoc2: function(c) {
        Tests.assert(c.req.status == 410,
                     'Expected HTTP status 410, got ' + c.req.status);
    },

    testVerifyDeleteNamedDoc: function() {
        var c = Tests._httpWithNext(function(c) 
            { Tests._testVerifyDeleteNamedDoc2(c) })
        c.GET(Tests._id_uri);
    },
    _testVerifyDeleteNamedDoc2: function(c) {
        Tests.assert(c.req.status == 404,
                     'Expected HTTP status 404, got ' + c.req.status);
    },
    
    /*********************************************************************
        Primitive testing framework below here, avert your eyes.
    **********************************************************************/
    
    // TODO: Implement some timeout catches to fail current test?
    
    AssertionFailedError: Error("Assertion failed."),

    init: function() {

        var all = [];
        for (var k in Tests)
            if (k.indexOf('test') == 0)
                all[all.length] = [k, Tests[k]];
        
        Tests.all          = all;
        Tests.failed       = [];
        Tests.idx          = 0
        Tests.curr         = null;
        Tests.curr_failed  = false;
        Tests.next();
        
        log.info("Tests starting up, "+all.length+" planned.");
    },

    next: function() { 
        // HACK: A little bit of indirection so as not to blow the stack.
        window.setTimeout(function() { Tests.runNextTest() }, 20)
    },

    runNextTest: function() {
        // Record a failed test before going onto the next.
        if (Tests.curr && Tests.curr_failed) {
            Tests.failed[Tests.failed.length] = Tests.curr[0];
            Tests.curr_failed = false;
        }
        
        // Attempt to get the next test.
        var t = Tests.all[Tests.idx++];
        if (t) {
            Tests.curr = t;
            log.info("Running test #"+Tests.idx+": "+t[0]);
            try {
                return t[1]();
            } catch(e) {
                if (e != Tests.AssertionFailedError) {
                    log.error("\tFAIL: Exception running test: "+e.message);
                    Tests.curr_failed = true;
                }
                Tests.next();
            }
        } else {
            log.info("Tests completed.");
            if (Tests.failed.length == 0) {
                log.info("All tests OK.");
            } else {
                log.info("Some tests FAILED ("+
                    Tests.failed.length+"/"+Tests.all.length+"):");
                for (var i=0; i<Tests.failed.length; i++)
                    log.info("\t- "+Tests.failed[i]);
            }
        }
    },

    assert: function(b, msg) {
        if (!b) {
            Tests.curr_failed = true;
            log.error("\tFAIL: "+msg);
            throw(Tests.AssertionFailedError);
        }
    }

}

