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

WI.HeapSnapshotClassDataGridNode = class HeapSnapshotClassDataGridNode extends WI.DataGridNode
{
    constructor(data, tree)
    {
        super(data, {hasChildren: true});

        this._data = data;
        this._tree = tree;

        this._batched = false;
        this._instances = null;

        this.addEventListener(WI.DataGridNode.Event.Populate, this._populate, this);
    }

    // Protected

    get data() { return this._data; }

    createCellContent(columnIdentifier)
    {
        if (columnIdentifier === "retainedSize") {
            let size = this._data.retainedSize;
            let fragment = document.createDocumentFragment();
            let sizeElement = fragment.appendChild(document.createElement("span"));
            sizeElement.classList.add("size");
            sizeElement.textContent = Number.bytesToString(size);
            let percentElement = fragment.appendChild(document.createElement("span"));
            percentElement.classList.add("percentage");
            percentElement.textContent = emDash;
            return fragment;
        }

        if (columnIdentifier === "size")
            return Number.bytesToString(this._data.size);

        if (columnIdentifier === "className") {
            const internal = false;
            let {className, isObjectSubcategory, isElementSubcategory} = this._data;
            let fragment = document.createDocumentFragment();
            let iconElement = fragment.appendChild(document.createElement("img"));
            iconElement.classList.add("icon", WI.HeapSnapshotClusterContentView.iconStyleClassNameForClassName(className, internal, isObjectSubcategory, isElementSubcategory));
            let nameElement = fragment.appendChild(document.createElement("span"));
            nameElement.classList.add("class-name");
            nameElement.textContent = className;
            return fragment;
        }

        return super.createCellContent(columnIdentifier);
    }

    sort()
    {
        if (this._batched) {
            this._removeFetchMoreDataGridNode();
            this._sortInstances();
            this._updateBatchedChildren();
            this._appendFetchMoreDataGridNode();
            return;
        }

        let children = this.children;
        children.sort(this._tree._sortComparator);

        for (let i = 0; i < children.length; ++i) {
            children[i]._recalculateSiblings(i);
            children[i].sort();
        }
    }

    removeCollectedNodes(collectedNodes)
    {
        let nodesToRemove = [];

        this.forEachImmediateChild((dataGridNode) => {
            if (dataGridNode instanceof WI.HeapSnapshotInstanceDataGridNode) {
                let heapSnapshotNode = dataGridNode.node;
                if (heapSnapshotNode.id in collectedNodes)
                    nodesToRemove.push(dataGridNode);
            }
        });

        if (nodesToRemove.length) {
            for (let dataGridNode of nodesToRemove)
                this.removeChild(dataGridNode);
        }

        if (this._instances) {
            this._instances = this._instances.filter((instance) => !(instance.id in collectedNodes));
            this._fetchBatch(nodesToRemove.length);
        }
    }

    updateCount(count)
    {
        if (count === this._data.count)
            return;

        if (!count) {
            this._tree.removeChild(this);
            return;
        }

        this._data.count = count;
        this.needsRefresh();
    }

    // Private

    _populate()
    {
        this.removeEventListener(WI.DataGridNode.Event.Populate, this._populate, this);

        this._tree.heapSnapshot.instancesWithClassName(this._data.className, (instances) => {
            // FIXME: <https://webkit.org/b/157905> Web Inspector: Provide a way to toggle between showing only live objects and live+dead objects
            this._instances = instances.filter((instance) => !instance.dead);
            this._sortInstances();

            // Batch.
            if (instances.length > WI.HeapSnapshotClassDataGridNode.ChildrenBatchLimit) {
                this._batched = true;
                this._fetchBatch(WI.HeapSnapshotClassDataGridNode.ChildrenBatchLimit);
                return;
            }

            for (let instance of this._instances)
                this.appendChild(new WI.HeapSnapshotInstanceDataGridNode(instance, this._tree));
        });
    }

    _fetchBatch(batchSize)
    {
        if (this._batched && this.children.length)
            this._removeFetchMoreDataGridNode();

        let oldCount = this.children.length;
        let newCount = oldCount + batchSize;

        if (newCount >= this._instances.length) {
            newCount = this._instances.length - 1;
            this._batched = false;
        }

        let count = newCount - oldCount;
        if (count) {
            for (let i = 0; i <= count; ++i) {
                let instance = this._instances[oldCount + i];
                this.appendChild(new WI.HeapSnapshotInstanceDataGridNode(instance, this._tree));
            }
        }

        if (this._batched)
            this._appendFetchMoreDataGridNode();
    }

    _sortInstances()
    {
        this._instances.sort((a, b) => {
            let fakeDataGridNodeA = {data: a};
            let fakeDataGridNodeB = {data: b};
            return this._tree._sortComparator(fakeDataGridNodeA, fakeDataGridNodeB);
        });
    }

    _updateBatchedChildren()
    {
        let count = this.children.length;

        this.removeChildren();

        for (let i = 0; i < count; ++i)
            this.appendChild(new WI.HeapSnapshotInstanceDataGridNode(this._instances[i], this._tree));
    }

    _removeFetchMoreDataGridNode()
    {
        console.assert(this.children[this.children.length - 1] instanceof WI.HeapSnapshotInstanceFetchMoreDataGridNode);

        this.removeChild(this.children[this.children.length - 1]);
    }

    _appendFetchMoreDataGridNode()
    {
        console.assert(!(this.children[this.children.length - 1] instanceof WI.HeapSnapshotInstanceFetchMoreDataGridNode));

        let count = this.children.length;
        let totalCount = this._instances.length;
        let remainingCount = totalCount - count;
        let batchSize = remainingCount >= WI.HeapSnapshotClassDataGridNode.ChildrenBatchLimit ? WI.HeapSnapshotClassDataGridNode.ChildrenBatchLimit : 0;

        this.appendChild(new WI.HeapSnapshotInstanceFetchMoreDataGridNode(this._tree, batchSize, remainingCount, this._fetchBatch.bind(this)));
    }
};

WI.HeapSnapshotClassDataGridNode.ChildrenBatchLimit = 100;
