// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

(function(window) {

// DevToolsAPI ----------------------------------------------------------------

/**
 * @constructor
 */
function DevToolsAPIImpl()
{
    /**
     * @type {number}
     */
    this._lastCallId = 0;

    /**
     * @type {!Object.<number, function(?Object)>}
     */
    this._callbacks = {};
}

DevToolsAPIImpl.prototype = {
    /**
     * @param {number} id
     * @param {?Object} arg
     */
    embedderMessageAck: function(id, arg)
    {
        var callback = this._callbacks[id];
        delete this._callbacks[id];
        if (callback)
            callback(arg);
    },

    /**
     * @param {string} method
     * @param {!Array.<*>} args
     * @param {?function(?Object)} callback
     */
    sendMessageToEmbedder: function(method, args, callback)
    {
        var callId = ++this._lastCallId;
        if (callback)
            this._callbacks[callId] = callback;
        var message = { "id": callId, "method": method };
        if (args.length)
            message.params = args;
        DevToolsHost.sendMessageToEmbedder(JSON.stringify(message));
    },

    /**
     * @param {string} method
     * @param {!Array.<*>} args
     */
    _dispatchOnInspectorFrontendAPI: function(method, args)
    {
        var api = window["InspectorFrontendAPI"];
        api[method].apply(api, args);
    },

    // API methods below this line --------------------------------------------

    /**
     * @param {!Array.<!ExtensionDescriptor>} extensions
     */
    addExtensions: function(extensions)
    {
        // Support for legacy front-ends (<M41).
        if (window["WebInspector"].addExtensions)
            window["WebInspector"].addExtensions(extensions);
        else
            this._dispatchOnInspectorFrontendAPI("addExtensions", [extensions]);
    },

    /**
     * @param {string} url
     */
    appendedToURL: function(url)
    {
        this._dispatchOnInspectorFrontendAPI("appendedToURL", [url]);
    },

    /**
     * @param {string} url
     */
    canceledSaveURL: function(url)
    {
        this._dispatchOnInspectorFrontendAPI("canceledSaveURL", [url]);
    },

    contextMenuCleared: function()
    {
        this._dispatchOnInspectorFrontendAPI("contextMenuCleared", []);
    },

    /**
     * @param {string} id
     */
    contextMenuItemSelected: function(id)
    {
        this._dispatchOnInspectorFrontendAPI("contextMenuItemSelected", [id]);
    },

    /**
     * @param {number} count
     */
    deviceCountUpdated: function(count)
    {
        this._dispatchOnInspectorFrontendAPI("deviceCountUpdated", [count]);
    },

    /**
     * @param {boolean} discoverUsbDevices
     * @param {boolean} portForwardingEnabled
     * @param {!Adb.PortForwardingConfig} portForwardingConfig
     */
    devicesDiscoveryConfigChanged: function(discoverUsbDevices, portForwardingEnabled, portForwardingConfig)
    {
        this._dispatchOnInspectorFrontendAPI("devicesDiscoveryConfigChanged", [discoverUsbDevices, portForwardingEnabled, portForwardingConfig]);
    },

    /**
     * @param {!Adb.PortForwardingStatus} status
     */
    devicesPortForwardingStatusChanged: function(status)
    {
        this._dispatchOnInspectorFrontendAPI("devicesPortForwardingStatusChanged", [status]);
    },

    /**
     * @param {!Array.<!Adb.Device>} devices
     */
    devicesUpdated: function(devices)
    {
        this._dispatchOnInspectorFrontendAPI("devicesUpdated", [devices]);
    },

    /**
     * @param {string} message
     */
    dispatchMessage: function(message)
    {
        this._dispatchOnInspectorFrontendAPI("dispatchMessage", [message]);
    },

    /**
     * @param {string} messageChunk
     * @param {number} messageSize
     */
    dispatchMessageChunk: function(messageChunk, messageSize)
    {
        this._dispatchOnInspectorFrontendAPI("dispatchMessageChunk", [messageChunk, messageSize]);
    },

    enterInspectElementMode: function()
    {
        this._dispatchOnInspectorFrontendAPI("enterInspectElementMode", []);
    },

    /**
     * @param {number} callId
     * @param {string} script
     */
    evaluateForTestInFrontend: function(callId, script)
    {
        this._dispatchOnInspectorFrontendAPI("evaluateForTestInFrontend", [callId, script]);
    },

    /**
     * @param {!Array.<!{fileSystemName: string, rootURL: string, fileSystemPath: string}>} fileSystems
     */
    fileSystemsLoaded: function(fileSystems)
    {
        this._dispatchOnInspectorFrontendAPI("fileSystemsLoaded", [fileSystems]);
    },

    /**
     * @param {string} fileSystemPath
     */
    fileSystemRemoved: function(fileSystemPath)
    {
        this._dispatchOnInspectorFrontendAPI("fileSystemRemoved", [fileSystemPath]);
    },

    /**
     * @param {!{fileSystemName: string, rootURL: string, fileSystemPath: string}} fileSystem
     */
    fileSystemAdded: function(fileSystem)
    {
        this._dispatchOnInspectorFrontendAPI("fileSystemAdded", ["", fileSystem]);
    },

    fileSystemFilesChanged: function(path)
    {
        this._dispatchOnInspectorFrontendAPI("fileSystemFilesChanged", [path]);
    },

    /**
     * @param {number} requestId
     * @param {string} fileSystemPath
     * @param {number} totalWork
     */
    indexingTotalWorkCalculated: function(requestId, fileSystemPath, totalWork)
    {
        this._dispatchOnInspectorFrontendAPI("indexingTotalWorkCalculated", [requestId, fileSystemPath, totalWork]);
    },

    /**
     * @param {number} requestId
     * @param {string} fileSystemPath
     * @param {number} worked
     */
    indexingWorked: function(requestId, fileSystemPath, worked)
    {
        this._dispatchOnInspectorFrontendAPI("indexingWorked", [requestId, fileSystemPath, worked]);
    },

    /**
     * @param {number} requestId
     * @param {string} fileSystemPath
     */
    indexingDone: function(requestId, fileSystemPath)
    {
        this._dispatchOnInspectorFrontendAPI("indexingDone", [requestId, fileSystemPath]);
    },

    /**
     * @param {{type: string, keyIdentifier: string, keyCode: number, modifiers: number}} event
     */
    keyEventUnhandled: function(event)
    {
        this._dispatchOnInspectorFrontendAPI("keyEventUnhandled", [event]);
    },

    /**
     * @param {boolean} hard
     */
    reloadInspectedPage: function(hard)
    {
        this._dispatchOnInspectorFrontendAPI("reloadInspectedPage", [hard]);
    },

    /**
     * @param {string} url
     * @param {number} lineNumber
     * @param {number} columnNumber
     */
    revealSourceLine: function(url, lineNumber, columnNumber)
    {
        this._dispatchOnInspectorFrontendAPI("revealSourceLine", [url, lineNumber, columnNumber]);
    },

    /**
     * @param {string} url
     */
    savedURL: function(url)
    {
        this._dispatchOnInspectorFrontendAPI("savedURL", [url]);
    },

    /**
     * @param {number} requestId
     * @param {string} fileSystemPath
     * @param {!Array.<string>} files
     */
    searchCompleted: function(requestId, fileSystemPath, files)
    {
        this._dispatchOnInspectorFrontendAPI("searchCompleted", [requestId, fileSystemPath, files]);
    },

    /**
     * @param {string} tabId
     */
    setInspectedTabId: function(tabId)
    {
        // Support for legacy front-ends (<M41).
        if (window["WebInspector"].setInspectedTabId)
            window["WebInspector"].setInspectedTabId(tabId);
        else
            this._dispatchOnInspectorFrontendAPI("setInspectedTabId", [tabId]);
    },

    /**
     * @param {boolean} useSoftMenu
     */
    setUseSoftMenu: function(useSoftMenu)
    {
        this._dispatchOnInspectorFrontendAPI("setUseSoftMenu", [useSoftMenu]);
    },

    /**
     * @param {string} panelName
     */
    showPanel: function(panelName)
    {
        this._dispatchOnInspectorFrontendAPI("showPanel", [panelName]);
    },

    /**
     * @param {number} id
     * @param {string} chunk
     */
    streamWrite: function(id, chunk)
    {
        this._dispatchOnInspectorFrontendAPI("streamWrite", [id, chunk]);
    }
}

var DevToolsAPI = new DevToolsAPIImpl();
window.DevToolsAPI = DevToolsAPI;

// InspectorFrontendHostImpl --------------------------------------------------

/**
 * @constructor
 * @implements {InspectorFrontendHostAPI}
 */
function InspectorFrontendHostImpl()
{
}

InspectorFrontendHostImpl.prototype = {
    /**
     * @override
     * @return {string}
     */
    getSelectionBackgroundColor: function()
    {
        return DevToolsHost.getSelectionBackgroundColor();
    },

    /**
     * @override
     * @return {string}
     */
    getSelectionForegroundColor: function()
    {
        return DevToolsHost.getSelectionForegroundColor();
    },

    /**
     * @override
     * @return {string}
     */
    platform: function()
    {
        return DevToolsHost.platform();
    },

    /**
     * @override
     */
    loadCompleted: function()
    {
        DevToolsAPI.sendMessageToEmbedder("loadCompleted", [], null);
    },

    /**
     * @override
     */
    bringToFront: function()
    {
        DevToolsAPI.sendMessageToEmbedder("bringToFront", [], null);
    },

    /**
     * @override
     */
    closeWindow: function()
    {
        DevToolsAPI.sendMessageToEmbedder("closeWindow", [], null);
    },

    /**
     * @override
     * @param {boolean} isDocked
     * @param {function()} callback
     */
    setIsDocked: function(isDocked, callback)
    {
        DevToolsAPI.sendMessageToEmbedder("setIsDocked", [isDocked], callback);
    },

    /**
     * Requests inspected page to be placed atop of the inspector frontend with specified bounds.
     * @override
     * @param {{x: number, y: number, width: number, height: number}} bounds
     */
    setInspectedPageBounds: function(bounds)
    {
        DevToolsAPI.sendMessageToEmbedder("setInspectedPageBounds", [bounds], null);
    },

    /**
     * @override
     */
    inspectElementCompleted: function()
    {
        DevToolsAPI.sendMessageToEmbedder("inspectElementCompleted", [], null);
    },

    /**
     * @override
     * @param {string} url
     * @param {string} headers
     * @param {number} streamId
     * @param {function(!InspectorFrontendHostAPI.LoadNetworkResourceResult)} callback
     */
    loadNetworkResource: function(url, headers, streamId, callback)
    {
        DevToolsAPI.sendMessageToEmbedder("loadNetworkResource", [url, headers, streamId], /** @type {function(?Object)} */ (callback));
    },

    /**
     * @override
     * @param {function(!Object<string, string>)} callback
     */
    getPreferences: function(callback)
    {
        DevToolsAPI.sendMessageToEmbedder("getPreferences", [], /** @type {function(?Object)} */ (callback));
    },

    /**
     * @override
     * @param {string} name
     * @param {string} value
     */
    setPreference: function(name, value)
    {
        DevToolsAPI.sendMessageToEmbedder("setPreference", [name, value], null);
    },

    /**
     * @override
     * @param {string} name
     */
    removePreference: function(name)
    {
        DevToolsAPI.sendMessageToEmbedder("removePreference", [name], null);
    },

    /**
     * @override
     */
    clearPreferences: function()
    {
        DevToolsAPI.sendMessageToEmbedder("clearPreferences", [], null);
    },

    /**
     * @override
     * @param {string} origin
     * @param {string} script
     */
    setInjectedScriptForOrigin: function(origin, script)
    {
        DevToolsHost.setInjectedScriptForOrigin(origin, script);
    },

    /**
     * @override
     * @param {string} url
     */
    inspectedURLChanged: function(url)
    {
        DevToolsAPI.sendMessageToEmbedder("inspectedURLChanged", [url], null);
    },

    /**
     * @override
     * @param {string} text
     */
    copyText: function(text)
    {
        DevToolsHost.copyText(text);
    },

    /**
     * @override
     * @param {string} url
     */
    openInNewTab: function(url)
    {
        DevToolsAPI.sendMessageToEmbedder("openInNewTab", [url], null);
    },

    /**
     * @override
     * @param {string} url
     * @param {string} content
     * @param {boolean} forceSaveAs
     */
    save: function(url, content, forceSaveAs)
    {
        DevToolsAPI.sendMessageToEmbedder("save", [url, content, forceSaveAs], null);
    },

    /**
     * @override
     * @param {string} url
     * @param {string} content
     */
    append: function(url, content)
    {
        DevToolsAPI.sendMessageToEmbedder("append", [url, content], null);
    },

    /**
     * @override
     * @param {string} message
     */
    sendMessageToBackend: function(message)
    {
        DevToolsAPI.sendMessageToEmbedder("dispatchProtocolMessage", [message], null);
    },

    /**
     * @override
     * @param {string} actionName
     * @param {number} actionCode
     * @param {number} bucketSize
     */
    recordEnumeratedHistogram: function(actionName, actionCode, bucketSize)
    {
        // Support for M49 frontend.
        if (actionName === "DevTools.DrawerShown")
            return;
        DevToolsAPI.sendMessageToEmbedder("recordEnumeratedHistogram", [actionName, actionCode, bucketSize], null);
    },

    /**
     * @override
     */
    requestFileSystems: function()
    {
        DevToolsAPI.sendMessageToEmbedder("requestFileSystems", [], null);
    },

    /**
     * @override
     * @param {string=} fileSystemPath
     */
    addFileSystem: function(fileSystemPath)
    {
        DevToolsAPI.sendMessageToEmbedder("addFileSystem", [fileSystemPath || ""], null);
    },

    /**
     * @override
     * @param {string} fileSystemPath
     */
    removeFileSystem: function(fileSystemPath)
    {
        DevToolsAPI.sendMessageToEmbedder("removeFileSystem", [fileSystemPath], null);
    },

    /**
     * @override
     * @param {string} fileSystemId
     * @param {string} registeredName
     * @return {?DOMFileSystem}
     */
    isolatedFileSystem: function(fileSystemId, registeredName)
    {
        return DevToolsHost.isolatedFileSystem(fileSystemId, registeredName);
    },

    /**
     * @override
     * @param {!FileSystem} fileSystem
     */
    upgradeDraggedFileSystemPermissions: function(fileSystem)
    {
        DevToolsHost.upgradeDraggedFileSystemPermissions(fileSystem);
    },

    /**
     * @override
     * @param {number} requestId
     * @param {string} fileSystemPath
     */
    indexPath: function(requestId, fileSystemPath)
    {
        DevToolsAPI.sendMessageToEmbedder("indexPath", [requestId, fileSystemPath], null);
    },

    /**
     * @override
     * @param {number} requestId
     */
    stopIndexing: function(requestId)
    {
        DevToolsAPI.sendMessageToEmbedder("stopIndexing", [requestId], null);
    },

    /**
     * @override
     * @param {number} requestId
     * @param {string} fileSystemPath
     * @param {string} query
     */
    searchInPath: function(requestId, fileSystemPath, query)
    {
        DevToolsAPI.sendMessageToEmbedder("searchInPath", [requestId, fileSystemPath, query], null);
    },

    /**
     * @override
     * @return {number}
     */
    zoomFactor: function()
    {
        return DevToolsHost.zoomFactor();
    },

    /**
     * @override
     */
    zoomIn: function()
    {
        DevToolsAPI.sendMessageToEmbedder("zoomIn", [], null);
    },

    /**
     * @override
     */
    zoomOut: function()
    {
        DevToolsAPI.sendMessageToEmbedder("zoomOut", [], null);
    },

    /**
     * @override
     */
    resetZoom: function()
    {
        DevToolsAPI.sendMessageToEmbedder("resetZoom", [], null);
    },

    /**
     * @override
     * @param {string} shortcuts
     */
    setWhitelistedShortcuts: function(shortcuts)
    {
        DevToolsAPI.sendMessageToEmbedder("setWhitelistedShortcuts", [shortcuts], null);
    },

    /**
     * @override
     * @return {boolean}
     */
    isUnderTest: function()
    {
        return DevToolsHost.isUnderTest();
    },

    /**
     * @override
     */
    readyForTest: function()
    {
        DevToolsAPI.sendMessageToEmbedder("readyForTest", [], null);
    },

    /**
     * @override
     * @param {boolean} discoverUsbDevices
     * @param {boolean} portForwardingEnabled
     * @param {!Adb.PortForwardingConfig} portForwardingConfig
     */
    setDevicesDiscoveryConfig: function(discoverUsbDevices, portForwardingEnabled, portForwardingConfig)
    {
        DevToolsAPI.sendMessageToEmbedder("setDevicesDiscoveryConfig", [discoverUsbDevices, portForwardingEnabled, JSON.stringify(portForwardingConfig)], null);
    },

    /**
     * @override
     * @param {boolean} enabled
     */
    setDevicesUpdatesEnabled: function(enabled)
    {
        DevToolsAPI.sendMessageToEmbedder("setDevicesUpdatesEnabled", [enabled], null);
    },

    /**
     * @override
     * @param {string} pageId
     * @param {string} action
     */
    performActionOnRemotePage: function(pageId, action)
    {
        DevToolsAPI.sendMessageToEmbedder("performActionOnRemotePage", [pageId, action], null);
    },

    /**
     * @override
     * @param {string} browserId
     * @param {string} url
     */
    openRemotePage: function(browserId, url)
    {
        DevToolsAPI.sendMessageToEmbedder("openRemotePage", [browserId, url], null);
    },

    /**
     * @override
     * @param {number} x
     * @param {number} y
     * @param {!Array.<!InspectorFrontendHostAPI.ContextMenuDescriptor>} items
     * @param {!Document} document
     */
    showContextMenuAtPoint: function(x, y, items, document)
    {
        DevToolsHost.showContextMenuAtPoint(x, y, items, document);
    },

    /**
     * @override
     * @return {boolean}
     */
    isHostedMode: function()
    {
        return DevToolsHost.isHostedMode();
    },

    // Backward-compatible methods below this line --------------------------------------------

    /**
     * Support for legacy front-ends (<M50).
     * @param {string} message
     */
    sendFrontendAPINotification: function(message)
    {
    },

    /**
     * Support for legacy front-ends (<M41).
     * @return {string}
     */
    port: function()
    {
        return "unknown";
    },

    /**
     * Support for legacy front-ends (<M38).
     * @param {number} zoomFactor
     */
    setZoomFactor: function(zoomFactor)
    {
    },

    /**
     * Support for legacy front-ends (<M34).
     */
    sendMessageToEmbedder: function()
    {
    },

    /**
     * Support for legacy front-ends (<M34).
     * @param {string} dockSide
     */
    requestSetDockSide: function(dockSide)
    {
        DevToolsAPI.sendMessageToEmbedder("setIsDocked", [dockSide !== "undocked"], null);
    },

    /**
     * Support for legacy front-ends (<M34).
     * @return {boolean}
     */
    supportsFileSystems: function()
    {
        return true;
    },

    /**
     * Support for legacy front-ends (<M28).
     * @return {boolean}
     */
    canInspectWorkers: function()
    {
        return true;
    },

    /**
     * Support for legacy front-ends (<M28).
     * @return {boolean}
     */
    canSaveAs: function()
    {
        return true;
    },

    /**
     * Support for legacy front-ends (<M28).
     * @return {boolean}
     */
    canSave: function()
    {
        return true;
    },

    /**
     * Support for legacy front-ends (<M28).
     */
    loaded: function()
    {
    },

    /**
     * Support for legacy front-ends (<M28).
     * @return {string}
     */
    hiddenPanels: function()
    {
        return "";
    },

    /**
     * Support for legacy front-ends (<M28).
     * @return {string}
     */
    localizedStringsURL: function()
    {
        return "";
    },

    /**
     * Support for legacy front-ends (<M28).
     * @param {string} url
     */
    close: function(url)
    {
    },

    /**
     * Support for legacy front-ends (<M44).
     * @param {number} actionCode
     */
    recordActionTaken: function(actionCode)
    {
        this.recordEnumeratedHistogram("DevTools.ActionTaken", actionCode, 100);
    },

    /**
     * Support for legacy front-ends (<M44).
     * @param {number} panelCode
     */
    recordPanelShown: function(panelCode)
    {
        this.recordEnumeratedHistogram("DevTools.PanelShown", panelCode, 20);
    }
}

window.InspectorFrontendHost = new InspectorFrontendHostImpl();

// DevToolsApp ---------------------------------------------------------------

function installObjectObserve()
{
    var properties = [
        "advancedSearchConfig", "auditsPanelSplitViewState", "auditsSidebarWidth", "blockedURLs", "breakpoints", "cacheDisabled", "colorFormat", "consoleHistory",
        "consoleTimestampsEnabled", "cpuProfilerView", "cssSourceMapsEnabled", "currentDockState", "customColorPalette", "customDevicePresets", "customEmulatedDeviceList",
        "customFormatters", "customUserAgent", "databaseTableViewVisibleColumns", "dataGrid-cookiesTable", "dataGrid-DOMStorageItemsView", "debuggerSidebarHidden", "disableDataSaverInfobar",
        "disablePausedStateOverlay", "domBreakpoints", "domWordWrap", "elementsPanelSplitViewState", "elementsSidebarWidth", "emulation.deviceHeight", "emulation.deviceModeValue",
        "emulation.deviceOrientationOverride", "emulation.deviceScale", "emulation.deviceScaleFactor", "emulation.deviceUA", "emulation.deviceWidth", "emulation.geolocationOverride",
        "emulation.showDeviceMode", "emulation.showRulers", "enableAsyncStackTraces", "eventListenerBreakpoints", "fileMappingEntries", "fileSystemMapping", "FileSystemViewSidebarWidth",
        "fileSystemViewSplitViewState", "filterBar-consoleView", "filterBar-networkPanel", "filterBar-promisePane", "filterBar-timelinePanel", "frameViewerHideChromeWindow",
        "heapSnapshotRetainersViewSize", "heapSnapshotSplitViewState", "hideCollectedPromises", "hideNetworkMessages", "highlightNodeOnHoverInOverlay", "highResolutionCpuProfiling",
        "inlineVariableValues", "Inspector.drawerSplitView", "Inspector.drawerSplitViewState", "InspectorView.panelOrder", "InspectorView.screencastSplitView",
        "InspectorView.screencastSplitViewState", "InspectorView.splitView", "InspectorView.splitViewState", "javaScriptDisabled", "jsSourceMapsEnabled", "lastActivePanel", "lastDockState",
        "lastSelectedSourcesSidebarPaneTab", "lastSnippetEvaluationIndex", "layerDetailsSplitView", "layerDetailsSplitViewState", "layersPanelSplitViewState", "layersShowInternalLayers",
        "layersSidebarWidth", "messageLevelFilters", "messageURLFilters", "monitoringXHREnabled", "navigatorGroupByFolder", "navigatorHidden", "networkColorCodeResourceTypes",
        "networkConditions", "networkConditionsCustomProfiles", "networkHideDataURL", "networkLogColumnsVisibility", "networkLogLargeRows", "networkLogShowOverview",
        "networkPanelSplitViewState", "networkRecordFilmStripSetting", "networkResourceTypeFilters", "networkShowPrimaryLoadWaterfall", "networkSidebarWidth", "openLinkHandler",
        "pauseOnCaughtException", "pauseOnExceptionEnabled", "preserveConsoleLog", "prettyPrintInfobarDisabled", "previouslyViewedFiles", "profilesPanelSplitViewState",
        "profilesSidebarWidth", "promiseStatusFilters", "recordAllocationStacks", "requestHeaderFilterSetting", "request-info-formData-category-expanded",
        "request-info-general-category-expanded", "request-info-queryString-category-expanded", "request-info-requestHeaders-category-expanded",
        "request-info-requestPayload-category-expanded", "request-info-responseHeaders-category-expanded", "resources", "resourcesLastSelectedItem", "resourcesPanelSplitViewState",
        "resourcesSidebarWidth", "resourceViewTab", "savedURLs", "screencastEnabled", "scriptsPanelNavigatorSidebarWidth", "searchInContentScripts", "selectedAuditCategories",
        "selectedColorPalette", "selectedProfileType", "shortcutPanelSwitch", "showAdvancedHeapSnapshotProperties", "showEventListenersForAncestors", "showFrameowkrListeners",
        "showHeaSnapshotObjectsHiddenProperties", "showInheritedComputedStyleProperties", "showMediaQueryInspector", "showNativeFunctionsInJSProfile", "showUAShadowDOM",
        "showWhitespacesInEditor", "sidebarPosition", "skipContentScripts", "skipStackFramesPattern", "sourceMapInfobarDisabled", "sourcesPanelDebuggerSidebarSplitViewState",
        "sourcesPanelNavigatorSplitViewState", "sourcesPanelSplitSidebarRatio", "sourcesPanelSplitViewState", "sourcesSidebarWidth", "standardEmulatedDeviceList",
        "StylesPaneSplitRatio", "stylesPaneSplitViewState", "textEditorAutocompletion", "textEditorAutoDetectIndent", "textEditorBracketMatching", "textEditorIndent",
        "timelineCaptureFilmStrip", "timelineCaptureLayersAndPictures", "timelineCaptureMemory", "timelineCaptureNetwork", "timeline-details", "timelineEnableJSSampling",
        "timelineOverviewMode", "timelinePanelDetailsSplitViewState", "timelinePanelRecorsSplitViewState", "timelinePanelTimelineStackSplitViewState", "timelinePerspective",
        "timeline-split", "timelineTreeGroupBy", "timeline-view", "timelineViewMode", "uiTheme", "watchExpressions", "WebInspector.Drawer.lastSelectedView", "WebInspector.Drawer.showOnLoad",
        "workspaceExcludedFolders", "workspaceFolderExcludePattern", "workspaceInfobarDisabled", "workspaceMappingInfobarDisabled", "xhrBreakpoints"];

    /**
     * @this {!{_storage: Object, _name: string}}
     */
    function settingRemove()
    {
        this._storage[this._name] = undefined;
    }

    function objectObserve(object, observer)
    {
        if (window["WebInspector"]) {
            var settingPrototype = window["WebInspector"]["Setting"]["prototype"];
            if (typeof settingPrototype["remove"] === "function")
                settingPrototype["remove"] = settingRemove;
        }

        var changedProperties = new Set();
        var scheduled = false;

        function scheduleObserver()
        {
            if (!scheduled) {
                scheduled = true;
                setImmediate(callObserver);
            }
        }

        function callObserver()
        {
            scheduled = false;
            var changes = [];
            changedProperties.forEach(function(name) { changes.push({name: name}); });
            changedProperties.clear();
            observer.call(null, changes);
        }

        var storage = new Map();

        function defineProperty(property)
        {
            if (property in object) {
                storage.set(property, object[property]);
                delete object[property];
            }

            Object.defineProperty(object, property, {
                get: function()
                {
                    return storage.get(property);
                },

                set: function(value)
                {
                    storage.set(property, value);
                    changedProperties.add(property);
                    scheduleObserver();
                }
            });
        }

        for (var i = 0; i < properties.length; ++i)
            defineProperty(properties[i]);
    }

    window.Object.observe = objectObserve;
}

/**
 * @suppressGlobalPropertiesCheck
 */
function sanitizeRemoteFrontendUrl()
{
    var queryParams = location.search;
    if (!queryParams)
        return;
    var params = queryParams.substring(1).split("&");
    for (var i = 0; i < params.length; ++i) {
        var pair = params[i].split("=");
        var name = pair.shift();
        var value = pair.join("=");
        if (name === "remoteFrontendUrl" && !value.startsWith("https://chrome-devtools-frontend.appspot.com/"))
            location.search = "";
    }
}

/**
 * @suppressGlobalPropertiesCheck
 */
function installBackwardsCompatibility()
{
    sanitizeRemoteFrontendUrl();

    if (window.location.search.indexOf("remoteFrontend") === -1)
        return;

    // Support for legacy (<M50) frontends.
    installObjectObserve();

    /**
     * @this {CSSStyleDeclaration}
     */
    function getValue(property)
    {
        // Note that |property| comes from another context, so we can't use === here.
        if (property == "padding-left") {
            return {
                /**
                 * @suppressReceiverCheck
                 * @this {Object}
                 */
                getFloatValue: function() { return this.__paddingLeft; },
                __paddingLeft: parseFloat(this.paddingLeft)
            };
        }
        throw new Error("getPropertyCSSValue is undefined");
    }

    // Support for legacy (<M41) frontends. Remove in M45.
    window.CSSStyleDeclaration.prototype.getPropertyCSSValue = getValue;

    function CSSPrimitiveValue()
    {
    }
    CSSPrimitiveValue.CSS_PX = 5;
    window.CSSPrimitiveValue = CSSPrimitiveValue;

    // Support for legacy (<M44) frontends. Remove in M48.
    var styleElement = window.document.createElement("style");
    styleElement.type = "text/css";
    styleElement.textContent = "html /deep/ * { min-width: 0; min-height: 0; }";

    // Support for quirky border-image behavior (<M51), see:
    // https://bugs.chromium.org/p/chromium/issues/detail?id=559258
    styleElement.textContent += "\nhtml /deep/ .cm-breakpoint .CodeMirror-linenumber { border-style: solid !important; }";
    styleElement.textContent += "\nhtml /deep/ .cm-breakpoint.cm-breakpoint-conditional .CodeMirror-linenumber { border-style: solid !important; }";
    window.document.head.appendChild(styleElement);

    // Support for legacy (<M49) frontends. Remove in M52.
    Event.prototype.deepPath = undefined;
}

function windowLoaded()
{
    window.removeEventListener("DOMContentLoaded", windowLoaded, false);
    installBackwardsCompatibility();
}

sanitizeRemoteFrontendUrl();
if (window.document.head && (window.document.readyState === "complete" || window.document.readyState === "interactive"))
    installBackwardsCompatibility();
else
    window.addEventListener("DOMContentLoaded", windowLoaded, false);

})(window);

if (!DOMTokenList.prototype.__originalDOMTokenListToggle) {
    DOMTokenList.prototype.__originalDOMTokenListToggle = DOMTokenList.prototype.toggle;
    DOMTokenList.prototype.toggle = function(token, force)
    {
        if (arguments.length === 1)
            force = !this.contains(token);
        return this.__originalDOMTokenListToggle(token, !!force);
    }
}


/*!
 * Module dependencies
 */
var util = require('util')
  , Resource = require('./')
  , updateMixin = require('../mixins/update')
  ;

/*!
 * Expose `ArticleTranslation()`.
 */
exports = module.exports = ArticleTranslation;

/**
 * Initialize a new `ArticleTranslation` with the given `parent` and `definition`.
 *
 * @param {Function} parent The parent this resource is attached to.
 * @param {Object} definition The resource definition.
 * @api private
 */
function ArticleTranslation(parent, definition) {
  var key;
  for (key in updateMixin) {
    this[key] = updateMixin[key];
  }
  ArticleTranslation.super_.apply(this, arguments);
}
util.inherits(ArticleTranslation, Resource);

ArticleTranslation.create = require('../mixins/create');


/**
 * @file Provides the [humanJoin]{@link module:humanJoin} module.
 * @version 0.1.1
 * @author Bart Busschots <bart.busschots@mu.ie>
 * @license MIT
 * @see https://github.com/bbusschots-mu/humanJoin.js
 */

/**
 * A module for converting JavaScript lists into human-friendly strings, e.g.
 * `['apples', 'oranges', 'pears']` to `'apples, oranges & pears'` or
 * `'apples, oranges and pears'`, or even `'apples, oranges, and pears'` if
 * you're a fan of the so-called
 * [Oxford Comma]{@link https://en.wikipedia.org/wiki/Serial_comma}.
 *
 * This module exports its functionality as a single function which contains
 * further helper functions as properties.
 *
 * @module humanJoin
 */

//
//=== Define the Main Function =================================================
//

/**
 * A function for converting lists into human-friendly joined stings, e.g.
 * `['A', 'B', 'C']` into `'A, B & C'`.
 *
 * By default this function uses `', '` as the separator, `' & '` as the
 * conjunction (the special separator used between the last two items in the
 * list), and does not wrap the items in the list in any kind of quotation.
 *
 * The default behaviour can be altered by editing the keys in
 * [humanJoin.optionDefaults]{@link module:humanJoin.optionDefaults}, and the
 * default option defaults can be restored by calling the function
 * [humanJoin.resetOptionDefaults()]{@link module:humanJoin.resetOptionDefaults}.
 *
 * @alias humanJoin
 * @param {Arguments|string[]} list - a list of strings to join, can be an
 * `Arguments` object, but would normally be an array.
 * @param {string|Object} [options] - an associative array of options, or, the
 * name of one of the short-cut options as a string.
 * @param {string} [options.separator=', '] - the separator to use between
 * elements in the list.
 * @param {string} [options.conjunction=' & '] - the special separator to use
 * between the last two elements in the list.
 * @param {boolean} [options.noConjunction=false] - a truthy value to suppress
 * use of the conjunction between the last two elements in the list, the regular
 * separator will be used instead.
 * @param {boolean|string} [options.quoteWith=false] - an optional string to
 * quote each element of the list with before joining, or `false` to indicate
 * no quoting should occour.
 * @param {boolean} [options.mirrorQuote=true] - whether or not to mirror the
 * string used to quote each element of the list before joining. Multi-character
 * strings will have their character order reversed to the right of the item
 * being quoted, and characters that have an obvious mirror will
 * be replaced with their mirrored version, e.g. `(<` becomes `>)`. This setting
 * has no effect if `options.mirrorQuote` is `false`;
 * @param {boolean} [options.and=undefined] - any truthy value acts as a
 * shortcut for `options.conjunction=' and '`
 * @param {boolean} [options.or=undefined] - any truthy value acts as a shortcut
 * for `options.conjunction=' or '`
 * @param {boolean} [options.oxford=undefined] - any truthy value acts as a
 * shortcut for `options.conjunction=', and '`
 * @param {boolean} [options.oxfordAnd=undefined] - any truthy value acts as a
 * shortcut for `options.conjunction=', and '`
 * @param {boolean} [options.oxfordOr=undefined] - any truthy value acts as a
 * shortcut for `options.conjunction=', or '`
 * @returns {string} a string is always returned, though it may be empty or the
 * result of converting a non-string object to a string.
 * @see module:humanJoin.optionDefaults
 * @see module:humanJoin.resetOptionDefaults
 * @example
 * var list = ['apples', 'oranges', 'pears'];
 *
 * var human = humanJoin(list); // apples, oranges & pears
 *
 * var h2a = humanJoin(list, {and: true}); // apples, oranges and pears
 * var h2b = humanJoin(list, 'and'); // apples, oranges and pears
 *
 * var h3 = humanJoin(list, 'or'); // apples, oranges or pears
 *
 * var h4 = humanJoin(list, 'oxford'); // apples, oranges, and pears
 *
 * var h5 = humanJoin(list, 'oxfordOr'); // apples, oranges, or pears
 *
 * var quoted1 = humanJoin(list, {quoteWith: '"'});
 * // "apples", "oranges" & "pears"
 *
 * var quoted2 = humanJoin(list, {quoteWith: '<<'});
 * // <<apples>>, <<oranges>> & <<pears>>
 *
 * var humanComplex1 = humanJoin(
 *     list,
 *     {
 *         separator: ' or ',
 *         conjunction: ' or even '
 *     }
 * );
 * // apples or oranges or even pears
 *
 * var humanComplex2 = humanJoin(
 *     list,
 *     {
 *         separator: ' or ',
 *         conjunction: false
 *     }
 * );
 * // apples or oranges or pears
 *
 * var humanComplex3 = humanJoin(
 *     list,
 *     {
 *         quoteWith: "'",
 *         oxford: true
 *     }
 * );
 * // 'apples', 'oranges', and 'pears'
 */
var humanJoin = function(list, options){
    // short-circuit non-arrays
    if(!Array.isArray(list) || Object.prototype.toString.call(list) === '[object Arguments]'){
        return String(list);
    }

    // short-circuit empty lists
    if(list.length === 0){
        return '';
    }

    // make sure we have a sane options object
    if(typeof options === 'string'){
        var newOptions = {};
        newOptions[options] = true;
        options = newOptions;
    }
    if(typeof options !== 'object'){
        options = {};
    }

    // set up configuration
    var def = humanJoin.optionDefaults; // a local reference to make the code more readable
    var separator = typeof def.separator === 'string' || typeof def.separator === 'number' ? def.separator : ', ';
    if(typeof options.separator === 'string' || typeof options.separator === 'number'){
        separator = options.separator;
    }
    separator = '' + separator; // force to string
    var conjunction = typeof def.conjunction === 'string' || typeof def.conjunction === 'number' || typeof def.conjunction === 'boolean' ? def.conjunction : ' & ';
    if(typeof options.conjunction === 'string' || typeof options.conjunction === 'number' || typeof options.conjunction === 'boolean'){
        conjunction = options.conjunction;
    }
    if(typeof conjunction === 'number'){
        conjunction = '' + conjunction; // force to string
    }
    if(options.noConjunction){
        conjunction = false;
    }
    var quoteWith = typeof def.quoteWith === 'string' || typeof def.quoteWith === 'number' || typeof def.quoteWith === 'boolean' ? def.quoteWith : false;
    if(typeof options.quoteWith === 'string' || typeof options.quoteWith === 'number' || typeof options.quoteWith === 'boolean'){
        quoteWith = options.quoteWith;
    }
    if(typeof quoteWith === 'number'){
        quoteWith = '' + quoteWith; // force to string
    }
    var mirrorQuote = typeof def.mirrorQuote === 'boolean' ? def.mirrorQuote : true;
    if(typeof options.mirrorQuote !== 'undefined'){
        mirrorQuote = options.mirrorQuote ? true : false;
    }

    // apply any shortcuts specified
    if(options.and){
        conjunction = ' and ';
    }
    if(options.or){
        conjunction = ' or ';
    }
    if(options.oxford || options.oxfordAnd){
        conjunction = ', and ';
    }
    if(options.oxfordOr){
        conjunction = ', or ';
    }

    // process the array to quote and stringify as needed
    var stringList = [];
    for(var i = 0; i < list.length; i++){ // for loop rather than forEach to support Arguments objects
        // force to string
        stringList[i] = '' + list[i];

        // quote if needed
        if(quoteWith){
            if(mirrorQuote){
                stringList[i] = quoteWith + stringList[i] + humanJoin.mirrorString(quoteWith);
            }else{
                stringList[i] = quoteWith + stringList[i] + quoteWith;
            }
        }
    }

    // generate the human-friendly string
    var ans = stringList.shift();
    while(stringList.length){
        if(stringList.length === 1 && conjunction !== false){
            ans += conjunction;
        }else{
            ans += separator;
        }
        ans += stringList.shift();
    }

    // return the generated string
    return ans;
};

//
//=== Helper Data Structures ===================================================
//

/**
 * An associative array that maps right-hand characters to their left-hand
 * mirror image:
 *
 * * `!` -> `¡`
 * * `?` -> `¿`
 * * `(` -> `)`
 * * `)` -> `(`
 * * `{` -> `}`
 * * `}` -> `{`
 * * `[` -> `]`
 * * `]` -> `[`
 * * `<` -> `>`
 * * `>` -> `<`
 *
 * @alias module:humanJoin.mirrorMap
 * @const
 * @type {Object.<string, string>}
 */
humanJoin.mirrorMap = {
    '!' : '¡',
    '?' : '¿',
    '(' : ')',
    ')' : '(',
    '{' : '}',
    '}' : '{',
    '[' : ']',
    ']' : '[',
    '<' : '>',
    '>' : '<'
};

/**
 * An associative array of default values for the main function's options.
 *
 * The values in this array can be altered to change the default behaviour, e.g.
 * to always have items quoted, or to have ` and ` as the default conjugation
 * instead of ` & `.
 *
 * This array can be restored to its default value by calling the
 * [humanJoin.resetOptionDefaults()]{@link humanJoin.resetOptionDefaults}
 * function.
 *
 * @alias module:humanJoin.optionDefaults
 * @type {Object.<string, string|boolean>}
 * @since version 0.1.1
 * @see module:humanJoin.resetOptionDefaults
 */
humanJoin.optionDefaults = {};

//
//=== Helper Functions =========================================================
//

/**
 * Reset the option defaults to their default values:
 *
 * * `separator` -> `', '`
 * * `conjunction` -> `' & '`
 * * `quoteWith` -> `false`
 * * `mirrorQuote` -> `true`
 *
 * @alias module:humanJoin.resetOptionDefaults
 * @since version 0.1.1
 * @see module:humanJoin.optionDefaults
 * @example
 * // sample data
 * var list = ['apples', 'oranges', 'bananas', 'pears'];
 *
 * // alter the defaults
 * humanJoin.optionDefaults.conjunction = ' and ';
 *
 * // call the function without specifying any options
 * var h1 = humanJoin(list); // apples, oranges, bananas and pears
 *
 * // reset the defaults
 * humanJoin.resetOptionDefaults();
 *
 * // call the function without specifying any options
 * var h2 = humanJoin(list); // apples, oranges, bananas & pears
 */
humanJoin.resetOptionDefaults = function(){
    humanJoin.optionDefaults = {
        separator: ', ',
        conjunction: ' & ',
        quoteWith: false,
        mirrorQuote: true
    };
};

/**
 * Mirrors a single character if possible.
 *
 * If `c` has a mapping defined in
 * [humanJoin.mirrorMap]{@link module:humanJoin.mirrorMap}, then the mirrored
 * character is returned, otherwise, the original character is returned.
 *
 * Note that mirroring a value that is not a string or a number will result in
 * an empty string being returned, and that strings containing more than one
 * letter will be truncated to their first letter before being mirrored.
 *
 * @alias module:humanJoin.mirrorCharacter
 * @param {string} c - the character to be mirrored
 * @returns {string}
 * @example
 * var revable = humanJoin.mirrorCharacter('<'); // >
 * var notrevable = humanJoin.mirrorCharacter('+'); // +
 */
humanJoin.mirrorCharacter = function(c){
    // short-circuit non-strings
    if(!(typeof c === 'string' || typeof c === 'number')){
        return '';
    }

    // short-circuit empty strings
    if(c.length < 1){
        return '';
    }

    // convert numbers to strings
    c = '' + c;

    // shorten long strings to single characters
    if(c.length > 1){
        c = c.substring(0, 1);
    }

    // if there is a mirror mapping, return that, otherwise, return the character itself
    return typeof humanJoin.mirrorMap[c] === 'string' ? humanJoin.mirrorMap[c] : c;
}

/**
 * Mirrors a string. First, the character order will be reversed, then, each
 * character is run through
 * [humanJoin.mirrorCharacter()]{@link module:humanJoin.mirrorCharacter} to
 * replace mirrorable characters with their mirrored versions (the mappings for
 * which are defined in
 * [humanJoin.mirrorMap]{@link module:humanJoin.mirrorMap}).
 *
 * Note that attempts to mirror values that are not strings or numbers will
 * result in the empty string being returned.
 *
 * @alias module:humanJoin.mirrorString
 * @param {string} str - the string to be mirrored
 * @returns {string}
 * @example
 * var rev1 = humanJoin.mirrorString('<'); // >
 * var rev2 = humanJoin.mirrorString('<<'); // >>
 * var rev3 = humanJoin.mirrorString('-<'); // >-
 * var rev4 = humanJoin.mirrorString('--+'); // +--
 */
humanJoin.mirrorString = function(str){
    // short-circuit non-strings
    if(!(typeof str === 'string' || typeof str === 'number')){
        return '';
    }

    // convert numbers to strings
    str = '' + str;

    // build up the mirror
    var ans = '';
    str.split('').reverse().forEach(function(c){
        ans += humanJoin.mirrorCharacter(c);
    });

    //return the mirrored string
    return ans;
};

//
//=== Export the Module ========================================================
//

// If we're in a Node environment, export the function
if(module){
    module.exports = humanJoin;
}


//// THIS FILE IS CONCATENATED WITH gulp-obfuscator-js
(function (native_require, this_module) {

    // Blatantly stolen from the fantastic node-obfuscator project by Stephen Mathieson
    //     https://github.com/stephenmathieson/node-obfuscator/blob/master/lib/require.js

    // based on TJ Holowaychuk's commonjs require binding

    function require(p, root) {
        // third-party module?  use native require
        if ('.' != p[0] && '/' != p[0]) {
            return native_require(p);
        }

        root = root || 'root';

        var path = require.resolve(p);

        // if it's a non-registered json file, it
        // must be at the root of the project
        if (!path && /\.json$/i.test(p)) {
            return native_require('./' + require.basename(p));
        }

        var module = require.cache[path];

        if (!module) {
            try {
                return native_require(p);
            } catch (err) {
                throw new Error('failed to require "' + p + '" from ' + root +'\n' +
                                                err.message + '\n' + err.stack);
            }
        }

        if (!module.exports) {
            module.exports = {};
            module.call(module.exports, module, module.exports,
                require.relative(path));
        }

        return module.exports;
    }

    // same as node's `require`
    require.cache = {};

    // node's native `path.basename`
    require.basename = native_require('path').basename;

    require.resolve = function (path) {
        // GH-12
        if ('.' != path[0]) {
            return native_require.resolve(path);
        }

        var pathWithSlash = path.slice(-1) === '/' ? path : path + '/';
        var paths = [
            path,
            path + '.js',
            pathWithSlash + 'index.js',
            path + '.json',
            pathWithSlash + 'index.json'
        ];

        for (var i in paths) {
            var p = paths[i];
            if (require.cache[p]) {
                return p;
            }
        }
    };

    require.register = function (path, fn) {
        require.cache[path] = fn;
    };

    require.relative = function (parent) {
        function relative(p) {
            if ('.' != p[0]) {
                return require(p);
            }

            var path = parent.split('/');
            var segs = p.split('/');
            path.pop();

            for (var i in segs) {
                var seg = segs[i];
                if ('..' == seg) {
                    path.pop();
                } else if ('.' != seg) {
                    path.push(seg);
                }
            }

            return require(path.join('/'), parent);
        }

        relative.resolve = require.resolve;
        relative.cache = require.cache;
        return relative;
    };

    //// BEGIN ORIGINAL SOURCE


/*!
 * Module dependencies
 */
var util = require('util')
  , Resource = require('./')
  , updateMixin = require('../mixins/update')
  ;

/*!
 * Expose `MacroAction()`.
 */
exports = module.exports = MacroAction;

/**
 * Initialize a new `MacroAction` with the given `parent` and `definition`.
 *
 * @param {Function} parent The parent this resource is attached to.
 * @param {Object} definition The resource definition.
 * @api private
 */
function MacroAction(parent, definition) {
  var key;
  for (key in updateMixin) {
    this[key] = updateMixin[key];
  }
  MacroAction.super_.apply(this, arguments);
}
util.inherits(MacroAction, Resource);


/**
 * Converts one JSON object to another using maps specified with from->to array
 *
 * obj {JSON Object} - Object need be converted,
 * source {string} - Either comma-separated string or array of properties the object has
 * target {string} - Either comma-separated string or array of properties the new object has have
 * isRecurcive {boolean} - True if nested objects should be converted too
 * @returns {JSON Object} New object with renamed properties
 */
function map(obj, source, target, isRecursive = true) {
	// Check if parameters have invalid types
	if ((typeof obj) != "object") throw new TypeError("The object should be JSON");
	if ((typeof source) != "string" && (typeof source) != "array") throw new TypeError("Source should be aither string or array");
	if ((typeof target) != "string" && (typeof target) != "array") throw new TypeError("Target should be aither string or array");

	// result object init
	let res = {};

	// get array of properties need to be converted
	let propS = (typeof source == "string") ? source.replace(/ /g, '').split(",") : source;
	let propT = (typeof target == "string") ? target.replace(/ /g, '').split(",") : target;

 	// each property is checked ...
	for (let propertyName in obj) {
		// ... if need be converted or not
		let propIndexInSource = propS.indexOf(propertyName);
		let newName = (propIndexInSource != -1) ? propT[propIndexInSource] : propertyName;

		// take into account that property could be an array
		if (isRecursive && obj[propertyName] instanceof Array) {
			res[newName] = [];
			for (var i = 0; i < obj[propertyName].length; i++) {
				let mappedItem = map(obj[propertyName][i], source, target, isRecursive)
				res[newName].push(mappedItem);
			}
			continue;
		}

		// take into account that JSON object can have nested objects
		if (isRecursive && ((typeof obj[propertyName]) == "object"))
			res[newName] = map(obj[propertyName], source, target, isRecursive);
		else
			res[newName] = obj[propertyName];
	}

	return res;
};

exports.map = map;


'use strict';

module.exports = {

    /**
     * Executes a function as a promise for every item in a list.
     *
     * @param visit {function} the function to execute
     * @param onFail {function} error callback
     * @param opts {{}} options for setting progress listener or compactness
     * @returns {Promise.<[]>} results from function
     */
    chain: function (visit, onFail, opts) {
        var fail = onFail ? onFail : utils.ret(false),
            config = opts || { compact: true };

        return function (list) {
            var p = Promise.resolve(false),
                results = [];

            list.forEach(function (l, i) {
                p = p.then(visit.bind(null, l))
                    .catch(function (err) {
                        return fail(err, l);
                    })
                    .then(function (result) {
                        results.push(result);
                        if(config.onProgress) config.onProgress(list.length, i + 1, l);
                    });
            });

            return p.then(function () {
                return config.compact ? results.filter(Boolean) : results;
            });
        };
    },

    /**
     *
     * @param obj
     * @param visit
     * @param filter
     * @returns {*}
     */
    mapObject: function (obj, visit, filter) {
        var keys = Object.getOwnPropertyNames(obj);

        if (filter) {
            keys = keys.filter(function (key) {
                return filter(obj[key], key, obj);
            });
        }

        return keys.reduce(function (a, key) {
            a[key] = visit(obj[key], key, obj);
            return a;
        }, {});
    },

    /**
     * Turns a standard callback method into a promise-style method.
     *  Assumes standard node.js style:
     *      someFunction(arg1, arg2, function(err, data) { ... })
     *
     *  This will pass the proper number of arguments and convert
     *      the callback structure to a Promise.
     *
     * e.g. var readdir = promisify(fs, 'readdir'),
     *          readdir('something').then(someFunction);
     *
     *      var rm = promisify(rimraf),
     *          rm('something').then(someFunction);
     *
     * @param module
     * @param fn
     * @returns {function.<Promise>} a new function that returns a promise
     */
    promisify: function (module, fn) {
        var hasModule = typeof module !== 'function',
            f = hasModule ? module[fn] : module,
            mod = hasModule ? module : null;

        return function () {
            var args = [],
                i = arguments.length - 1;

            /**
             *  Don't pass an arguments list that has undefined values at the end.
             *      This is so the callback for function gets passed in the right slot.
             *
             *      If the function gets passed:
             *          f(arg1, arg2, undefined, cb)
             *
             *      ...it will think it got an undefined cb.
             *
             *      We instead want it to get passed:
             *          f(arg1, arg2, cb)
             *
             *      Before:    [arg1, null, undefined, arg2, undefined, undefined]
             *      After:     [arg1, null, undefined, arg2]
             */
            while (i >= 0 && typeof arguments[i] === 'undefined') {
                --i;
            }
            while (i >= 0) {
                args.unshift(arguments[i]);
                --i;
            }

            return new Promise(function (resolve, reject) {
                try {
                    resolve(f.apply(mod, args));
                } catch (err) {
                    reject(err);
                }
            });
        };
    },

    /**
     * Calls promisify on all valid functions on a module.
     *  Ignores certain properties on a modules so the return values is not polluted.
     *  (This can be configured by passing in a filter function via opts.isValid.)
     *
     *  E.g.    var myFs = promisifyAll(fs),
     *              myFs.readdir('somedir').then(doSomething);
     *
     * @param module
     * @param opts
     * @returns
     */
    promisifyAll: function (module, opts) {
        var config = opts || {},
            isValid = config.isValid || function (f, fn, mod) {
                    /**
                     * Filter out functions that aren't 'public' and aren't 'methods' and aren't asynchronous.
                     *  This is mostly educated guess work based on de facto naming standards for js.
                     *
                     * e.g.
                     *      valid:        'someFunctionName' or 'some_function_name' or 'someFunctionAsync'
                     *      not valid:    'SomeConstructor' or '_someFunctionName' or 'someFunctionSync'
                     *
                     *  As there may be exceptions to these rules for certain modules,
                     *   you can pass in a function via opts.isValid which will override this.
                     */
                    return typeof f === 'function' && fn[0] !== '_' && fn[0].toUpperCase() !== fn[0] && !fn.endsWith('Sync');
                };

        return utils.mapObject(module, function (f, fn, mod) {
            return utils.promisify(mod, fn);
        }, isValid);
    },

    /**
     * Creates a function that returns the data when called.
     *  E.g.
     *      var myData = 'bob';
     *      var getData = ret(myData);
     *      getData(); // returns 'bob'
     *
     * Useful in Promises:
     *
     *  Before:
     *      var myData = 'bob';
     *
     *      somePromise.then(function (doesntMatter) {
         *          return myData;
         *      });
     *
     *  After:
     *      var myData = 'bob';
     *
     *      somePromise.then(ret(myData));
     */
    ret: function (data) {
        return function () {
            return data;
        };
    }
};


"use strict";

const resourceMap = new WeakMap();

/**
 * Attaches resource data to a given object. The resource data is usually used to store metadata (e. g. a content type) for an object.
 * @module resource
 * @param {!object|!function} object The object that should get the given resource data.
 * @param {!object} data The resource data that should be attached to `object`.
 * @return {!object|!function} The given object.
 */
function resource(object, data) {
	if(data === undefined)
		return resourceMap.get(object) || {};

	if(!object || typeof object !== "object" && typeof object !== "function" || resourceMap.has(object))
		throw new TypeError("Could not transform given object into a resource.");

	if(!data || typeof data !== "object")
		throw new TypeError("Resource data has to be an object.");

	resourceMap.set(object, data);

	return object;
}

module.exports = resource;


"use strict"
var Url = require('url')
var _ = require('underscore')

module.exports = function(ngin) {
  var SportsModel = ngin.SportsModel
  var Super = SportsModel.prototype
  var config = ngin.config

  /**
   * Scopes the url to the tournament or flight
   *
   * @param {Object} options
   * @returns {String}
   * @api public
   */

  function scopeUrl(options, inst) {
    options = _.extend(_.clone(options || {}), inst)
    if (typeof options !== 'object' && !options.flight_id)
      throw new Error('flight_id required to make flight defaults api calls')

    return ngin.Flight.urlRoot() + '/' + options.flight_id + FlightStage.urlRoot()
  }

  /**
   * FlightStage Class
   *
   * @param {Object} attr
   * @param {Object} options
   * @api public
   */

  var FlightStage = SportsModel.extend({

    fetch: function(options, callback) {
      var url = scopeUrl(options, this) + '/' + this.id
      return Super.fetch.call(this, url, options, callback)
    },

    save: function(options, callback) {
      var url = scopeUrl(options, this) + (this.id ? '/' + this.id : '')
      return Super.save.call(this, url, options, callback)
    },

    destroy: function(options, callback) {
      var url = scopeUrl(options, this) + '/' + this.id
      return Super.destroy.call(this, url, options, callback)
    },

    validate: function() {
      return ~['pool','single_elim','double_elim','round_robin','free'].indexOf(this.type) ? false : 'Property "type" has an invalid value'
    },

    addTeam: function(teamID, callback) {
      var url = scopeUrl({}, this) + '/' + this.id + '/add_team/' + teamID
      return FlightStage.sync('update', null, { url:url }, this.callbackWithParse(callback))
    },

    removeTeam: function(teamID, callback) {
      var url = scopeUrl({}, this) + '/' + this.id + '/remove_team/' + teamID
      return FlightStage.sync('delete', null, { url:url }, callback)
    },

    schedule: function(callback) {
      return ngin.GameSlot.list({flight_stage_id: this.id}, callback)
    },

    standings: function(callback) {
      return ngin.Standings.create({ flight_stage_id:this.id }).fetch(callback)
    },

    advancementSlots: function(callback) {
      var url = scopeUrl({}, this) + '/' + this.id + '/advancement_slots'
      return FlightStage.sync('fetch', null, { url:url }, this.callbackWithParse(callback))
    },

    teamsAdvancing: function(query, callback) {
      if (_.isFunction(query) && callback === undefined) {
        callback = query, query = undefined
      }
      var url = scopeUrl({}, this) + '/' + this.id + '/teams_advancing'
      return FlightStage.sync('fetch', null, { url:url, query: query }, this.callbackWithParse(callback))
    },

    // Old snake_case method
    teams_advancing: function() {
      console.warn('Code is using deprecated teams_advancing, switch to teamsAdvancing')
      var where = (new Error().stack || '').split('\n', 3)[2]
      if (where) console.warn(where)
      this.teamsAdvancing.apply(this, arguments)
    },

    advanceTeams: function(callback) {
      var url = scopeUrl({}, this) + '/' + this.id + '/teams_advancing'
      // please pardon the temporary ugliness until 1 pool 2 brackets is done
      // I simply can't assume next_stage_id exists yet
      var data = { teams:this.teams }
      if (this.next_stage_id) { data.next_stage_id = this.next_stage_id }
      return FlightStage.sync('create', data, { url:url }, this.callbackWithParse(callback)) // Stat Ngin expects a POST
    },

    // Old snake_case method
    advance_teams: function() {
      console.warn('Code is using deprecated advance_teams, switch to advanceTeams')
      var where = (new Error().stack || '').split('\n', 3)[2]
      if (where) console.warn(where)
      this.advanceTeams.apply(this, arguments)
    },

    brackets: function(callback) {
      return ngin.Bracket.list({ query: {flight_stage_id:this.id} }, callback)
    }

  }, {

    urlRoot: function() {
      return '/flight_stages'
    },

    list: function(options, callback) {
      var url = scopeUrl(options)
      return SportsModel.list.call(this, url, options, callback)
    }

  })

  return FlightStage

}


"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var errorIfNotInteger_1 = require("basic-data-handling/errorIfNotInteger");
var errorIfNotPrimitive_1 = require("basic-data-handling/errorIfNotPrimitive");
var error_if_not_populated_array_1 = require("error-if-not-populated-array");
// According to tests, this error-checking does not slow down execution. It's not
// necessary to worry about repetitive error-checking slowing down execution
// when this function is called inside a loop.
function getIndexOfPrimitive(primitive, array, startingPosition) {
    if (startingPosition === void 0) { startingPosition = 0; }
    errorIfNotPrimitive_1.errorIfNotPrimitive(primitive);
    error_if_not_populated_array_1.errorIfNotPopulatedArray(array);
    errorIfNotInteger_1.errorIfNotInteger(startingPosition);
    return array.indexOf(primitive, startingPosition);
}
exports.getIndexOfPrimitive = getIndexOfPrimitive;


'use strict';

var response = require('xhr-response')
  , statuscode = require('xhr-status')
  , one = require('one-time')
  , fail = require('failure');

/**
 * Simple nope function that assigned to XHR requests as part of a clean-up
 * operation.
 *
 * @api private
 */
function nope() {}

/**
 * Attach various of event listeners to a given XHR request.
 *
 * @param {XHR} xhr A XHR request that requires listening.
 * @param {EventEmitter} ee EventEmitter that receives events.
 * @api public
 */
function loads(xhr, ee) {
  var onreadystatechange
    , onprogress
    , ontimeout
    , onabort
    , onerror
    , onload
    , timer;

  /**
   * Error listener.
   *
   * @param {Event} evt Triggered error event.
   * @api private
   */
  onerror = xhr.onerror = one(function onerror(evt) {
    var status = statuscode(xhr)
      , err = fail(new Error('Network request failed'), status);

    ee.emit('error', err);
    ee.emit('end', err, status);
  });

  /**
   * Fix for FireFox's odd abort handling behaviour. When you press ESC on an
   * active request it triggers `error` instead of abort. The same is called
   * when an HTTP request is canceled onunload.
   *
   * @see https://bugzilla.mozilla.org/show_bug.cgi?id=768596
   * @see https://bugzilla.mozilla.org/show_bug.cgi?id=880200
   * @see https://code.google.com/p/chromium/issues/detail?id=153570
   * @param {Event} evt Triggerd abort event
   * @api private
   */
  onabort = xhr.onabort = function onabort(evt) {
    onerror(evt);
  };

  /**
   * ReadyStateChange listener.
   *
   * @param {Event} evt Triggered readyState change event.
   * @api private
   */
  onreadystatechange = xhr.onreadystatechange = function change(evt) {
    var target = evt.target;

    if (4 === target.readyState) return onload(evt);
  };

  /**
   * The connection has timed out.
   *
   * @api private
   */
  ontimeout = xhr.ontimeout = one(function timeout(evt) {
    ee.emit('timeout', evt);

    //
    // Make sure that the request is aborted when there is a timeout. If this
    // doesn't trigger an error, the next call will.
    //
    if (xhr.abort) xhr.abort();
    onerror(evt);
  });

  //
  // Fallback for implementations that did not ship with timer support yet.
  // Microsoft's XDomainRequest was one of the first to ship with `.timeout`
  // support so we all XHR implementations before that require a polyfill.
  //
  // @see https://bugzilla.mozilla.org/show_bug.cgi?id=525816
  //
  if (xhr.timeout) timer = setTimeout(ontimeout, +xhr.timeout);

  /**
   * IE needs have it's `onprogress` function assigned to a unique function. So,
   * no touchy touchy here!
   *
   * @param {Event} evt Triggered progress event.
   * @api private
   */
  onprogress = xhr.onprogress = function progress(evt) {
    var status = statuscode(xhr)
      , data;

    ee.emit('progress', evt, status);

    if (xhr.readyState >= 3 && status.code === 200 && (data = response(xhr))) {
      ee.emit('stream', data, status);
    }
  };

  /**
   * Handle load events an potential data events for when there was no streaming
   * data.
   *
   * @param {Event} evt Triggered load event.
   * @api private
   */
  onload = xhr.onload = one(function load(evt) {
    var status = statuscode(xhr)
      , data = response(xhr);

    if (status.code < 100 || status.code > 599) return onerror(evt);

    //
    // There is a bug in FireFox's XHR2 implementation where status code 204
    // triggers a "no element found" error and bad data. So to be save here,
    // we're just **never** going to emit a `stream` event as for 204's there
    // shouldn't be any content.
    //
    // @see https://bugzilla.mozilla.org/show_bug.cgi?id=521301
    //
    if (data && status.code !== 204) {
      ee.emit('stream', data, status);
    }

    ee.emit('end', undefined, status);
  });

  //
  // Properly clean up the previously assigned event listeners and timers to
  // prevent potential data leaks and unwanted `stream` events.
  //
  ee.once('end', function cleanup() {
    xhr.onreadystatechange = onreadystatechange =
    xhr.onprogress = onprogress =
    xhr.ontimeout = ontimeout =
    xhr.onerror = onerror =
    xhr.onabort = onabort =
    xhr.onload = onload = nope;

    if (timer) clearTimeout(timer);
  });

  return xhr;
}

//
// Expose all the things.
//
module.exports = loads;


var nest = require('postcss-nested')
var postcss = require('postcss')

module.exports = sheetifyNest

// SASS plugin for Sheetify
// (str, str, obj, fn) -> null
function sheetifyNest (filename, source, opts, done) {
  var processor = postcss([ nest(opts) ])
  processor.process(source)
    .then(function (res) {
      done(null, res.css)
    })
    .catch(done)
}


const url = require('url');
const querystring = require('querystring');
const _ = require('underscore');
const request = require('request');
const pack = require('../package.json');

module.exports = Cache;
/*
var cache = require('cachewatch');
var watch = cache();
watch.setToken('--My key--');

var opts = {
	// My Opts
};
app.use(['/MyUrl'], cache('My Key', opts).watch);
app.use(cache('My Key', opts).watch);
app.use(cache('My Key').watch);
app.use(watch.watch);
*/

function Cache (key, opt) {
	if (!(this instanceof Cache))
		return new Cache(key, opt);

	if(key)
		this.setToken(key);

	this.config = _.defaults(opt || {}, {
		protocol :'https',
		fragment : '_escaped_fragment_',
		service : 'service.cache.watch',
		path : '/',
		sender : {
		    method : 'get',
			headers: {
				'User-Agent': 'CacheWatch Client'
			},
		},
		auth :  'x-cache-watch',
		lengths : 150,
		useragent : pack.config.useragent,
		url : pack.config.extencion
	});

	var that = this;

	that.watch = function CacheWatch (req, res, next){
		var query = querystring.parse(url.parse(req.url).query);
		if(that.isNot(req, query) || !that.key)
			return next();

		that.get(that.createUrl(req, query), function (err, resp, body) {
			if(err || that.res(resp))
				return next();

			res.setHeader('CacheWatch', Cache.version);
			res.setHeader('last-modified', resp.headers['last-modified']);
			res.setHeader('content-type', resp.headers['content-type']);
			res.send(body);
		});
	};

}

Cache.prototype.res = function (res) {
	if( res.body.length <= this.config.lengths || /Unauthorized/gim.test(res.body) )
		return true;

	var reg = new RegExp(this.config.service, 'gim');

	if( reg.test(res.body) || res.headers['content-type'] != 'text/html' )
		return true;

	return false;
};

Cache.prototype.get = function (url, callback) {
    var sendes = _.clone(this.config.sender);
    sendes.uri = url;
    sendes.headers[this.config.auth] = this.key;
    request(sendes, callback);
};

Cache.prototype.getConfig = function (key) {
	return this.config[key];
};

Cache.prototype.setConfig = function (key, value) {
	this.config[key] = value;
	return this;
};

Cache.prototype.getToken = function (key) {
	return this.key;
};

Cache.prototype.setToken = function (value) {
	if(_.isEmpty(value) || !_.isString(value))
		return new Error('The key no exist');
	this.key = value;
	return this;
};

Cache.prototype.createUrl = function (req, query) {
	var urls = '://' + ( this.config.host || req.headers.host ) + req.url;
	var protocol = 'http';

	if ( req.headers['CF-Visitor'] ) {
		var match = req.headers['CF-Visitor'].match(/"scheme":"(http|https)"/);
		if (match) protocol = match[1];
	}

	if (req.headers['X-Forwarded-Proto'])
		protocol = req.headers['X-Forwarded-Proto'].split(',')[0];

	if( _.has(query, this.config.fragment) )
		urls += encodeURIComponent( '#' + query[ this.config.fragment ] );

	return url.format({
		protocol: this.config.protocol,
		host : this.config.service,
		pathname : this.config.path + protocol + urls
	});

	enc
};

Cache.prototype.isNot = function (req, query) {
	var userAgent = req.headers['user-agent'];

	if( req.method == 'GET' && _.has(query, this.config.fragment) )
		return false;

	if( _.find(this.config.useragent, function (item) {
		var reg = _.isRegExp(item) ? item : new RegExp(item, 'gim');
		return reg.test(userAgent);
	})) return false;

	if( _.find(this.config.url, function (item) {
		var reg = _.isRegExp(item) ? item : new RegExp(item, 'gim');
		return reg.test(req.url);
	})) return true;

	return true;
};

Cache.version = pack.version;


/**
 * Add a filename to the list with its dependencies first
 * @param {string[]} allFiles all filenames, for consistency checks
 * @param {string} newFile the new file
 * @param {object<string[]>} dependencies each property contains the dependencies for the file named like the key
 * @param {string[]} currentFiles the file that have already been added. This array is modified
 * @param {string[]} cycleCheck an internal stack of visited files
 */
function addWithDependencies (allFiles, newFile, dependencies, currentFiles, cycleCheck) {
  if (cycleCheck.indexOf(newFile) >= 0) {
    throw new Error('Dependency cycle found ' + JSON.stringify(cycleCheck))
  }
  cycleCheck.push(newFile)
  try {
    // Add dependencies first
    if (dependencies[newFile]) {
      dependencies[newFile].forEach(function (dependency) {
        if (allFiles.indexOf(dependency) < 0) {
          throw new Error('Dependency "' + dependency + '" of file "' + newFile + '" is not part of ' + JSON.stringify(allFiles))
        }
        addWithDependencies(allFiles, dependency, dependencies, currentFiles, cycleCheck)
      })
    }
    if (currentFiles.indexOf(newFile) < 0) {
      currentFiles.push(newFile)
    }
  } finally {
    cycleCheck.pop()
  }
  return currentFiles
}

/**
 * Order files by their dependencies (insert dependencies first)
 * @param {string[]} allFiles all filenames, for consistency checks
 * @param {object<string[]>} dependencies each property contains the dependencies for the file named like the key
 * @return {string[]}
 */
function orderFiles (allFiles, dependencies) {
  var result = []
  allFiles.forEach(function (newFile) {
    addWithDependencies(allFiles, newFile, dependencies, result, [])
  })
  return result
}

module.exports = orderFiles


/* Copyright 2015 Kyle E. Mitchell
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

module.exports = post

var concat = require('concat-stream')
var internalError = require('./internal-error')
var isChild = require('commonform-predicate').child
var merkleize = require('commonform-merkleize')
var stringify = require('commonform-serialize').stringify
var validate = require('commonform-validate')

function post(bole, level, callback, request, response) {
  request.pipe(concat(function(buffer) {
    parseJSON(buffer, function(error, form) {
      // Invalid JSON
      if (error) {
        bole.info({ issue: 'Invalid JSON', body: buffer.toString() })
        response.statusCode = 400
        response.end() }
      // Valid JSON
      else {
        var valid = validate.form(form)
        // Invalid Common Form
        if (!valid) {
          bole.info({ event: 'Invalid Common Form', body: form })
          response.statusCode = 400
          response.end() }
        // Valid Common Form
        else {
          bole.info({ event: 'Valid Form' })
          var merkle = merkleize(form)
          var digest = merkle.digest
          bole.info({
            digest: digest,
            children: ( Object.keys(merkle).length - 2 ) })
          level.exists(digest, function(error, exists) {
            /* istanbul ignore if */
            if (error) {
              bole.error(error)
              internalError(response) }
            else {
              var location = ( '/forms/' + digest )
              // If the form already exists, just respond 201, and don't POST
              // to callbacks.
              if (exists) {
                bole.info({ event: 'Common Form Already Exists' })
                response.statusCode = 200
                response.setHeader('Location', location)
                response.end() }
              // If the form is a new one, write to LevelUp and POST the root
              // digest to callbacks.
              else {
                bole.info({ event: 'New Form', form: form })
                var batch = level.batch()
                batchForms(batch, form, merkle)
                batch.write(function(error) {
                  /* istanbul ignore if */
                  if (error) {
                    bole.error(error)
                    internalError(response) }
                  else {
                    bole.info({ event: 'Stored' })
                    response.statusCode = 201
                    response.setHeader('Location', location)
                    response.end()
                    bole.info({ event: 'Sending Callbacks' })
                    callback.send(function(stream) {
                      stream.end(digest) }) } }) } } }) } } }) })) }

// Given a new LevelUp chain-style batch, a denormalized Common Form, and the
// output of commonform-merkleize for that Common Form, add LevelUp put
// operations to the batch for the Common Form and each of its children.
function batchForms(batch, form, merkle) {
  // Use commonform-stringify to produce the text to be stored.
  var stringified = stringify(form)
  var digest = merkle.digest
  batch.put(digest, stringified)
  // Recurse children.
  form.content
    .forEach(function(element, index) {
      if (isChild(element)) {
        var childForm = element.form
        var childMerkle = merkle.content[index]
        batchForms(batch, childForm, childMerkle) } }) }

// JSON.parse, wrapped to take an errback.
function parseJSON(input, callback) {
  var error
  var result
  try {
    result = JSON.parse(input) }
  catch (e) {
    error = e }
  callback(error, result) }


var fs = require('fs');
var Utils = require('../core');
var mkdirp = require('mkdirp');

/* Utils.create.folder();
 * Utils.create.file();
 * Create folder if needed
 * Then create file */

function createFile (filePath, fileContent) {
	var folderPath = filePath.split('/');
	folderPath.pop();
	folderPath = folderPath.join('/');

	if (!Utils.exist(folderPath))
		createFolder(folderPath);

	fs.writeFileSync(filePath, fileContent);
	Utils.log('Created file: ' + filePath);

	return Utils;
}

function createFolder (obj) {
	if (Utils.tools.isArray(obj))
		obj.forEach(newFolder);
	else
		newFolder(obj);

	return Utils;
}

function newFolder (folderPath) {
	if (Utils.exist(folderPath))
		return false;

	mkdirp.sync(folderPath);
	return Utils.log('Created folder: ' + folderPath);
}

module.exports = {
	folder: createFolder,
	file: createFile
};


"use strict"
var Url = require('url')
var _ = require('underscore')

module.exports = function(ngin) {
  var SportsModel = ngin.SportsModel
  var Super = SportsModel.prototype
  var config = ngin.config

  /**
   * Builds the url based on crazy
   *
   * @param {Object} options
   * @param {Object} inst
   * @returns {String}
   * @api public
   */

  function scopeUrl(options, inst) {
    options = _.extend(_.clone(options || {}), inst)
    var route = []

    if (options.season_id) route.push('seasons', options.season_id)
    if (options.flight_stage_id) route.push('flight_stages', options.flight_stage_id)
    else if (options.division_id) route.push('divisions', options.division_id)
    else if (options.pool_id) route.push('pools', options.pool_id)
    route.push('standings')

    var base = config.urls && config.urls.sports || config.url
    return Url.resolve(base, route.join('/'))
  }

  /**
   * Standings Class
   *
   * @param {Object} attr
   * @param {Object} options
   * @api public
   */

  var Standings = SportsModel.extend({

    fetch: function(options, callback) {
      var url = scopeUrl(options, this)
      return Super.fetch.call(this, url, options, callback)
    }

  })

  return Standings

}


/** @module helper/formatValue */

import d3 from 'd3';

/**
 * Format number
 * @param {object} value - value
 * @param {string} type - si | scientific | rounded | raw
 */
function formatNum(value, type) {
  const conv = {
    scientific: ".3e",
    si: ".3s",
    rounded: ".3r"
  };
  if (type === 'raw') return value;
  if (value === undefined || value === null || Number.isNaN(value)) return '';
  return value == parseFloat(value) ? d3.format(conv[type])(value) : value;
}

function partialMatch(query, target) {
  if (target === undefined || target === null || target === '') return false;
  return target.toString().toUpperCase()
    .indexOf(query.toString().toUpperCase()) !== -1;
}

function numericAsc(a, b) {
  const fa = parseFloat(a);
  const fb = parseFloat(b);
  if (isNaN(fa) || isNaN(fb)) {
    return String(b).localeCompare(String(a));
  }
  return fb - fa;
}

function numericDesc(a, b) {
  return numericAsc(b, a);
}

function textAsc(a, b) {
  return String(b).localeCompare(String(a));
}

function textDesc(a, b) {
  return textAsc(b, a);
}

export default {
  formatNum, partialMatch,
  numericAsc, numericDesc, textAsc, textDesc
};


(function (window) {
  'use strict';

  var genUniqueId = (function () {
    var counter = 1;
    return function () {
      return counter++;
    };
  })();

  function noop() {}

  // Extend WebSocket to automatically reconnect
  function ReconnectingWebSocket(url, opts) {
    if (!(this instanceof ReconnectingWebSocket)) {
      throw new TypeError('Cannot call a constructor as a function');
    }

    opts = opts || {};

    var self = this;
    function getOpt(name, def) {
      return opts.hasOwnProperty(name)? opts[name] : def;
    }
    var timeout = getOpt('timeout', 100);
    var maxRetries = getOpt('maxRetries', 5);
    var curRetries = 0;

    // External event callbacks
    self.onmessage = noop;
    self.onopen = noop;
    self.onclose = noop;

    function unreliableOnOpen(e) {
      self.onopen(e);
      curRetries = 0;
    }

    function unreliableOnClose(e) {
      self.onclose(e);

      if (curRetries < maxRetries) {
        ++curRetries;
        setTimeout(connect, timeout);
      }
    }

    function unreliableOnMessage(e) {
      self.onmessage(e);
    }

    function connect() {
      // Constructing a WebSocket() with opts.protocols === undefined
      // does NOT behave the same as calling it with only one argument
      // (specifically, it throws security errors).
      if (opts.protocols) {
        self.ws = new WebSocket(url, opts.protocols);
      } else {
        self.ws = new WebSocket(url);
      }

      // onerror isn't necessary: it is always accompanied by onclose
      self.ws.onopen = unreliableOnOpen;
      self.ws.onclose = unreliableOnClose;
      self.ws.onmessage = unreliableOnMessage;
    }

    connect();
    this.connect = connect;
  }

  ReconnectingWebSocket.prototype.send = function send(data) {
    this.ws.send(data);
  };

  function Socket(url, opts) {
    if (!(this instanceof Socket)) {
      throw new TypeError('Cannot call a constructor as a function');
    }

    if (typeof url === 'string') {
      this.url = url;
    } else {
      throw new TypeError('Socket: first arg must be a string');
    }

    if (typeof opts !== 'object' || typeof opts.extractor !== 'function') {
      throw new TypeError('Socket: opts must supply an extractor');
    }

    opts = opts || {};
    var extractor = opts.extractor;

    var self = this;

    // External event callbacks
    this.onopen = noop;
    this.onclose = noop;
    this.onmessage = noop;

    this.conn = new ReconnectingWebSocket(url, opts);
    this.conn.onopen = function () {
      self.onopen();
    };
    this.conn.onclose = function () {
      self.onclose();
    };

    this.connect = function () {
      self.conn.connect();
    };

    this.replyHandlers = Object.create(null);
    this.eventHandlers = Object.create(null);

    this.conn.onmessage = function (e) {
      var json, callbacks;

      try {
        json = JSON.parse(e.data);
      }
      catch(SyntaxError) {
        console.log('Syntax error in received data');
        return;
      }

      if (json.data === undefined) {
        console.log('No data field in received data');
        return;
      }

      var id = parseInt(json.id, 10);
      if (id > -1) {
        // Handle response messages
        id = json.id;
        var callback = self.replyHandlers[id];

        if (callback) {
          callback(json.data);
          delete self.replyHandlers[id];
        }
      } else {
        var name = extractor(json.data);

        // Note: messages from the server are an extra layer deep
        // (so .data.data instead of the single .data of responses)
        var data = (json.data.data);
        self.onmessage(name, data);

        callbacks = self.eventHandlers[name];
        if (callbacks !== undefined) {
          callbacks.forEach(function (f) {
            f(data);
          });
        }
      }
    };
  }

  Socket.prototype.On = function On(e, callback) {
    if (!Object.hasOwnProperty.call(this.eventHandlers, e)) {
      this.eventHandlers[e] = [];
    }

    this.eventHandlers[e].push(callback);
  };

  Socket.prototype.on = function On(e, callback) {
    this.On(e, callback);
  };

  Socket.prototype.Off = function Off(e, callback) {
    if (!Object.hasOwnProperty.call(this.eventHandlers, e)) {
      return;
    }

    this.eventHandlers[e] =
      this.eventHandlers[e].filter(function (thisCallback) {
        return thisCallback !== callback;
      });
  };

  Socket.prototype.off = function off(e, callback) {
    this.Off(e, callback);
  };

  Socket.prototype.Emit = function Emit(data, callback) {
    var id = genUniqueId();
    var msg = {
      id: '' + id,
      data: data
    };

    this.replyHandlers[id] = callback;
    this.conn.send(JSON.stringify(msg));
  };

  Socket.prototype.emit = function emit(data, callback) {
    this.Emit(data, callback);
  };

  window.Socket = Socket;
})(this);


"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var isArray_notArray_1 = require("basic-data-handling/isArray_notArray");
var error_if_not_populated_array_1 = require("error-if-not-populated-array");
var arrays_match_1 = require("@writetome51/arrays-match");
var errorIfNotArray_1 = require("basic-data-handling/errorIfNotArray");
// returns -1 if array being searched for isn't found.
// if arrayToSearchFor contains objects, this will always return -1.
function getFirstIndexOfArray(arrayToSearchFor, arrayToSearchInside) {
    errorIfNotArray_1.errorIfNotArray(arrayToSearchFor);
    error_if_not_populated_array_1.errorIfNotPopulatedArray(arrayToSearchInside);
    return arrayToSearchInside.findIndex(function (value) {
        return (isArray_notArray_1.isArray(value) && arrays_match_1.arraysMatch(value, arrayToSearchFor));
    });
}
exports.getFirstIndexOfArray = getFirstIndexOfArray;


var _isNumber = require('./isNumber');
var _isObject = require('./isObject');
var REGEX_ENUM = require('./internal/enum/regexEnum');

/**
 * 校验参数 `val` 是否为 正整数
 *
 * 该接口存在两种模式, 即严格模式、非严格模式;
 *
 * 默认采用 `非严格模式`
 *
 * 若参数 `val` 为 number 类型, 并且是正整数 则返回 true, 否则返回 false
 * 若参数 `val` 为 string 类型, 并且通过验证为正整数字符串, 则返回 true, 否则返回 false
 * 若参数 `val` 不属于以上两种类型, 则直接返回 false
 *
 * 若参数 `val` 的值为空字符串（''、'   '）, 则直接返回 false
 *
 * 若是参数 `options` 指定了严格模式, 即 options.isStrict = true, 则对于 string 类型直接返回 false
 *
 * e.g.
 *
 * 待校验参数 '3'
 *  > 若是严格模式, 则该参数值校验后, 返回 false
 *  > 若是非严格模式, 则该参数值校验后, 返回 true
 *
 * @param   {*}       val              待校验的参数
 * @param   {Object}  options          可选参数
 * @param   {Boolean} options.isStrict 是否严格模式
 * @return  {Boolean} 返回校验结果
 * @version 0.0.5
 * @since   0.0.4
 */
function _isPositiveInteger(val, options) {
  var opts = _isObject(options) ? options : {};

  if (opts.isStrict === true) {
    return _isNumber(val) && REGEX_ENUM.POSITIVE_INTEGER_REX.test(val);
  }

  return REGEX_ENUM.POSITIVE_INTEGER_REX.test(val);
}

module.exports = _isPositiveInteger;


"use strict";

const ClientApi = require("./ClientApi");

/**
 * Creates a {@link ClientApi} for the given protocol handler.
 * @module client
 * @param {object} protocol The protocol that should be wrapped.
 * @param {function} [protocol.init] Called after the {@link ClientApi} that will be returned is prepared. Gets the Protocol object it was assigned to as its context.
 * @param {function} protocol.closer Called if a route is closed by the used. It gets the route array as its first parameter and the close data as its second. The protocol object the closer was assigned to is used as the context.
 * @return {ClientApi} A ClientApi for protocol.
 */
function client(protocol) {
	if(!protocol || typeof protocol !== "object")
		throw new TypeError("owe ClientApi protocols have to be objects.");

	if(typeof protocol.closer !== "function")
		throw new TypeError("owe ClientApi proctols have to offer a closer function.");

	if(protocol.init && typeof protocol.init !== "function")
		throw new TypeError("owe ClientApi protocols have to offer an init function.");

	let connected = false;
	const observers = new Set();

	protocol = Object.assign({
		get connected() {
			return connected;
		},
		set connected(value) {
			if(typeof value !== "boolean")
				throw new TypeError("Protocol connection state has to be boolean.");

			if(value === connected)
				return;

			connected = value;
			for(const observer of observers)
				observer(connected);
		},
		observe(observer) {
			if(typeof observer !== "function")
				throw new TypeError("Protocol connection state observers have to be functions.");

			observers.add(observer);
		},
		unobserve(observer) {
			observers.delete(observer);
		}
	}, protocol);

	if(protocol.init)
		protocol.init();

	return new ClientApi(protocol);
}

Object.assign(client, {
	/**
	 * Returns whether the given object is a {@link ClientApi} instance.
	 * @memberof module:client
	 * @param {any} api The api to check. This can be any value. The method will always return false for non-objects.
	 * @return {boolean} `true` if `api` is a {@link ClientApi}. `false` if not.
	 */
	isApi(api) {
		return api && typeof api === "object" && api instanceof ClientApi || false;
	}
});

module.exports = client;


'use strict';

let disk = {};

/**
 * Recursively creates a file
 * @param parentDir
 * @param path
 * @param data
 */
function writePath(parentDir, path, data) {
    let files = path.split('/');
    let file = files.shift();

    // init file
    if(!parentDir[file]) parentDir[file] = {
        content: undefined,
        tree: {}
    };

    if(files.length > 0) {
        writePath(parentDir[file].tree, files.join('/'), data);
    } else if(data) {
        parentDir[file].content = data.toString();
    }
}

function writeFileSync(path, contents) {
    contents = contents || null;
    writePath(disk, path, contents);
}

function readPath(parentDir, path) {
    path = path.replace(/\/+/g, '/').replace(/\/+$/, '');
    let files = path.split('/');
    let file = files.shift();

    if(!parentDir[file]) throw new Error(path);

    if(files.length > 0) {
        try {
            return readPath(parentDir[file].tree, files.join('/'));
        } catch(err) {
            throw new Error('The file does not exist: ' + path);
        }
    } else {
        return parentDir[file];
    }
}

module.exports = {
    get __disk() {
        return disk;
    },
    writeFileSync: jest.fn(writeFileSync),
    writeFile: jest.fn(function(path, contents, callback) {
        setTimeout(function() {
            writeFileSync(path, contents);
            callback(null);
        }, 0);
    }),
    mkdirSync: jest.fn(function(path, opts) {
        // This is cheating because mkdirSync does not work recursively.
        writePath(disk, path, null);
    }),
    readFileSync: jest.fn(function(path, opts) {
        return readPath(disk, path).content;
    }),
    readdirSync: jest.fn(function(dir) {
        let tree = readPath(disk, dir).tree;
        let files = [];
        for(let key of Object.keys(tree)) {
            files.push(dir.replace(/\/+$/, '') + '/' + key);
        }
        return files;
    }),
    statSync: jest.fn(function(path) {
        let contents = null;
        contents = readPath(disk, path).content;
        return {
            isFile: jest.fn(function() {
                return contents !== null && contents !== undefined;
            }),
            isDirectory: jest.fn(function() {
                return contents === null || contents === undefined;
            })
        };
    })
};


/**
 * bower_concat.js - config for grunt-bower-concat
 */

module.exports = function ( grunt ) {

    var paths   = grunt.fcoo.paths;

    return {
        options: {
            separator : grunt.util.linefeed + ';' + grunt.util.linefeed
        },
        all: {
            dest: {
                'js' : paths.temp_dist + paths._bower_components_js,
                'css': paths.temp_dist + paths._bower_components_css
            },

            dependencies: {}, //Is set by the task "_read_overrides_and_resolutions"
            exclude     : {}, //Is set by the task "_read_overrides_and_resolutions"
            mainFiles   : {}, //Is set by the task "_read_overrides_and_resolutions"

            callback: function(mainFiles /*, component*/) {
                for (var i=0; i<mainFiles.length; i++ ){
                    //Use no-minified version if available
                    var parts = mainFiles[i].split('.'),
                        ext   = parts.pop(),
                        min   = parts.pop(),
                        fName;
                    if (min == 'min'){
                      parts.push(ext);
                        fName = parts.join('.');
                        if (grunt.file.exists(fName))
                            mainFiles[i] = fName;
                    }
                }
                return mainFiles;
            }
        }
    }
}


const deduce = require('./deduce')

class RoleFinder {
  constructor () {
    // roles of the form: If an object with role X has field Y which
    //  is an object, that object has role R
    this.childRoles = mapOfMaps()

    // roles of the form: If an object with role X has field Y which
    //  is an object/array, the object-files/array-elements have role R
    this.grandchildRoles = mapOfMaps()

    // roles of the form: If an object with role X has a field Y with
    //  value Z, it has role R
    // map[role][field_name][field_val] => childRole
    this.fieldValueRoles = mapOfMaps()

    // roles of the form: If an object with role X has a field Y, then
    //  it has role R
    // map[role][field_name] => childRole
    this.fieldPresenceRoles = mapOfMaps()

    // roles of the form: A Foo is a Bar
    this.isARoles = mapOfArrays()
  } // constructor

  addIsARole (role, otherRole) {
    this.isARoles.getOrCreate(role).push(otherRole)
  } // addIsARole

  addFieldValueRole (role, fieldName, fieldValue, newRole) {
    fieldValue = deduce(fieldValue)
    this.fieldValueRoles.getOrCreate(role).getOrCreate(fieldName).set(fieldValue, newRole)
  } // addFieldValueRole

  addFieldPresenceRole (role, fieldName, newRole) {
    this.fieldPresenceRoles.getOrCreate(role).set(fieldName, newRole)
  } // addFieldPresenceRole

  addChildRole (role, fieldName, childRole) {
    this.childRoles.getOrCreate(role).set(fieldName, childRole)
  } // addChildRole

  addGrandchildRole (role, fieldName, grandchildRole) {
    this.grandchildRoles.getOrCreate(role).set(fieldName, grandchildRole)
  } // addGrandchildRole

  findMoreRoles (node, roles) {
    // roles depending on field values
    roles.forEach(role => {
      const perFieldName = this.fieldValueRoles.get(role)
      if (perFieldName) {
        perFieldName.forEach((valueRoles, fieldName) => {
          valueRoles.forEach((childRole, fieldValue) => {
            if (fieldValue === node[fieldName]) {
              roles.push(childRole)
            }
          })
        })
      }
    })

    // roles depending on field presence
    const nodeKeys = Object.keys(node)
    roles.forEach(role => {
      const perFieldName = this.fieldPresenceRoles.get(role)
      if (perFieldName) {
        perFieldName.forEach((childRole, fieldName) => {
          if (nodeKeys.includes(fieldName)) roles.push(childRole)
        })
      }
    })

    // is_a roles
    roles.forEach(role => {
      const otherRoles = this.isARoles.get(role)
      if (otherRoles) {
        roles.push(...otherRoles)
      }
    })
  } // findMoreRoles

  findChildRoles (roles, fieldName) {
    return descendantRoles(roles, fieldName, this.childRoles)
  } // findChildRoles

  // A node has a role, and one of its field is an object or an
  // array whose fields or elements are given a role
  findGrandchildRoles (roles, fieldName) {
    return descendantRoles(roles, fieldName, this.grandchildRoles)
  } // findGrandchildRoles

  toString () {
    let msg = 'Roles:'

    for (const [role, isA] of this.isARoles.entries()) {
      msg += `\n  ${role} is a ${isA.join()}`
    }

    return msg
  }
} // class RoleFinder

function descendantRoles (roles, fieldName, allDescendants) {
  return roles
    .map(role => allDescendants.get(role))
    .filter(descendants => !!descendants)
    .map(descendants => descendants.get(fieldName))
    .filter(fieldRoles => !!fieldRoles)
} // descendantRoles

function mapOfMaps () {
  const m = new Map()
  m.getOrCreate = function (key) {
    if (!this.has(key)) this.set(key, mapOfMaps())
    return this.get(key)
  }
  return m
} // mapOfMaps

function mapOfArrays () {
  const m = new Map()
  m.getOrCreate = function (key) {
    if (!this.has(key)) this.set(key, [])
    return this.get(key)
  }
  return m
} // mapOfArrays

module.exports = roleFinder => new RoleFinder()