var test = require('tap').test;
var Blocks = require('../../src/engine/blocks');

test('spec', function (t) {
    var b = new Blocks();

    t.type(Blocks, 'function');
    t.type(b, 'object');
    t.ok(b instanceof Blocks);

    t.type(b._blocks, 'object');
    t.type(b._stacks, 'object');
    t.ok(Array.isArray(b._stacks));

    t.type(b.createBlock, 'function');
    t.type(b.moveBlock, 'function');
    t.type(b.changeBlock, 'function');
    t.type(b.deleteBlock, 'function');
    t.type(b.getBlock, 'function');
    t.type(b.getStacks, 'function');
    t.type(b.getNextBlock, 'function');
    t.type(b.getSubstack, 'function');
    t.type(b.getOpcode, 'function');


    t.end();
});

// Getter tests
test('getBlock', function (t) {
    var b = new Blocks();
    b.createBlock({
        id: 'foo',
        opcode: 'TEST_BLOCK',
        next: null,
        fields: {},
        inputs: {},
        topLevel: true
    });
    var block = b.getBlock('foo');
    t.type(block, 'object');
    var notBlock = b.getBlock('?');
    t.type(notBlock, 'undefined');
    t.end();
});

test('getStacks', function (t) {
    var b = new Blocks();
    var stacks = b.getStacks();
    t.type(stacks, 'object');
    t.equals(stacks.length, 0);
    // Create two top-level blocks and one not.
    b.createBlock({
        id: 'foo',
        opcode: 'TEST_BLOCK',
        next: null,
        fields: {},
        inputs: {},
        topLevel: true
    });
    b.createBlock({
        id: 'foo2',
        opcode: 'TEST_BLOCK',
        next: null,
        fields: {},
        inputs: {},
        topLevel: true
    });
    b.createBlock({
        id: 'foo3',
        opcode: 'TEST_BLOCK',
        next: null,
        fields: {},
        inputs: {},
        topLevel: false
    });

    stacks = b.getStacks();
    t.type(stacks, 'object');
    t.equals(stacks.length, 2);
    t.ok(stacks.indexOf('foo') > -1);
    t.ok(stacks.indexOf('foo2') > -1);
    t.equals(stacks.indexOf('foo3'), -1);
    t.end();

});

test('getNextBlock', function (t) {
    var b = new Blocks();
    b.createBlock({
        id: 'foo',
        opcode: 'TEST_BLOCK',
        next: null,
        fields: {},
        inputs: {},
        topLevel: true
    });

    var next = b.getNextBlock('foo');
    t.equals(next, null);

    // Add a block with "foo" as its next.
    b.createBlock({
        id: 'foo2',
        opcode: 'TEST_BLOCK',
        next: 'foo',
        fields: {},
        inputs: {},
        topLevel: true
    });

    next = b.getNextBlock('foo2');
    t.equals(next, 'foo');

    // Block that doesn't exist.
    var noBlock = b.getNextBlock('?');
    t.equals(noBlock, null);

    t.end();
});

test('getSubstack', function (t) {
    var b = new Blocks();
    // Single substack
    b.createBlock({
        id: 'foo',
        opcode: 'TEST_BLOCK',
        next: null,
        fields: {},
        inputs: {
            SUBSTACK: {
                name: 'SUBSTACK',
                block: 'foo2'
            }
        },
        topLevel: true
    });
    b.createBlock({
        id: 'foo2',
        opcode: 'TEST_BLOCK',
        next: null,
        fields: {},
        inputs: {},
        topLevel: false
    });

    var substack = b.getSubstack('foo');
    t.equals(substack, 'foo2');

    var notSubstack = b.getSubstack('?');
    t.equals(notSubstack, null);

    t.end();
});

test('getSubstack2', function (t) {
    var b = new Blocks();
    // Second substack
    b.createBlock({
        id: 'foo',
        opcode: 'TEST_BLOCK',
        next: null,
        fields: {},
        inputs: {
            SUBSTACK: {
                name: 'SUBSTACK',
                block: 'foo2'
            },
            SUBSTACK2: {
                name: 'SUBSTACK2',
                block: 'foo3'
            }
        },
        topLevel: true
    });
    b.createBlock({
        id: 'foo2',
        opcode: 'TEST_BLOCK',
        next: null,
        fields: {},
        inputs: {},
        topLevel: false
    });
    b.createBlock({
        id: 'foo3',
        opcode: 'TEST_BLOCK',
        next: null,
        fields: {},
        inputs: {},
        topLevel: false
    });

    var substack1 = b.getSubstack('foo', 1);
    var substack2 = b.getSubstack('foo', 2);
    t.equals(substack1, 'foo2');
    t.equals(substack2, 'foo3');

    t.end();
});

test('getSubstack with none', function (t) {
    var b = new Blocks();
    b.createBlock({
        id: 'foo',
        opcode: 'TEST_BLOCK',
        next: null,
        fields: {},
        inputs: {},
        topLevel: true
    });
    var noSubstack = b.getSubstack('foo');
    t.equals(noSubstack, null);
    t.end();
});

test('getOpcode', function (t) {
    var b = new Blocks();
    b.createBlock({
        id: 'foo',
        opcode: 'TEST_BLOCK',
        next: null,
        fields: {},
        inputs: {},
        topLevel: true
    });
    var opcode = b.getOpcode('foo');
    t.equals(opcode, 'TEST_BLOCK');
    var notOpcode = b.getOpcode('?');
    t.equals(notOpcode, null);
    t.end();
});

// Block events tests
test('create', function (t) {
    var b = new Blocks();
    b.createBlock({
        id: 'foo',
        opcode: 'TEST_BLOCK',
        next: null,
        fields: {},
        inputs: {},
        topLevel: true
    });

    t.type(b._blocks['foo'], 'object');
    t.equal(b._blocks['foo'].opcode, 'TEST_BLOCK');
    t.notEqual(b._stacks.indexOf('foo'), -1);
    t.end();
});

test('move', function (t) {
    var b = new Blocks();
    b.createBlock({
        id: 'foo',
        opcode: 'TEST_BLOCK',
        next: null,
        fields: {},
        inputs: {},
        topLevel: true
    });
    b.createBlock({
        id: 'bar',
        opcode: 'TEST_BLOCK',
        next: null,
        fields: {},
        inputs: {},
        topLevel: true
    });

    // Attach 'bar' to the end of 'foo'
    b.moveBlock({
        id: 'bar',
        newParent: 'foo'
    });
    t.equal(b._stacks.length, 1);
    t.equal(Object.keys(b._blocks).length, 2);
    t.equal(b._blocks['foo'].next, 'bar');

    // Detach 'bar' from 'foo'
    b.moveBlock({
        id: 'bar',
        oldParent: 'foo'
    });
    t.equal(b._stacks.length, 2);
    t.equal(Object.keys(b._blocks).length, 2);
    t.equal(b._blocks['foo'].next, null);

    t.end();
});

test('change', function (t) {
    var b = new Blocks();
    b.createBlock({
        id: 'foo',
        opcode: 'TEST_BLOCK',
        next: null,
        fields: {
            someField: {
                name: 'someField',
                value: 'initial-value'
            }
        },
        inputs: {},
        topLevel: true
    });

    // Test that the field is updated
    t.equal(b._blocks['foo'].fields.someField.value, 'initial-value');

    b.changeBlock({
        element: 'field',
        id: 'foo',
        name: 'someField',
        value: 'final-value'
    });

    t.equal(b._blocks['foo'].fields.someField.value, 'final-value');

    // Invalid cases
    // No `element`
    b.changeBlock({
        id: 'foo',
        name: 'someField',
        value: 'invalid-value'
    });
    t.equal(b._blocks['foo'].fields.someField.value, 'final-value');

    // No block ID
    b.changeBlock({
        element: 'field',
        name: 'someField',
        value: 'invalid-value'
    });
    t.equal(b._blocks['foo'].fields.someField.value, 'final-value');

    // No such field
    b.changeBlock({
        element: 'field',
        id: 'foo',
        name: 'someWrongField',
        value: 'final-value'
    });
    t.equal(b._blocks['foo'].fields.someField.value, 'final-value');

    t.end();
});

test('delete', function (t) {
    var b = new Blocks();
    b.createBlock({
        id: 'foo',
        opcode: 'TEST_BLOCK',
        next: null,
        fields: {},
        inputs: {},
        topLevel: true
    });
    b.deleteBlock({
        id: 'foo'
    });

    t.type(b._blocks['foo'], 'undefined');
    t.equal(b._stacks.indexOf('foo'), -1);
    t.end();
});

test('delete chain', function (t) {
    // Create a chain of connected blocks and delete the top one.
    // All of them should be deleted.
    var b = new Blocks();
    b.createBlock({
        id: 'foo',
        opcode: 'TEST_BLOCK',
        next: 'foo2',
        fields: {},
        inputs: {},
        topLevel: true
    });
    b.createBlock({
        id: 'foo2',
        opcode: 'TEST_BLOCK',
        next: 'foo3',
        fields: {},
        inputs: {},
        topLevel: false
    });
    b.createBlock({
        id: 'foo3',
        opcode: 'TEST_BLOCK',
        next: null,
        fields: {},
        inputs: {},
        topLevel: false
    });
    b.deleteBlock({
        id: 'foo'
    });
    t.type(b._blocks['foo'], 'undefined');
    t.type(b._blocks['foo2'], 'undefined');
    t.type(b._blocks['foo3'], 'undefined');
    t.equal(b._stacks.indexOf('foo'), -1);
    t.equal(Object.keys(b._blocks).length, 0);
    t.equal(b._stacks.length, 0);
    t.end();
});

test('delete inputs', function (t) {
    // Create a block with two inputs, one of which has its own input.
    // Delete the block - all of them should be deleted.
    var b = new Blocks();
    b.createBlock({
        id: 'foo',
        opcode: 'TEST_BLOCK',
        next: null,
        fields: {},
        inputs: {
            input1: {
                name: 'input1',
                block: 'foo2'
            },
            SUBSTACK: {
                name: 'SUBSTACK',
                block: 'foo3'
            }
        },
        topLevel: true
    });
    b.createBlock({
        id: 'foo2',
        opcode: 'TEST_BLOCK',
        next: null,
        fields: {},
        inputs: {},
        topLevel: false
    });
    b.createBlock({
        id: 'foo3',
        opcode: 'TEST_BLOCK',
        next: null,
        fields: {},
        inputs: {
            subinput: {
                name: 'subinput',
                block: 'foo4'
            }
        },
        topLevel: false
    });
    b.createBlock({
        id: 'foo4',
        opcode: 'TEST_BLOCK',
        next: null,
        fields: {},
        inputs: {},
        topLevel: false
    });
    b.deleteBlock({
        id: 'foo'
    });
    t.type(b._blocks['foo'], 'undefined');
    t.type(b._blocks['foo2'], 'undefined');
    t.type(b._blocks['foo3'], 'undefined');
    t.type(b._blocks['foo4'], 'undefined');
    t.equal(b._stacks.indexOf('foo'), -1);
    t.equal(Object.keys(b._blocks).length, 0);
    t.equal(b._stacks.length, 0);
    t.end();
});