/* See license.txt for terms of usage */

/**
 * Builder implementation
 * @param {Object} panelNode
 * @param {Object} data
 */
var NetBuilder =
{
    table: null,
    summaryRow: null,
    phases: [],

    parseInputData: function()
    {
        try
        {
            var sourceEditor = getElementByClass(document.documentElement, "sourceEditor");
            return eval(sourceEditor.value);
        }
        catch (err)
        {
            this.errors = [{
                "message": "Failed to parse JSON",
                "property": "JSON evaluation"
            },
            {
                "message": err.name,
                "property": err.message
            }];
        }

        return null;
    },

    getInputData: function()
    {
        var inputData = this.parseInputData();
        this.setInputData(inputData);
        return this.inputData;
    },

    setInputData: function(inputData)
    {
        this.inputData = inputData;
        return this.inputData;
    },

    addInputData: function(inputData)
    {
        if (!inputData)
            return this.inputData;

        if (this.inputData)
        {
            for (var i=0; i<inputData.log.pages.length; i++)
                this.importPage(inputData.log.pages[i], inputData.log.entries);
        }
        else
        {
            this.inputData = inputData;
        }

        return this.setInputData(this.inputData);
    },

    importPage: function(page, entries)
    {
        var pageId = this.getUniquePageID(page.id);
        var prevPageId = page.id;
        page.id = pageId;

        this.inputData.log.pages.push(page);
        for (var i=0; i<entries.length; i++)
        {
            var entry = entries[i];
            if (entry.pageref == prevPageId)
            {
                entry.pageref = pageId;
                this.inputData.log.entries.push(entry);
            }
        }
    },

    removePage: function(page)
    {
        remove(this.inputData.log.pages, page);
        var entries = this.inputData.log.entries;
        for (var i=0; i<entries.length; i++)
        {
            var entry = entries[i];
            if (entry.pageref == page.id)
                entries.splice(i--, 1);
        }
        return this.inputData;
    },

    getPageEntries: function(page)
    {
        var entries = [];
        for (var i=0; i<this.inputData.log.entries.length; i++)
        {
            var entry = this.inputData.log.entries[i];
            if (entry.pageref == page.id)
                entries.push(entry);
        }
        return entries;
    },

    getUniquePageID: function(defaultId)
    {
        var pages = this.inputData.log.pages;
        var hashTable = {};
        for (var i=0; i<pages.length; i++)
            hashTable[pages[i].id] = true;

        if (!hashTable[defaultId])
            return defaultId;

        var counter = 1;
        while (true)
        {
            var pageId = defaultId + counter;
            if (!hashTable[pageId])
                return pageId;
            counter++;
        }
    },

    buildPageList: function(parentNode, inputData)
    {
        if (!inputData)
            return null;

        var table = PageList.tableTag.replace({groups: inputData.log.pages}, parentNode, PageList);
        var rows = table.firstChild.childNodes;
        for (var i=0; i<rows.length; i++)
            rows[i].repObject = inputData.log.pages[i];

        //if (table.firstChild.firstChild)
        //    PageList.toggleRow(table.firstChild.firstChild);
    },

    buildErrorList: function(parentNode, errors)
    {
        SchemaRep.errorTable.replace({errors:errors}, parentNode, SchemaRep);
    },

    buildPageContent: function(parentNode, page)
    {
        //var container = document.createElement("div");
        this.table = NetPanel.tableTag.replace({}, parentNode, NetPanel);
        //parentNode.appendChild(container);
        this.summaryRow =  NetPanel.summaryTag.insertRows({}, this.table.firstChild)[0];

        var requests = this.getPageEntries(page);

        var tbody = this.table.firstChild;
        var lastRow = tbody.lastChild.previousSibling; //xxxHonza
        var row = this.firstRow = NetPanel.fileTag.insertRows({files: requests}, lastRow)[0];

        this.phases = [];
        phaseMap = [];

        var phaseInterval = 1000;
        var phase = null;

        for (var i=0; i<requests.length; i++)
        {
            var file = requests[i];
            row.repObject = file;
            row = row.nextSibling;

            if (!phase || (file.startedDateTime - phase.getLastStartTime()) >= phaseInterval &&
                !file.timings.load)
            {
                phase = this.startPhase(file);
            }
            else
                phase.addFile(file);

            if (phase.startTime == undefined || phase.startTime > file.startedDateTime)
                phase.startTime = file.startedDateTime;

            // file.time represents total elapsed time of the request.
            if (phase.endTime == undefined || phase.endTime < file.startedDateTime + file.time)
                phase.endTime = file.startedDateTime + file.time;

            if (file.timings.load && phase.endTime < file.startedDateTime + file.timings.load)
                phase.endTime = file.startedDateTime + file.timings.load;
        }

        this.updateTimeline();
        this.updateSummaries();
    },

    startPhase: function(file)
    {
        var phase = new NetPhase(file);
        this.phases.push(phase);
        return phase;
    },

    updateTimeline: function()
    {
        var tbody = this.table.firstChild;

        var phase;

        // Iterate over all existing entries. Some rows aren't associated with a file 
        // (e.g. header, sumarry) so, skip them.
        for (var row = this.firstRow; row; row = row.nextSibling)
        {
            var file = row.repObject;
            if (!file)
                continue;

            phase = this.calculateFileTimes(file, phase);
            delete file.phase;

            // Get bar nodes
            var resolvingBar = row.childNodes[4].firstChild.childNodes[1];
            var connectingBar = resolvingBar.nextSibling;
            var waitingBar = connectingBar.nextSibling;
            var respondedBar = waitingBar.nextSibling;
            var contentLoadBar = respondedBar.nextSibling;
            var windowLoadBar = contentLoadBar.nextSibling;
            var timeBar = windowLoadBar.nextSibling;

            // All bars starts at the beginning
            resolvingBar.style.left = connectingBar.style.left = waitingBar.style.left =
                respondedBar.style.left = timeBar.style.left = this.barOffset + "%";

            // Sets width of all bars (using style). The width is computed according to measured timing.
            resolvingBar.style.width = this.barResolvingWidth ? this.barResolvingWidth + "%" : "1px";
            connectingBar.style.width = this.barConnectingWidth ? this.barConnectingWidth + "%" : "1px";
            waitingBar.style.width = this.barWaitingWidth + "%";
            respondedBar.style.width = this.barRespondedWidth + "%";
            timeBar.style.width = this.barWidth + "%";

            if (this.contentLoadBarOffset) {
                contentLoadBar.style.left = this.contentLoadBarOffset + "%";
                contentLoadBar.style.display = "block";
                this.contentLoadBarOffset = null;
            }

            if (this.windowLoadBarOffset) {
                windowLoadBar.style.left = this.windowLoadBarOffset + "%";
                windowLoadBar.style.display = "block";
                this.windowLoadBarOffset = null;
            }
        }
    },

    calculateFileTimes: function(file, phase)
    {
        if (phase != file.phase)
        {
            phase = file.phase;
            this.phaseStartTime = phase.startTime;
            this.phaseEndTime = phase.endTime;

            // End of the first phase has to respect even the window "onload" event time, which
            // can occur after the last received file. This sets the extent of the timeline so,
            // the windowLoadBar is visible.
            //if (phase.load && this.phaseEndTime < phase.windowLoadTime)
            //    this.phaseEndTime = phase.load;

            this.phaseElapsed = this.phaseEndTime - phase.startTime;
        }

        var resolving = file.timings.dns;
        var connecting = resolving + file.timings.connect;
        var waiting = connecting + file.timings.blocked;
        var responded = waiting + file.timings.wait;

        //var resolving = (file.timings.resolvingTime - file.startedDateTime);
        //var connecting = (file.timings.connectingTime - file.startedDateTime);
        //var waiting = (file.timings.waitingForTime - file.startedDateTime);
        //var responded = (file.timings.respondedTime - file.startedDateTime);

        var elapsed = file.time;
        this.barWidth = Math.floor((elapsed/this.phaseElapsed) * 100);
        this.barOffset = Math.floor(((file.startedDateTime-this.phaseStartTime)/this.phaseElapsed) * 100);
        this.barResolvingWidth = Math.floor((resolving/this.phaseElapsed) * 100);
        this.barConnectingWidth = Math.floor((connecting/this.phaseElapsed) * 100);
        this.barWaitingWidth = Math.floor((waiting/this.phaseElapsed) * 100);
        this.barRespondedWidth = Math.floor((responded/this.phaseElapsed) * 100);

        // Total request time doesn't include the time spent in queue.
        //file.elapsed = elapsed - (file.waitingForTime - file.connectingTime);

        // Compute also offset for the contentLoadBar and windowLoadBar, which are
        // displayed for the first phase.
        if (file.timings.DOMContentLoad)
            this.contentLoadBarOffset = Math.floor(
                ((file.startedDateTime+file.timings.DOMContentLoad-phase.startTime)/this.phaseElapsed) * 100);

        if (file.timings.load)
            this.windowLoadBarOffset = Math.floor(
                ((file.startedDateTime+file.timings.load-phase.startTime)/this.phaseElapsed) * 100);

        return phase;
    },

    updateSummaries: function()
    {
        var phases = this.phases;

        var p
        var fileCount = 0, totalSize = 0, cachedSize = 0, totalTime = 0;
        for (var i = 0; i < phases.length; ++i)
        {
            var phase = phases[i];
            phase.invalidPhase = false;

            var summary = this.summarizePhase(phase);
            fileCount += summary.fileCount;
            totalSize += summary.totalSize;
            cachedSize += summary.cachedSize;
            totalTime += summary.totalTime
        }

        var row = this.summaryRow;
        if (!row)
            return;

        var countLabel = row.firstChild.firstChild;
        countLabel.firstChild.nodeValue = fileCount == 1
            ? $STR("Request")
            : $STRF("RequestCount", [fileCount]);

        var sizeLabel = row.childNodes[1].firstChild;
        sizeLabel.setAttribute("totalSize", totalSize);
        sizeLabel.firstChild.nodeValue = formatSize(totalSize);

        var cacheSizeLabel = row.lastChild.firstChild.firstChild;
        cacheSizeLabel.setAttribute("collapsed", cachedSize == 0);
        cacheSizeLabel.childNodes[1].firstChild.nodeValue = formatSize(cachedSize);

        var timeLabel = row.lastChild.firstChild.lastChild.firstChild;
        timeLabel.innerHTML = formatTime(totalTime);
    },

    summarizePhase: function(phase)
    {
        var cachedSize = 0, totalSize = 0;

        var category = "all";
        if (category == "all")
            category = null;

        var fileCount = 0;
        var minTime = 0, maxTime = 0;

        for (var i=0; i<phase.files.length; i++)
        {
            var file = phase.files[i];

            if (!category || file.category == category)
            {
                ++fileCount;

                if (file.response.content.text.length)
                {
                    totalSize += file.response.content.text.length;
                    if (file.fromCache)
                        cachedSize += file.response.content.text.length;
                }

                if (!minTime || file.startedDateTime < minTime)
                    minTime = file.startedDateTime;
                if (phase.endTime > maxTime)
                    maxTime = phase.endTime;
            }
        }

        var totalTime = maxTime - minTime;
        return {cachedSize: cachedSize, totalSize: totalSize, totalTime: totalTime,
                fileCount: fileCount}
    }
}
