/*
 * Copyright (C) 2013, 2014 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

WI.DefaultDashboard = class DefaultDashboard extends WI.Object
{
    constructor()
    {
        super();

        this._waitingForFirstMainResourceToStartTrackingSize = true;

        // Necessary event required to track page load time and resource sizes.
        WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this);
        WI.timelineManager.addEventListener(WI.TimelineManager.Event.CapturingStateChanged, this._handleTimelineCapturingStateChanged, this);

        // Necessary events required to track load of resources.
        WI.Frame.addEventListener(WI.Frame.Event.ResourceWasAdded, this._resourceWasAdded, this);
        WI.Target.addEventListener(WI.Target.Event.ResourceAdded, this._resourceWasAdded, this);
        WI.networkManager.addEventListener(WI.NetworkManager.Event.FrameWasAdded, this._frameWasAdded, this);

        // Necessary events required to track console messages.
        WI.consoleManager.addEventListener(WI.ConsoleManager.Event.Cleared, this._consoleWasCleared, this);
        WI.consoleManager.addEventListener(WI.ConsoleManager.Event.MessageAdded, this._consoleMessageAdded, this);
        WI.consoleManager.addEventListener(WI.ConsoleManager.Event.PreviousMessageRepeatCountUpdated, this._consoleMessageWasRepeated, this);

        // FIXME: This is working around the order of events. Normal page navigation
        // triggers a MainResource change and then a MainFrame change. Page Transition
        // triggers a MainFrame change then a MainResource change.
        this._transitioningPageTarget = false;

        WI.notifications.addEventListener(WI.Notification.TransitionPageTarget, this._transitionPageTarget, this);

        this._resourcesCount = 0;
        this._resourcesSize = 0;
        this._time = 0;
        this._logs = 0;
        this._errors = 0;
        this._issues = 0;
    }

    // Public

    get resourcesCount()
    {
        return this._resourcesCount;
    }

    set resourcesCount(value)
    {
        this._resourcesCount = value;
        this._dataDidChange();
    }

    get resourcesSize()
    {
        return this._resourcesSize;
    }

    set resourcesSize(value)
    {
        this._resourcesSize = value;
        this._dataDidChange();
    }

    get time()
    {
        return this._time;
    }

    set time(value)
    {
        this._time = value;
        this._dataDidChange();
    }

    get logs()
    {
        return this._logs;
    }

    set logs(value)
    {
        this._logs = value;
        this._dataDidChange();
    }

    get errors()
    {
        return this._errors;
    }

    set errors(value)
    {
        this._errors = value;
        this._dataDidChange();
    }

    get issues()
    {
        return this._issues;
    }

    set issues(value)
    {
        this._issues = value;
        this._dataDidChange();
    }

    // Private

    _dataDidChange()
    {
        this.dispatchEventToListeners(WI.DefaultDashboard.Event.DataDidChange);
    }

    _mainResourceDidChange(event)
    {
        console.assert(event.target instanceof WI.Frame);

        if (!event.target.isMainFrame())
            return;

        if (!this._transitioningPageTarget) {
            this._time = 0;
            this._resourcesCount = 1;
            this._resourcesSize = WI.networkManager.mainFrame.mainResource.size || 0;
        }

        // We should only track resource sizes on fresh loads.
        if (this._waitingForFirstMainResourceToStartTrackingSize) {
            this._waitingForFirstMainResourceToStartTrackingSize = false;
            WI.Resource.addEventListener(WI.Resource.Event.SizeDidChange, this._resourceSizeDidChange, this);
        }

        this._dataDidChange();

        if (!this._transitioningPageTarget)
            this._startUpdatingTime();

        if (this._transitioningPageTarget)
            this._transitioningPageTarget = false;
    }

    _handleTimelineCapturingStateChanged(event)
    {
        if (WI.timelineManager.isCapturing())
            return;

        // If recording stops, we should stop the timer if it hasn't stopped already.
        this._stopUpdatingTime();
    }

    _resourceWasAdded(event)
    {
        ++this.resourcesCount;
    }

    _frameWasAdded(event)
    {
        ++this.resourcesCount;
    }

    _resourceSizeDidChange(event)
    {
        if (event.target.urlComponents.scheme === "data")
            return;

        let delta = event.target.size - event.data.previousSize;
        console.assert(!isNaN(delta), "Resource size change should never be NaN.");
        this.resourcesSize += delta;
    }

    _startUpdatingTime()
    {
        this._stopUpdatingTime();

        this.time = 0;

        this._timelineBaseTime = Date.now();
        this._timeIntervalDelay = 50;
        this._timeIntervalIdentifier = setInterval(this._updateTime.bind(this), this._timeIntervalDelay);
    }

    _stopUpdatingTime()
    {
        if (!this._timeIntervalIdentifier)
            return;

        clearInterval(this._timeIntervalIdentifier);
        this._timeIntervalIdentifier = undefined;
    }

    _updateTime()
    {
        var duration = Date.now() - this._timelineBaseTime;

        var timeIntervalDelay = this._timeIntervalDelay;
        if (duration >= 1000) // 1 second
            timeIntervalDelay = 100;
        else if (duration >= 60000) // 60 seconds
            timeIntervalDelay = 1000;
        else if (duration >= 3600000) // 1 minute
            timeIntervalDelay = 10000;

        if (timeIntervalDelay !== this._timeIntervalDelay) {
            this._timeIntervalDelay = timeIntervalDelay;

            clearInterval(this._timeIntervalIdentifier);
            this._timeIntervalIdentifier = setInterval(this._updateTime.bind(this), this._timeIntervalDelay);
        }

        var mainFrame = WI.networkManager.mainFrame;
        var mainFrameStartTime = mainFrame.mainResource.firstTimestamp;
        var mainFrameLoadEventTime = mainFrame.loadEventTimestamp;

        if (isNaN(mainFrameStartTime) || isNaN(mainFrameLoadEventTime)) {
            this.time = duration / 1000;
            return;
        }

        this.time = mainFrameLoadEventTime - mainFrameStartTime;

        this._stopUpdatingTime();
    }

    _consoleMessageAdded(event)
    {
        var message = event.data.message;
        this._lastConsoleMessageType = message.level;
        this._incrementConsoleMessageType(message.level, message.repeatCount);
    }

    _consoleMessageWasRepeated(event)
    {
        this._incrementConsoleMessageType(this._lastConsoleMessageType, 1);
    }

    _incrementConsoleMessageType(type, increment)
    {
        switch (type) {
        case WI.ConsoleMessage.MessageLevel.Log:
        case WI.ConsoleMessage.MessageLevel.Info:
        case WI.ConsoleMessage.MessageLevel.Debug:
            this.logs += increment;
            break;
        case WI.ConsoleMessage.MessageLevel.Warning:
            this.issues += increment;
            break;
        case WI.ConsoleMessage.MessageLevel.Error:
            this.errors += increment;
            break;
        }
    }

    _consoleWasCleared(event)
    {
        this._logs = 0;
        this._issues = 0;
        this._errors = 0;
        this._dataDidChange();
    }

    _transitionPageTarget()
    {
        this._transitioningPageTarget = true;

        this._time = 0;
        this._resourcesCount = 0;
        this._resourcesSize = 0;

        this._dataDidChange();
    }
};

WI.DefaultDashboard.Event = {
    DataDidChange: "default-dashboard-data-did-change"
};
