import { expect } from 'chai';
import Module from './_build/JsMaterialXCore.js';

describe('Traversal', () =>
{
    let mx;
    before(async () =>
    {
        mx = await Module();
    });

    it('Traverse Graph', () =>
    {
        // Create a document.
        const doc = mx.createDocument();
        // Create a node graph with the following structure:
        //
        // [image1] [constant]     [image2]
        //        \ /                 |
        //    [multiply]          [contrast]         [noise3d]
        //             \____________  |  ____________/
        //                          [mix]
        //                            |
        //                         [output]
        //
        const nodeGraph = doc.addNodeGraph();
        const image1 = nodeGraph.addNode('image');
        const image2 = nodeGraph.addNode('image');
        const constant = nodeGraph.addNode('constant');
        const multiply = nodeGraph.addNode('multiply');
        const contrast = nodeGraph.addNode('contrast');
        const noise3d = nodeGraph.addNode('noise3d');
        const mix = nodeGraph.addNode('mix');
        const output = nodeGraph.addOutput();
        multiply.setConnectedNode('in1', image1);
        multiply.setConnectedNode('in2', constant);
        contrast.setConnectedNode('in', image2);
        mix.setConnectedNode('fg', multiply);
        mix.setConnectedNode('bg', contrast);
        mix.setConnectedNode('mask', noise3d);
        output.setConnectedNode(mix);

        expect(doc.validate()).to.be.true;

        // Traverse the document tree (implicit iterator).
        let nodeCount = 0;
        for (let elem of doc.traverseTree())
        {
            if (elem instanceof mx.Node)
            {
                nodeCount++;
            }
            elem.delete();
        }
        expect(nodeCount).to.equal(7);

        // Traverse the document tree (explicit iterator)
        let treeIter = doc.traverseTree();
        nodeCount = 0;
        let maxElementDepth = 0;
        for (let elem of treeIter)
        {
            if (elem instanceof mx.Node)
            {
                nodeCount++;
            }
            maxElementDepth = Math.max(maxElementDepth, treeIter.getElementDepth());
            elem.delete();
        }
        expect(nodeCount).to.equal(7);
        expect(maxElementDepth).to.equal(3);

        // Traverse the document tree (prune subtree).
        nodeCount = 0;
        treeIter = doc.traverseTree();
        for (let elem of treeIter)
        {
            if (elem instanceof mx.Node)
            {
                nodeCount++;
            }
            if (elem instanceof mx.NodeGraph)
            {
                treeIter.setPruneSubtree(true);
            }
            elem.delete();
        }
        expect(nodeCount).to.equal(0);

        // Traverse upstream from the graph output (implicit iterator)
        nodeCount = 0;
        for (let edge of output.traverseGraph())
        {
            const upstreamElem = edge.getUpstreamElement();
            const connectingElem = edge.getConnectingElement();
            const downstreamElem = edge.getDownstreamElement();
            if (upstreamElem instanceof mx.Node)
            {
                nodeCount++;
                if (downstreamElem instanceof mx.Node)
                {
                    expect(connectingElem instanceof mx.Input).to.be.true;
                }
            }
            if (upstreamElem) upstreamElem.delete();
            if (connectingElem) connectingElem.delete();
            if (downstreamElem) downstreamElem.delete();
            if (edge) edge.delete();
        }
        expect(nodeCount).to.equal(7);

        // Traverse upstream from the graph output (explicit iterator)
        nodeCount = 0;
        maxElementDepth = 0;
        let maxNodeDepth = 0;
        let graphIter = output.traverseGraph();
        for (let edge of graphIter)
        {
            const upstreamElem = edge.getUpstreamElement();
            if (upstreamElem instanceof mx.Node)
            {
                nodeCount++;
            }
            maxElementDepth = Math.max(maxElementDepth, graphIter.getElementDepth());
            maxNodeDepth = Math.max(maxNodeDepth, graphIter.getNodeDepth());
            if (upstreamElem) upstreamElem.delete();
            if (edge) edge.delete();
        }
        expect(nodeCount).to.equal(7);
        expect(maxElementDepth).to.equal(3);
        expect(maxNodeDepth).to.equal(3);

        // Traverse upstream from the graph output (prune subgraph)
        nodeCount = 0;
        graphIter = output.traverseGraph();
        for (let edge of graphIter)
        {
            const upstreamElem = edge.getUpstreamElement();
            expect(upstreamElem.getSelf()).to.be.an.instanceof(mx.Element);
            if (upstreamElem instanceof mx.Node)
            {
                nodeCount++;
            }
            if (upstreamElem.getCategory() === 'multiply')
            {
                graphIter.setPruneSubgraph(true);
            }
            if (upstreamElem) upstreamElem.delete();
            if (edge) edge.delete();
        }
        expect(nodeCount).to.equal(5);

        // Create and detect a cycle
        multiply.setConnectedNode('in2', mix);
        expect(output.hasUpstreamCycle()).to.be.true;
        expect(doc.validate()).to.be.false;
        multiply.setConnectedNode('in2', constant);
        expect(output.hasUpstreamCycle()).to.be.false;
        expect(doc.validate()).to.be.true;

        // Create and detect a loop
        contrast.setConnectedNode('in', contrast);
        expect(output.hasUpstreamCycle()).to.be.true;
        expect(doc.validate()).to.be.false;
        contrast.setConnectedNode('in', image2);
        expect(output.hasUpstreamCycle()).to.be.false;
        expect(doc.validate()).to.be.true;

        // Cleanup wrappers
        output.delete();
        mix.delete();
        noise3d.delete();
        contrast.delete();
        multiply.delete();
        constant.delete();
        image2.delete();
        image1.delete();
        nodeGraph.delete();
        doc.delete();
    });

    describe("Traverse inheritance", () =>
    {
        let nodeDefInheritanceLevel2, nodeDefInheritanceLevel1, nodeDefParent;
        let doc;
        beforeEach(() =>
        {
            doc = mx.createDocument();
            nodeDefParent = doc.addNodeDef();
            nodeDefParent.setName('BaseClass');
            nodeDefInheritanceLevel1 = doc.addNodeDef();
            nodeDefInheritanceLevel1.setName('InheritanceLevel1');
            nodeDefInheritanceLevel2 = doc.addNodeDef();
            nodeDefInheritanceLevel2.setName('InheritanceLevel2');
            nodeDefInheritanceLevel2.setInheritsFrom(nodeDefInheritanceLevel1);
            nodeDefInheritanceLevel1.setInheritsFrom(nodeDefParent);
        });
        afterEach(() =>
        {
            nodeDefInheritanceLevel2.delete();
            nodeDefInheritanceLevel1.delete();
            nodeDefParent.delete();
            doc.delete();
        });

        it('for of loop', () =>
        {
            const inheritanceIterator = nodeDefInheritanceLevel2.traverseInheritance();
            let inheritanceChainLength = 0;
            for (const elem of inheritanceIterator)
            {
                if (elem instanceof mx.NodeDef)
                {
                    inheritanceChainLength++;
                }
            }
            expect(inheritanceChainLength).to.equal(2);;
        });

        it('while loop', () =>
        {
            const inheritanceIterator = nodeDefInheritanceLevel2.traverseInheritance();
            let inheritanceChainLength = 0;
            let elem = inheritanceIterator.next();
            while (!elem.done)
            {
                if (elem.value instanceof mx.NodeDef)
                {
                    inheritanceChainLength++;
                }
                elem = inheritanceIterator.next();
            }
            expect(inheritanceChainLength).to.equal(2);;
        });
    });
});
