describe('iD.actionConnect', function() {
    it('merges tags', function() {
        var graph = new iD.coreGraph([
            new iD.osmNode({id: 'a', tags: { highway: 'traffic_signals' }}),
            new iD.osmNode({id: 'b', tags: { crossing: 'marked' }}),
        ]);

        graph = iD.actionConnect(['a', 'b'])(graph);
        expect(graph.hasEntity('a')).toBeFalsy();

        var survivor = graph.hasEntity('b');
        expect(survivor).toBeInstanceOf(iD.osmNode);
        expect(survivor.tags).toEqual({ highway: 'traffic_signals', crossing: 'marked' }, 'merge all tags');
    });

    it('chooses the oldest node as the survivor', function() {
        var graph = new iD.coreGraph([
            new iD.osmNode({id: 'n3'}),
            new iD.osmNode({id: 'n-1'}),
            new iD.osmNode({id: 'n2'}),
            new iD.osmNode({id: 'n4'})
        ]);

        graph = iD.actionConnect(['n3', 'n-1', 'n2', 'n4'])(graph);
        expect(graph.hasEntity('n3')).toBeFalsy();
        expect(graph.hasEntity('n-1')).toBeFalsy();
        expect(graph.hasEntity('n2')).toBeTruthy();
        expect(graph.hasEntity('n4')).toBeFalsy();
    });

    it('chooses the oldest interesting node as the survivor', function() {
        var graph = new iD.coreGraph([
            new iD.osmNode({id: 'n3'}),
            new iD.osmNode({id: 'n1'}),
            new iD.osmNode({id: 'n2', tags: { highway: 'traffic_signals' }}),
            new iD.osmNode({id: 'n4', tags: { crossing: 'marked' }})
        ]);

        graph = iD.actionConnect(['n3', 'n1', 'n2', 'n4'])(graph);

        expect(graph.hasEntity('n3')).toBeFalsy();
        expect(graph.hasEntity('n1')).toBeFalsy();
        expect(graph.hasEntity('n4')).toBeFalsy();

        var survivor = graph.hasEntity('n2');
        expect(survivor).toBeInstanceOf(iD.osmNode);
        expect(survivor.tags).toEqual({ highway: 'traffic_signals', crossing: 'marked' }, 'merge all tags');
    });

    it('chooses an existing node as the survivor', function() {
        var graph = new iD.coreGraph([
            new iD.osmNode({id: 'n3'}),
            new iD.osmNode({id: 'n-1'}),
            new iD.osmNode({id: 'n-2', tags: { highway: 'traffic_signals' }}),
            new iD.osmNode({id: 'n-4', tags: { crossing: 'marked' }})
        ]);

        graph = iD.actionConnect(['n3', 'n-1', 'n-2', 'n-4'])(graph);

        expect(graph.hasEntity('n-1')).toBeFalsy();
        expect(graph.hasEntity('n-2')).toBeFalsy();
        expect(graph.hasEntity('n-4')).toBeFalsy();

        var survivor = graph.hasEntity('n3');
        expect(survivor).toBeInstanceOf(iD.osmNode);
        expect(survivor.tags).toEqual({ highway: 'traffic_signals', crossing: 'marked' }, 'merge all tags');
    });

    it('chooses the last node as the survivor when all are new', function() {
        var graph = new iD.coreGraph([
            new iD.osmNode({id: 'a', tags: { highway: 'traffic_signals' }}),
            new iD.osmNode({id: 'b', tags: { crossing: 'marked' }}),
            new iD.osmNode({id: 'c'})
        ]);

        graph = iD.actionConnect(['a', 'b', 'c'])(graph);
        expect(graph.hasEntity('a')).toBeFalsy();
        expect(graph.hasEntity('b')).toBeFalsy();

        var survivor = graph.hasEntity('c');
        expect(survivor).toBeInstanceOf(iD.osmNode);
        expect(survivor.tags).toEqual({ highway: 'traffic_signals', crossing: 'marked' }, 'merge all tags');
    });


    it('replaces non-surviving nodes in parent ways', function() {
        // a --- b --- c
        //
        //       e
        //       |
        //       d
        //
        // Connect [e, b].
        //
        // Expected result:
        //
        // a --- b --- c
        //       |
        //       d
        //
        var graph = new iD.coreGraph([
            new iD.osmNode({id: 'a'}),
            new iD.osmNode({id: 'b'}),
            new iD.osmNode({id: 'c'}),
            new iD.osmNode({id: 'd'}),
            new iD.osmNode({id: 'e'}),
            new iD.osmWay({id: '-', nodes: ['a', 'b', 'c']}),
            new iD.osmWay({id: '|', nodes: ['d', 'e']})
        ]);

        graph = iD.actionConnect(['e', 'b'])(graph);
        expect(graph.entity('-').nodes).toEqual(['a', 'b', 'c']);
        expect(graph.entity('|').nodes).toEqual(['d', 'b']);
    });

    it('handles circular ways', function() {
        // c -- a   d === e
        // |   /
        // |  /
        // | /
        // b
        //
        // Connect [a, d].
        //
        var graph = new iD.coreGraph([
            new iD.osmNode({id: 'a'}),
            new iD.osmNode({id: 'b'}),
            new iD.osmNode({id: 'c'}),
            new iD.osmNode({id: 'd'}),
            new iD.osmNode({id: 'e'}),
            new iD.osmWay({id: '-', nodes: ['a', 'b', 'c', 'a']}),
            new iD.osmWay({id: '=', nodes: ['d', 'e']})
        ]);

        graph = iD.actionConnect(['a', 'd'])(graph);
        expect(graph.entity('-').nodes).toEqual(['d', 'b', 'c', 'd']);
    });

    it('merges adjacent nodes', function() {
        // a --- b --- c
        //
        // Connect [b, c]
        //
        // Expected result:
        //
        // a --- c
        //
        var graph = new iD.coreGraph([
            new iD.osmNode({id: 'a'}),
            new iD.osmNode({id: 'b'}),
            new iD.osmNode({id: 'c'}),
            new iD.osmWay({id: '-', nodes: ['a', 'b', 'c']})
        ]);

        graph = iD.actionConnect(['b', 'c'])(graph);
        expect(graph.entity('-').nodes).toEqual(['a', 'c']);
        expect(graph.hasEntity('b')).toBeUndefined();
    });

    it('merges adjacent nodes with connections', function() {
        // a --- b --- c
        //       |
        //       d
        //
        // Connect [b, c]
        //
        // Expected result:
        //
        // a --- c
        //       |
        //       d
        //
        var graph = new iD.coreGraph([
            new iD.osmNode({id: 'a'}),
            new iD.osmNode({id: 'b'}),
            new iD.osmNode({id: 'c'}),
            new iD.osmNode({id: 'c'}),
            new iD.osmWay({id: '-', nodes: ['a', 'b', 'c']}),
            new iD.osmWay({id: '|', nodes: ['b', 'd']})
        ]);

        graph = iD.actionConnect(['b', 'c'])(graph);
        expect(graph.entity('-').nodes).toEqual(['a', 'c']);
        expect(graph.entity('|').nodes).toEqual(['c', 'd']);
        expect(graph.hasEntity('b')).toBeUndefined();
    });

    it('deletes a degenerate way', function() {
        // a --- b
        //
        // Connect [a, b]
        //
        var graph = new iD.coreGraph([
            new iD.osmNode({id: 'a'}),
            new iD.osmNode({id: 'b'}),
            new iD.osmWay({id: '-', nodes: ['a', 'b']})
        ]);

        graph = iD.actionConnect(['a', 'b'])(graph);
        expect(graph.hasEntity('a')).toBeUndefined();
        expect(graph.hasEntity('-')).toBeUndefined();
    });

    it('merges tags to the surviving node', function() {
        var graph = new iD.coreGraph([
            new iD.osmNode({id: 'a', tags: {a: 'a'}}),
            new iD.osmNode({id: 'b', tags: {b: 'b'}}),
            new iD.osmNode({id: 'c', tags: {c: 'c'}})
        ]);

        graph = iD.actionConnect(['a', 'b', 'c'])(graph);
        expect(graph.entity('c').tags).toEqual({a: 'a', b: 'b', c: 'c'});
    });

    it('merges memberships to the surviving node', function() {
        var graph = new iD.coreGraph([
            new iD.osmNode({id: 'a'}),
            new iD.osmNode({id: 'b'}),
            new iD.osmNode({id: 'c'}),
            new iD.osmNode({id: 'c'}),
            new iD.osmWay({id: '-', nodes: ['a', 'b']}),
            new iD.osmWay({id: '=', nodes: ['c', 'd']}),
            new iD.osmRelation({id: 'r1', members: [{id: 'b', role: 'r1', type: 'node'}]}),
            new iD.osmRelation({id: 'r2', members: [{id: 'b', role: 'r2', type: 'node'}, {id: 'c', role: 'r2', type: 'node'}]})
        ]);

        graph = iD.actionConnect(['b', 'c'])(graph);
        expect(graph.entity('r1').members).toEqual([{id: 'c', role: 'r1', type: 'node'}]);
        expect(graph.entity('r2').members).toEqual([{id: 'c', role: 'r2', type: 'node'}]);
    });


    describe('#disabled', function () {
        it('returns falsy when connecting members of the same relation and same roles', function () {
            var graph = new iD.coreGraph([
                new iD.osmNode({id: 'a'}),
                new iD.osmNode({id: 'b'}),
                new iD.osmNode({id: 'c'}),
                new iD.osmWay({id: '-', nodes: ['a', 'b', 'c']}),
                new iD.osmRelation({id: 'r1', members: [
                    { id: 'b', type: 'node', role: 'foo' },
                    { id: 'c', type: 'node', role: 'foo' }
                ]})
            ]);

            expect(iD.actionConnect(['b', 'c']).disabled(graph)).toBeFalsy();
        });

        it('returns falsy when connecting members of different relation and different roles', function () {
            var graph = new iD.coreGraph([
                new iD.osmNode({id: 'a'}),
                new iD.osmNode({id: 'b'}),
                new iD.osmNode({id: 'c'}),
                new iD.osmWay({id: '-', nodes: ['a', 'b', 'c']}),
                new iD.osmRelation({id: 'r1', members: [{ id: 'b', type: 'node', role: 'foo' } ]}),
                new iD.osmRelation({id: 'r2', members: [{ id: 'c', type: 'node', role: 'bar' } ]})
            ]);

            expect(iD.actionConnect(['b', 'c']).disabled(graph)).toBeFalsy();
        });

        it('returns \'relation\' when connecting members of the same relation but different roles', function () {
            var graph = new iD.coreGraph([
                new iD.osmNode({id: 'a'}),
                new iD.osmNode({id: 'b'}),
                new iD.osmNode({id: 'c'}),
                new iD.osmWay({id: '-', nodes: ['a', 'b', 'c']}),
                new iD.osmRelation({id: 'r1', members: [
                    { id: 'b', type: 'node', role: 'foo' },
                    { id: 'c', type: 'node', role: 'bar' }
                ]})
            ]);

            expect(iD.actionConnect(['b', 'c']).disabled(graph)).toEqual('relation');
        });

        it('returns falsy when connecting a node unrelated to the restriction', function () {
            //
            //  a --- b   d ~~~ e        r1:  `no_right_turn`
            //        |                        FROM '-'
            //        |                        VIA  'b'
            //        c                        TO   '|'
            //
            var graph = new iD.coreGraph([
                new iD.osmNode({id: 'a'}),
                new iD.osmNode({id: 'b'}),
                new iD.osmNode({id: 'c'}),
                new iD.osmNode({id: 'd'}),
                new iD.osmNode({id: 'e'}),
                new iD.osmWay({id: '-', nodes: ['a', 'b']}),
                new iD.osmWay({id: '|', nodes: ['b', 'c']}),
                new iD.osmWay({id: '~', nodes: ['d', 'e']}),
                new iD.osmRelation({id: 'r1', tags: { type: 'restriction', restriction: 'no_right_turn' }, members: [
                    { id: '-', type: 'way', role: 'from' },
                    { id: 'b', type: 'node', role: 'via' },
                    { id: '|', type: 'way', role: 'to' }
                ]})
            ]);

            expect(iD.actionConnect(['a', 'd']).disabled(graph)).toBeFalsy();
            expect(iD.actionConnect(['b', 'd']).disabled(graph)).toBeFalsy();
            expect(iD.actionConnect(['c', 'd']).disabled(graph)).toBeFalsy();
        });

        it('returns falsy when connecting nodes that would not break a via-node restriction', function () {
            //
            //  a --- b --- c      r1:  `no_right_turn`
            //              |            FROM '-'
            //              d            VIA  'c'
            //              |            TO   '|'
            //              e
            //
            var graph = new iD.coreGraph([
                new iD.osmNode({id: 'a'}),
                new iD.osmNode({id: 'b'}),
                new iD.osmNode({id: 'c'}),
                new iD.osmNode({id: 'd'}),
                new iD.osmNode({id: 'e'}),
                new iD.osmWay({id: '-', nodes: ['a', 'b', 'c']}),
                new iD.osmWay({id: '|', nodes: ['c', 'd', 'e']}),
                new iD.osmRelation({id: 'r1', tags: { type: 'restriction', restriction: 'no_right_turn' }, members: [
                    { id: '-', type: 'way', role: 'from' },
                    { id: 'c', type: 'node', role: 'via' },
                    { id: '|', type: 'way', role: 'to' }
                ]})
            ]);

            // allowed: adjacent connections that don't destroy a way
            expect(iD.actionConnect(['a', 'b']).disabled(graph)).toBeFalsy();
            expect(iD.actionConnect(['b', 'c']).disabled(graph)).toBeFalsy();
            expect(iD.actionConnect(['c', 'd']).disabled(graph)).toBeFalsy();
            expect(iD.actionConnect(['d', 'e']).disabled(graph)).toBeFalsy();
        });

        it('returns falsy when connecting nodes that would not break a via-way restriction', function () {
            //
            //  a --- b --- c      r1:  `no_u_turn`
            //              |            FROM '='
            //              d            VIA  '|'
            //              |            TO   '-'
            //  g === f === e
            //
            var graph = new iD.coreGraph([
                new iD.osmNode({id: 'a'}),
                new iD.osmNode({id: 'b'}),
                new iD.osmNode({id: 'c'}),
                new iD.osmNode({id: 'd'}),
                new iD.osmNode({id: 'e'}),
                new iD.osmNode({id: 'f'}),
                new iD.osmNode({id: 'g'}),
                new iD.osmWay({id: '-', nodes: ['a', 'b', 'c']}),
                new iD.osmWay({id: '|', nodes: ['c', 'd', 'e']}),
                new iD.osmWay({id: '=', nodes: ['e', 'f', 'g']}),
                new iD.osmRelation({id: 'r1', tags: { type: 'restriction', restriction: 'no_u_turn' }, members: [
                    { id: '=', type: 'way', role: 'from' },
                    { id: '|', type: 'way', role: 'via' },
                    { id: '-', type: 'way', role: 'to' }
                ]})
            ]);

            // allowed: adjacent connections that don't destroy a way
            expect(iD.actionConnect(['a', 'b']).disabled(graph)).toBeFalsy();
            expect(iD.actionConnect(['b', 'c']).disabled(graph)).toBeFalsy();
            expect(iD.actionConnect(['c', 'd']).disabled(graph)).toBeFalsy();
            expect(iD.actionConnect(['d', 'e']).disabled(graph)).toBeFalsy();
            expect(iD.actionConnect(['e', 'f']).disabled(graph)).toBeFalsy();
            expect(iD.actionConnect(['f', 'g']).disabled(graph)).toBeFalsy();
        });

        it('returns \'restriction\' when connecting nodes that would break a via-node restriction', function () {
            //
            //  a --- b --- c      r1:  `no_right_turn`
            //              |            FROM '-'
            //              d            VIA  'c'
            //              |            TO   '|'
            //              e
            //
            var graph = new iD.coreGraph([
                new iD.osmNode({id: 'a'}),
                new iD.osmNode({id: 'b'}),
                new iD.osmNode({id: 'c'}),
                new iD.osmNode({id: 'd'}),
                new iD.osmNode({id: 'e'}),
                new iD.osmWay({id: '-', nodes: ['a', 'b', 'c']}),
                new iD.osmWay({id: '|', nodes: ['c', 'd', 'e']}),
                new iD.osmRelation({id: 'r1', tags: { type: 'restriction', restriction: 'no_right_turn' }, members: [
                    { id: '-', type: 'way', role: 'from' },
                    { id: 'c', type: 'node', role: 'via' },
                    { id: '|', type: 'way', role: 'to' }
                ]})
            ]);

            // prevented:
            // extra connections to the VIA node, or any connections between distinct FROM and TO
            expect(iD.actionConnect(['a', 'c']).disabled(graph)).toEqual('restriction', 'extra connection FROM-VIA');
            expect(iD.actionConnect(['e', 'c']).disabled(graph)).toEqual('restriction', 'extra connection TO-VIA');
            expect(iD.actionConnect(['b', 'd']).disabled(graph)).toEqual('restriction', 'extra connection FROM-TO');
        });

        it('returns falsy when connecting nodes on a via-node u_turn restriction', function () {
            //
            //  a --- b --- c      r1:  `no_u_turn`
            //              |            FROM '-'
            //              d            VIA  'c'
            //              |            TO   '-'
            //              e
            //
            var graph = new iD.coreGraph([
                new iD.osmNode({id: 'a'}),
                new iD.osmNode({id: 'b'}),
                new iD.osmNode({id: 'c'}),
                new iD.osmNode({id: 'd'}),
                new iD.osmNode({id: 'e'}),
                new iD.osmWay({id: '-', nodes: ['a', 'b', 'c']}),
                new iD.osmWay({id: '|', nodes: ['c', 'd', 'e']}),
                new iD.osmRelation({id: 'r1', tags: { type: 'restriction', restriction: 'no_u_turn' }, members: [
                    { id: '-', type: 'way', role: 'from' },
                    { id: 'c', type: 'node', role: 'via' },
                    { id: '-', type: 'way', role: 'to' }
                ]})
            ]);

            // The u-turn case is one where a connection between FROM-TO should be allowed
            expect(iD.actionConnect(['a', 'b']).disabled(graph)).toBeFalsy();
            expect(iD.actionConnect(['b', 'c']).disabled(graph)).toBeFalsy();
        });

        it('returns \'restriction\' when connecting nodes that would break a via-way restriction', function () {
            //
            //  a --- b --- c      r1:  `no_u_turn`
            //              |            FROM '='
            //              d            VIA  '|'
            //              |            TO   '-'
            //  g === f === e
            //
            var graph = new iD.coreGraph([
                new iD.osmNode({id: 'a'}),
                new iD.osmNode({id: 'b'}),
                new iD.osmNode({id: 'c'}),
                new iD.osmNode({id: 'd'}),
                new iD.osmNode({id: 'e'}),
                new iD.osmNode({id: 'f'}),
                new iD.osmNode({id: 'g'}),
                new iD.osmWay({id: '-', nodes: ['a', 'b', 'c']}),
                new iD.osmWay({id: '|', nodes: ['c', 'd', 'e']}),
                new iD.osmWay({id: '=', nodes: ['e', 'f', 'g']}),
                new iD.osmRelation({id: 'r1', tags: { type: 'restriction', restriction: 'no_u_turn' }, members: [
                    { id: '=', type: 'way', role: 'from' },
                    { id: '|', type: 'way', role: 'via' },
                    { id: '-', type: 'way', role: 'to' }
                ]})
            ]);

            // prevented:
            // extra connections to any node along VIA way
            expect(iD.actionConnect(['a', 'c']).disabled(graph)).toEqual('restriction', 'extra connection TO-VIA c');
            expect(iD.actionConnect(['b', 'd']).disabled(graph)).toEqual('restriction', 'extra connection TO-VIA d');
            expect(iD.actionConnect(['b', 'e']).disabled(graph)).toEqual('restriction', 'extra connection TO-VIA e');
            expect(iD.actionConnect(['c', 'e']).disabled(graph)).toEqual('restriction', 'extra connection VIA-VIA');
            expect(iD.actionConnect(['f', 'c']).disabled(graph)).toEqual('restriction', 'extra connection FROM-VIA c');
            expect(iD.actionConnect(['f', 'd']).disabled(graph)).toEqual('restriction', 'extra connection FROM-VIA d');
            expect(iD.actionConnect(['g', 'e']).disabled(graph)).toEqual('restriction', 'extra connection FROM-VIA e');
        });

        it('returns \'restriction\' when connecting would destroy a way in a via-node restriction', function () {
            //
            //  a --- b      r1:  `no_right_turn`
            //        |            FROM '-'
            //        |            VIA  'b'
            //        c            TO   '|'
            //
            var graph = new iD.coreGraph([
                new iD.osmNode({id: 'a'}),
                new iD.osmNode({id: 'b'}),
                new iD.osmNode({id: 'c'}),
                new iD.osmWay({id: '-', nodes: ['a', 'b']}),
                new iD.osmWay({id: '|', nodes: ['b', 'c']}),
                new iD.osmRelation({id: 'r1', tags: { type: 'restriction', restriction: 'no_right_turn' }, members: [
                    { id: '-', type: 'way', role: 'from' },
                    { id: 'b', type: 'node', role: 'via' },
                    { id: '|', type: 'way', role: 'to' }
                ]})
            ]);

            expect(iD.actionConnect(['a', 'b']).disabled(graph)).toEqual('restriction', 'destroy FROM');
            expect(iD.actionConnect(['b', 'c']).disabled(graph)).toEqual('restriction', 'destroy TO');
        });

        it('returns \'restriction\' when connecting would destroy a way in via-way restriction', function () {
            //
            //  a --- b      r1:  `no_u_turn`
            //        |            FROM '='
            //        |            VIA  '|'
            //  d === c            TO   '-'
            //
            var graph = new iD.coreGraph([
                new iD.osmNode({id: 'a'}),
                new iD.osmNode({id: 'b'}),
                new iD.osmNode({id: 'c'}),
                new iD.osmNode({id: 'd'}),
                new iD.osmWay({id: '-', nodes: ['a', 'b']}),
                new iD.osmWay({id: '|', nodes: ['b', 'c']}),
                new iD.osmWay({id: '=', nodes: ['c', 'd']}),
                new iD.osmRelation({id: 'r1', tags: { type: 'restriction', restriction: 'no_u_turn' }, members: [
                    { id: '=', type: 'way', role: 'from' },
                    { id: '|', type: 'way', role: 'via' },
                    { id: '-', type: 'way', role: 'to' }
                ]})
            ]);

            expect(iD.actionConnect(['a', 'b']).disabled(graph)).toEqual('restriction', 'destroy TO');
            expect(iD.actionConnect(['b', 'c']).disabled(graph)).toEqual('restriction', 'destroy VIA');
            expect(iD.actionConnect(['c', 'd']).disabled(graph)).toEqual('restriction', 'destroy FROM');
        });

    });
});
