(function () {
    var log = function () {};

    function OrientationTester(container, orientation) {
        this.container = container;
        this.setOrientation(orientation);
    }
    extend(OrientationTester.prototype, {
        setOrientation: function (orientation) {
            this.orientation = orientation;
        },
        measure: function (results) {
            this.results = results;
            this._measureNode(this.container);
        },
        _measureNode: function (node, block) {
            switch (node.nodeType) {
            case Node.ELEMENT_NODE:
                var blockOverride = node.dataset.block;
                if (blockOverride)
                    block = blockOverride;
                var nodes = node.childNodes;
                for (var i = 0; i < nodes.length; i++)
                    this._measureNode(nodes[i], block);
                return;
            case Node.TEXT_NODE:
                break;
            default:
                return;
            }

            if (this.orientation == "R") {
                var advanceExpected = 8;
                var advanceFailed = 4;
            } else {
                advanceExpected = 4;
                advanceFailed = 8;
            }

            var range = document.createRange();
            var text = node.textContent;
            for (var ich = 0; ich < text.length; ich++) {
                var code = text.charCodeAt(ich);
                if (code == 10 || code == 13)
                    continue;
                range.setStart(node, ich);
                if (code >= 0xD800 && code <= 0xDBFF) {
                    var next = text.charCodeAt(ich+1);
                    if (next >= 0xDC00 && next <= 0xDFFF) {
                        ich++;
                        code = ((code & 0x3FF) << 10) + (next & 0x3FF) + 0x10000;
                    }
                }
                range.setEnd(node, ich + 1);
                rect = range.getBoundingClientRect();
                if (rect.width == 16) {
                    if (rect.height == advanceExpected) {
                        this.results.passCount++;
                        continue;
                    }
                    //log("U+" + stringFromUnicode(code) + " " + rect.width + "x" + rect.height);
                    if (rect.height == advanceFailed) {
                        this.results.failed(this, code, block);
                        continue;
                    }
                }
                this.results.inconclusive(this, code, block, rect);
            }
        }});

    function Results(name) {
        this.details = document.createElement("details");
        this.summary = appendChildElement(this.details, "summary");
        this.summary.textContent = name;
        var typeList = appendChildElement(this.details, "ul");
        this.failList = appendChildElement(appendChildElement(typeList, "li", "Failures"), "ol");
        this.inconclusiveList = appendChildElement(appendChildElement(typeList, "li", "Inconclusives"), "ol");
        this.passCount = 0;
        this.failCount = 0;
        this.inconclusiveCount = 0;
    }
    extend(Results.prototype, {
        failed: function (test, code, block) {
            this.failCount++;
            this.append(this.failList, test, code, block);
        },
        inconclusive: function (test, code, block, rect) {
            this.inconclusiveCount++;
            this.append(this.inconclusiveList, test, code, block, " but inconclusive (rendered as " + rect.width + "x" + rect.height + ")");
        },
        append: function (list, test, code, block, message) {
            var text = stringFromUnicode(code) + " should be " + test.orientation;
            if (block)
                text = block + ": " + text;
            if (message)
                text += message;
            appendChildElement(list, "li", text);
        },
        done: function (test) {
            this.summary.textContent += " (" + this.passCount + " passes, " +
                this.failCount + " fails, " +
                this.inconclusiveCount + " inconclusives)";
            details.appendChild(this.details);
            assert_equals(this.failCount, 0, "Fail count");
            assert_greater_than(this.passCount, 0, "Pass count");
            test.done();
        }});

    function Runner() {
        var nodes = document.querySelectorAll("div[data-vo]");
        this.testers = [];
        for (var i = 0; i < nodes.length; i++) {
            var node = nodes[i];
            var vo = node.dataset.vo;
            var tester = new OrientationTester(node, vo);
            tester.test = async_test("Default orientation for vo=" + vo);
            this.testers.push(tester);
        }
        this.testU = async_test("Orientation=Upright");
        this.testR = async_test("Orientation=Rotated");
    }
    extend(Runner.prototype, {
        run: function () {
            log("Started");
            var start = new Date;

            for (var i = 0; i < this.testers.length; i++) {
                var tester = this.testers[i];
                var test = tester.test;
                test.step(function () {
                    var results = new Results(test.name);
                    tester.measure(results);
                    results.done(test);
                });
            }
            this.runOrientation(this.testU, "U");
            this.runOrientation(this.testR, "R");

            log("Elapsed " + (new Date() - start));
            done();
        },
        runOrientation: function (test, orientation) {
            container.classList.add(orientation);
            var results = new Results(test.name);
            var me = this;
            test.step(function () {
                for (var i = 0; i < me.testers.length; i++) {
                    var tester = me.testers[i];
                    tester.setOrientation(orientation);
                    tester.measure(results);
                }
                results.done(test);
            })
            container.classList.remove(orientation);
        }});

    setup({explicit_done:true, explicit_timeout:true});
    var runner = new Runner();
    window.onload = function () {
        if (window.location.search == "?wait") {
            log("Sleeping 5 secs for debug purpose");
            return setTimeout(run, 5000);
        }
        run();
    }

    function run() {
        onFontReady("16px orientation", function () { runner.run(); });
    }

    function onFontReady(font, func) {
        log("Waiting test fonts to load");
        if (document.fonts) {
            if ('load' in document.fonts)
                return document.fonts.load(font).then(func);
            if ('ready' in document.fonts)
                return document.fonts.ready.then(func);
        }
        document.offsetTop; // last resort to load @font-face
        func();
    }

    function arrayFromRangesByValue(dict) {
        var array = [];
        for (var value in dict) {
            var ranges = dict[value];
            for (var i = 0; i < ranges.length; i += 2) {
                var to = ranges[i+1];
                for (var code = ranges[i]; code <= to; code++)
                    array[code] = value;
            }
        }
        return array;
    };

    function stringFromUnicode(code) {
        var hex = code.toString(16).toUpperCase();
        if (hex.length < 4) {
            hex = "0000" + hex;
            hex = hex.substr(hex.length - 4);
        }
        return hex + ' "' + String.fromCharCode(code) + '"';
    }

    function appendChildElement(parent, tag, text) {
        var node = document.createElement(tag);
        if (text)
            node.textContent = text;
        parent.appendChild(node);
        return node;
    }

    function extend(target, dict) {
        for (var key in dict)
            target[key] = dict[key];
    }
})();
