// jshint ignore: start

largeModule('Node', SetupEngine);

test('basic test', function () {
    var node = new cc.Node();
    ok(node.name, 'node has default name');
    strictEqual(new cc.Node('myNode').name, 'myNode', 'can specify node name');

    strictEqual(node.active, true, 'node is active');

    node.active = false;

    strictEqual(node.active, false, 'node is deactive');

    node._destroyImmediate();
    strictEqual(node.isValid, false, 'node can be destroyed');
});

test('test hierarchy', function () {
    var parent = new cc.Node();
    var child1 = new cc.Node();
    var child2 = new cc.Node();

    strictEqual(parent.parent, null, 'node\'s default parent is null');
    strictEqual(parent.childrenCount, 0, 'node\'s default child count is 0');

    child1.parent = parent;
    ok(child1.parent === parent, 'can get/set parent');
    strictEqual(parent.childrenCount, 1, 'child count increased to 1');
    ok(parent.children[0] === child1, 'can get child1');

    child2.parent = parent;
    strictEqual(parent.childrenCount, 2, 'child count increased to 2');
    ok(parent.children[1] === child2, 'can get child2');

    child1.destroy();

    FO._deferredDestroy();

    strictEqual(parent.childrenCount, 1, 'child count should return to 1');
    ok(parent.children[0] === child2, 'only child2 left');

    child2.parent = null;
    strictEqual(parent.childrenCount, 0, 'child2 detached');

    parent.addChild(child2);
    strictEqual(parent.childrenCount, 1, 'child2 attached by using addChild');
});

test('test walk', function () {
    var A = new cc.Node('A');
    var B = new cc.Node('B');
    var C = new cc.Node('C');
    var D = new cc.Node('D');
    var E = new cc.Node('E');
    var F = new cc.Node('F');
    var G = new cc.Node('G');
    var H = new cc.Node('H');
    var I = new cc.Node('I');
    // A
    // |
    // B-C
    // |
    // D-E-F
    //  /  |
    // G-H I
    B.parent = A;
    C.parent = A;
    D.parent = B;
    E.parent = B;
    F.parent = B;
    G.parent = E;
    H.parent = E;
    I.parent = F;
    
    var walkHistory = '';
    A.walk(function (node) {
        walkHistory += node.name;
    }, function (node) {
        walkHistory += node.name + '*';
    });

    strictEqual(walkHistory, 'ABDD*EGG*HH*E*FII*F*B*CC*A*', 'walk history should be the same as predicted');
});

test('activeInHierarchy', function () {
    var parent = new cc.Node();
    var child = new cc.Node();
    child.parent = parent;

    strictEqual(parent.activeInHierarchy, false, 'parent should not activeInHierarchy before add to scene');
    strictEqual(child.activeInHierarchy, false, 'child should not activeInHierarchy before add to scene');

    cc.director.getScene().addChild(parent);

    strictEqual(parent.activeInHierarchy, true, 'parent should become activeInHierarchy after add to scene');
    strictEqual(child.activeInHierarchy, true, 'child should become activeInHierarchy after add to scene');

    child.active = false;

    strictEqual(parent.activeInHierarchy, true, 'parent unchanged');
    strictEqual(child.activeInHierarchy, false, 'child deactiveInHierarchy');

    parent.active = false;

    strictEqual(parent.activeInHierarchy, false, 'parent deactiveInHierarchy');
    strictEqual(child.activeInHierarchy, false, 'child still deactiveInHierarchy');

    child.active = true;

    strictEqual(parent.activeInHierarchy, false, 'parent unchanged');
    strictEqual(child.activeInHierarchy, false, 'child deactiveInHierarchy because parent deactived');

    parent.active = true;

    strictEqual(parent.activeInHierarchy, true, 'parent become activeInHierarchy');
    strictEqual(child.activeInHierarchy, true, 'child become activeInHierarchy because parent actived');

    parent.removeFromParent();

    strictEqual(parent.activeInHierarchy, false, 'parent should not activeInHierarchy after remove from scene');
    strictEqual(child.activeInHierarchy, false, 'child should not activeInHierarchy after remove from scene');
});

test('activeInHierarchy should be false after destroy', function () {
    var parent = new cc.Node();
    var child = new cc.Node();
    child.parent = parent;

    cc.director.getScene().addChild(parent);

    parent.destroy();
    cc.game.step();

    strictEqual(parent.activeInHierarchy, false, 'parent should not activeInHierarchy after destroy');
    strictEqual(child.activeInHierarchy, false, 'child should not activeInHierarchy after destroy');
});

test('nodes should be destroyed with the scene', function () {
    var child = new cc.Node();
    var scene = cc.director.getScene();
    child.parent = scene;

    cc.director.runSceneImmediate(new cc.Scene());

    strictEqual(child.activeInHierarchy, false, 'nodes should not activeInHierarchy after destroy');
    strictEqual(child.isValid, false, 'nodes should be destroyed with the scene');
});

test('activation logic for component', function () {
    // 这里主要测试node，不是测试component

    // my component
    var MyComponentBase = cc.Class({
        name: 'MyComponentBase',
        extends: CallbackTester,
        editor: {
            executeInEditMode: true
        },
    });

    var MyComponent = cc.Class({
        name: 'MyComponent',
        extends: MyComponentBase,
        ctor: function () {
            this.expect(CallbackTester.OnLoad, 'call onLoad while attaching to node');
            this.expect(CallbackTester.OnEnable, 'then call onEnable if node active', true);
        },
        start: function () {
            this._assert(MyComponent.start);
        },
        update: null,
        lateUpdate: null
    });
    MyComponent.start = "MyCompStart";

    var obj = new cc.Node();
    cc.director.getScene().addChild(obj);

    var comp = obj.addComponent(MyComponent); // onEnable
    comp.expect(MyComponent.start, 'call start after onEnable');
    cc.game.step();
    comp.notExpect(MyComponent.start, 'start should be called only once');
    cc.game.step();

    strictEqual(comp.node, obj, 'can get node from component');

    comp.expect(CallbackTester.OnDisable);
    obj.active = false; // onDisable

    comp.expect(CallbackTester.OnEnable);
    obj.active = true;  // onEnable

    //strictEqual(obj.getComponent(cc.Transform), obj.transform, 'getComponent: can get transform');
    ok(obj.getComponent(MyComponent) === comp, 'getComponent: can get my component');
    ok(obj.getComponent(cc.js.getClassName(MyComponent)) === comp, 'getComponent: can get my component by name');
    ok(obj.getComponent(MyComponentBase) === comp, 'getComponent: can get component by base type');
    ok(obj.getComponent(cc.js.getClassName(MyComponentBase)) === comp, 'getComponent: can get component by base name');

    comp.expect(CallbackTester.OnDisable, 'should called onDisable when destroy');
    comp.destroy();     // onDisable

    strictEqual(obj.getComponent(MyComponent), comp, 'can still get component in this frame');

    comp.expect(CallbackTester.OnDestroy);
    FO._deferredDestroy();    // onDestroy

    strictEqual(obj.getComponent(MyComponent), null, 'can not get component after this frame');

    comp.stopTest();

    cc.js.unregisterClass(MyComponent, MyComponentBase);
});

test('get self components', function () {

    var MyComponent = cc.Class({
        name: 'MyComponent',
        extends: cc.Component
    });

    var obj = new cc.Node("New Node 1");
    cc.director.getScene().addChild(obj);
    obj.addComponent(MyComponent);
    obj.addComponent(MyComponent);
    obj.addComponent(MyComponent);

    //-- layer 1
    var obj1 = new cc.Node("New Node 2");
    obj1.parent = obj;

    var obj2 = new cc.Node("New Node 3");
    var comp = obj2.addComponent(MyComponent);
    obj2.parent = obj1;

    //-- layer 2
    var obj3 = new cc.Node("New Node 4");
    obj3.parent = obj2;

    var obj4 = new cc.Node("New Node 5");
    obj4.addComponent(MyComponent);
    obj4.parent = obj3;

    ok(obj.getComponents(MyComponent).length === 3, 'getComponents: can get my component array');
    ok(obj.getComponentInChildren(MyComponent) === comp, 'getComponentInChildren: can get my component in children');
    ok(obj.getComponentsInChildren(MyComponent).length === 5, 'getComponentsInChildren: can get my components in children and self');

    cc.js.unregisterClass(MyComponent);
});


test('should not include self component', function () {
    var MyComponent = cc.Class({
        name: 'MyComponent',
        extends: cc.Component
    });

    var obj = new cc.Node("New Node");
    cc.director.getScene().addChild(obj);
    obj.addComponent(MyComponent);
    obj.addComponent(MyComponent);
    obj.addComponent(MyComponent);

    var obj1 = new cc.Node("New Node 1");
    obj1.parent = obj;

    ok(obj.getComponentInChildren(MyComponent) === null, 'getComponentInChildren should not include self component');

    cc.js.unregisterClass(MyComponent);
});

test('should include self component', function () {
    var MyComponent = cc.Class({
        name: 'MyComponent',
        extends: cc.Component
    });

    var obj = new cc.Node("New Node");
    cc.director.getScene().addChild(obj);
    obj.addComponent(MyComponent);
    obj.addComponent(MyComponent);
    obj.addComponent(MyComponent);

    var obj1 = new cc.Node("New Node 1");
    obj1.parent = obj;

    ok(obj.getComponentsInChildren(MyComponent).length === 3, 'getComponentsInChildren should include self component');

    cc.js.unregisterClass(MyComponent);
});

test('destroy', function () {
    var parent = new cc.Node();
    var child = new cc.Node();
    parent.addChild(child);
    cc.director.getScene().addChild(parent);

    // add child component
    var ChildComp = cc.Class({
        extends: CallbackTester,
        editor: {
            executeInEditMode: true
        },
        ctor: function () {
            this.expect([CallbackTester.OnLoad, CallbackTester.OnEnable]);
        }
    });
    var childComp = child.addComponent(ChildComp);

    // expect ondisable
    childComp.expect(CallbackTester.OnDisable, 'should disable while destroy parent');
    childComp.notExpect(CallbackTester.OnDestroy, 'can not destroyed before the end of this frame');

    // call destroy
    parent.destroy();

    childComp.notExpect(CallbackTester.OnDisable, 'child comp should only disabled once');
    childComp.notExpect(CallbackTester.OnEnable, 'child component should not re-enable when parent destroying');
    childComp.expect(CallbackTester.OnDestroy, 'should destroyed at the end of frame');

    // try add component after destroy

    var ChildComp_new = cc.Class({
        extends: CallbackTester,
        ctor: function () {
            this.expect(CallbackTester.OnLoad, 'new child component should onLoad even if added after destroy');
            this.expect(CallbackTester.OnEnable, 'new child component should enable even if added after destroy', true);
            this.expect(CallbackTester.OnDisable, 'new component\'s onDisable should be called before its onDestroy', true);
            this.expect(CallbackTester.OnDestroy, 'new component should also destroyed at the end of frame', true);
        }
    });
    var childComp_new = child.addComponent(ChildComp_new);

    var NewParentComp = cc.Class({
        extends: CallbackTester,
        ctor: function () {
            this.expect([CallbackTester.OnLoad,
                         CallbackTester.OnEnable,
                         CallbackTester.OnDisable,
                         CallbackTester.OnDestroy]);
        }
    });
    var newParentComp = parent.addComponent(NewParentComp);

    // try add child after destroy

    var NewChildNodeComp = cc.Class({
        extends: CallbackTester,
        ctor: function () {
            this.expect([CallbackTester.OnLoad,
                         CallbackTester.OnEnable,
                         CallbackTester.OnDisable,
                         CallbackTester.OnDestroy]);
        }
    });
    var newChildNode = new cc.Node();
    parent.addChild(newChildNode);
    var newChildNodeComp = newChildNode.addComponent(NewChildNodeComp);

    // do destroy

    FO._deferredDestroy();

    strictEqual(childComp_new.isValid, false, 'node will finally destroyed with its component which added after calling destroy');
    strictEqual(childComp.isValid, false, 'node will destroyed with its child components');
    strictEqual(child.isValid, false, 'node will destroyed with its children');
    strictEqual(newChildNode.isValid, false, 'node will destroyed with its new children');

    childComp.stopTest();
    childComp_new.stopTest();
    newParentComp.stopTest();
    newChildNodeComp.stopTest();
});

test('isChildOf', function () {
    var ent1 = new cc.Node();
    var ent2 = new cc.Node();
    var ent3 = new cc.Node();

    ent1.addChild(ent2);
    ent2.addChild(ent3);

    strictEqual(ent1.isChildOf(ent2), false, 'not a child of its children');
    strictEqual(ent1.isChildOf(ent1), true, 'is child of itself');
    strictEqual(ent2.isChildOf(ent1), true, 'is child of its parent');
    strictEqual(ent3.isChildOf(ent1), true, 'is child of its ancestor');
});


test('attach events', function () {
    var scene = cc.director.getScene();
    var parent = new cc.Node();
    var child = new cc.Node();

    var attachedNodes = {};

    var onAttach = cc.engine.on('node-attach-to-scene', function (node) {
        if (node.uuid in attachedNodes) {
            ok(false, 'already attached!');
        }
        attachedNodes[node.uuid] = true;
    });
    var onDetach = cc.engine.on('node-detach-from-scene', function (node) {
        if (!(node.uuid in attachedNodes)) {
            ok(false, 'not yet attached!');
        }
        delete attachedNodes[node.uuid];
    });

    child.parent = parent;

    strictEqual(child.uuid in attachedNodes, false, 'child node should not attach scene if parent also detached');

    parent.parent = scene;

    strictEqual(parent.uuid in attachedNodes, true, 'parent node should attach to scene');
    strictEqual(child.uuid in attachedNodes, true, 'child node should attach to scene');

    parent.parent = null;

    strictEqual(parent.uuid in attachedNodes, false, 'child node should also detached if parent become detached');
    strictEqual(child.uuid in attachedNodes, false, 'parent node should detached');

    cc.engine.off('node-attach-to-scene', onAttach);
    cc.engine.off('node-detach-from-scene', onDetach);
});

test('getBoundingBox', function () {
    var node = new cc.Node();
    node.anchorX = 0.5;
    node.anchorY = 0.5;
    node.width = 10;
    node.height = 20;

    deepEqual(node.getBoundingBox(), cc.rect(-5, -10, 10, 20), 'should compute anchor');
});