import { FilteredMarkerSource } from './filtered-marker-source';
import { expect } from 'chai';
import sinon from 'sinon';
import { FilterItem } from './filter-item';
import { Marker } from './marker';

const sleep = time => new Promise(resolve => setTimeout(resolve, time));

describe('FilteredMarkerSource', () => {

    /** @type {FilteredMarkerSource} */
    let subject;

    beforeEach(() => {
        subject = new FilteredMarkerSource({ });
    });

    it('should call callbacks if new filter item added', () => {
        const cb = sinon.fake();
        subject.allFilterItems(cb);
        expect(cb.callCount).to.equal(1);
        expect(cb.getCall(0).args[0].length).to.equal(0);

        subject.addFilterItem(new FilterItem());
        expect(cb.callCount).to.equal(2);
        expect(cb.getCall(1).args[0].length).to.equal(1);
    });

    it('should call callbacks if new marker added', () => {
        const cb = sinon.fake();
        subject.allMarkers(cb);
        expect(cb.callCount).to.equal(1);
        expect(cb.getCall(0).args[0].test).to.equal(undefined);

        const marker = new Marker('test');
        marker.coordinates = { latitude: 50, longitude: 50 };
        subject.addMarker(marker);

        expect(cb.callCount).to.equal(2);
        expect(cb.getCall(1).args[0].test).to.equal(marker);
    });


    it('should call callbacks if path changed', () => {
        const cb = sinon.fake();
        subject.currentFilter(cb);
        expect(cb.callCount).to.equal(1);
        expect(cb.getCall(0).args[0]).to.equal(null);

        const item = new FilterItem();
        item.path = '/foo';
        subject.setCurrentFilter(item);

        expect(cb.callCount).to.equal(2);
        expect(cb.getCall(1).args[0].path).to.equal('/foo');
    });

    it('should call filtered markers callbacks if new markers added', async () => {
        const cb = sinon.fake();
        subject.filteredMarkers(cb);

        await sleep(500);
        expect(cb.callCount).to.equal(1);

        const marker = new Marker('test');
        marker.coordinates = { latitude: 50, longitude: 50 };
        subject.addMarker(marker);
        await sleep(500);
        expect(cb.callCount).to.equal(2);
    });

    it('should call filtered markers callbacks if new filter path changed', async () => {
        const cb = sinon.fake();
        subject.filteredMarkers(cb);

        await sleep(500);
        expect(cb.callCount).to.equal(1);

        const item = new FilterItem();
        item.path = '/foo';
        subject.setCurrentFilter(item);
        await sleep(500);

        expect(cb.callCount).to.equal(2);
    });


    it('should filter markers based on path', async () => {
        const foo = new Marker('foo');
        foo.coordinates = { latitude: 10, longitude: 10 };
        foo.paths = [ '/foo' ];
        subject.addMarker(foo);

        const fooBar = new Marker('fooBar');
        fooBar.coordinates = { latitude: 10, longitude: 10 };
        fooBar.paths = [ '/foo/bar' ];
        subject.addMarker(fooBar);

        const test = new Marker('test');
        test.coordinates = { latitude: 10, longitude: 10 };
        test.paths = [ '/test' ];
        subject.addMarker(test);

        // Initial run: no filter
        const cb = sinon.fake();
        subject.filteredMarkers(cb);

        await sleep(500);
        expect(cb.lastCall.args[0].length).to.equal(3);


        const itemOne = new FilterItem();
        itemOne.path = '/foo';
        subject.setCurrentFilter(itemOne);
        await sleep(500);
        expect(cb.lastCall.args[0].length).to.equal(2);

        const itemTwo = new FilterItem();
        itemTwo.path = '/foo/bar';
        subject.setCurrentFilter(itemTwo);
        await sleep(500);
        expect(cb.lastCall.args[0].length).to.equal(1);
    });

    it ('applies styles from filter item if it overwrites', () => {

        const foo = new Marker('foo');
        foo.coordinates = { latitude: 10, longitude: 10 };
        foo.paths = [ '/foo' ];
        subject.addMarker(foo);

        const bar = new Marker('bar');
        bar.coordinates = { latitude: 10, longitude: 10 };
        bar.paths = [ '/bar' ];
        subject.addMarker(bar);

        const itemOne = new FilterItem();
        itemOne.path = '/foo';
        itemOne.style = { color: '#fff' };
        itemOne.overwriteMarkerStyles = true;
        subject.addFilterItem(itemOne);
        subject.setCurrentFilter(itemOne);

        expect(foo.style.color).to.equal('#fff');
        expect(bar.style.color).to.not.equal('#fff');
    });

    it ('does not apply styles from filter item if it does not overwrite', () => {

        const foo = new Marker('foo');
        foo.coordinates = { latitude: 10, longitude: 10 };
        foo.paths = [ '/foo' ];
        subject.addMarker(foo);

        const bar = new Marker('bar');
        bar.coordinates = { latitude: 10, longitude: 10 };
        bar.paths = [ '/bar' ];
        subject.addMarker(bar);

        const itemOne = new FilterItem();
        itemOne.path = '/foo';
        itemOne.style = { color: '#fff' };
        itemOne.overwriteMarkerStyles = false;
        subject.addFilterItem(itemOne);
        subject.setCurrentFilter(itemOne);

        expect(foo.style.color).to.not.equal('#fff');
        expect(bar.style.color).to.not.equal('#fff');
    });

    it ('applies style of top-level filter item when selecting root', () => {
        const foo = new Marker('foo');
        foo.coordinates = { latitude: 10, longitude: 10 };
        foo.paths = [ '/foo' ];
        subject.addMarker(foo);

        const bar = new Marker('bar');
        bar.coordinates = { latitude: 10, longitude: 10 };
        bar.paths = [ '/bar' ];
        subject.addMarker(bar);

        const itemOne = new FilterItem();
        itemOne.path = '/foo';
        itemOne.style = { color: '#f00' };
        itemOne.overwriteMarkerStyles = true;

        const deepItem = new FilterItem();
        deepItem.path = '/foo/bar';
        deepItem.style = { color: '#0f0' };
        deepItem.overwriteMarkerStyles = true;
        itemOne.children.push(deepItem);

        subject.addFilterItem(itemOne);
        subject.setCurrentFilter({ path: '/' });


        expect(foo.style.color).to.equal('#f00');
        expect(bar.style.color).to.not.equal('#f00');
    });

    it ('does not apply style of deeper filter item when selecting root', () => {
        const foo = new Marker('foo');
        foo.coordinates = { latitude: 10, longitude: 10 };
        foo.paths = [ '/foo/bar' ];
        subject.addMarker(foo);

        const bar = new Marker('bar');
        bar.coordinates = { latitude: 10, longitude: 10 };
        bar.paths = [ '/bar/test' ];
        subject.addMarker(bar);

        const itemOne = new FilterItem();
        itemOne.path = '/foo';
        itemOne.style = { color: '#f00' };
        itemOne.overwriteMarkerStyles = true;

        const deepItem = new FilterItem();
        deepItem.path = '/foo/bar';
        deepItem.style = { color: '#0f0' };
        deepItem.overwriteMarkerStyles = true;
        itemOne.children.push(deepItem);

        subject.addFilterItem(itemOne);
        subject.setCurrentFilter({ path: '/' });


        expect(foo.style.color).to.not.equal('#0f0');
        expect(bar.style.color).to.not.equal('#0f0');
    });


    it ('reapplies styles when a new filter item is added', () => {
        subject.setCurrentFilter({ path: '/' });

        const foo = new Marker('foo');
        foo.coordinates = { latitude: 10, longitude: 10 };
        foo.paths = [ '/foo/bar' ];
        subject.addMarker(foo);

        expect(foo.style.color).to.not.equal('#f00');

        const itemOne = new FilterItem();
        itemOne.path = '/foo';
        itemOne.style = { color: '#f00' };
        itemOne.overwriteMarkerStyles = true;
        subject.addFilterItem(itemOne);

        expect(foo.style.color).to.equal('#f00');
    });

    it ('should open filter items by default', () => {
        const cb = sinon.fake();
        subject.currentFilter(cb);

        const itemOne = new FilterItem();
        const itemTwo = new FilterItem();
        itemTwo.openByDefault = true;

        subject.addFilterItems([ itemOne, itemTwo ]);

        // currentFilter emits the inital filter item on first call, we are not interested in that one.
        expect(cb.callCount).to.equal(2);
        expect(cb.getCall(1).args[0]).to.equal(itemTwo);
    });

    it ('should open first open by default filter item if multiple found', () => {
        const cb = sinon.fake();
        subject.currentFilter(cb);

        const itemOne = new FilterItem();
        itemOne.openByDefault = true;
        const itemTwo = new FilterItem();
        itemTwo.openByDefault = true;

        subject.addFilterItems([ itemOne, itemTwo ]);

        // currentFilter emits the inital filter item on first call, we are not interested in that one.
        expect(cb.callCount).to.equal(2);
        expect(cb.getCall(1).args[0]).to.equal(itemOne);
    });

    it ('should force open items', () => {
        const cb = sinon.fake();
        subject.currentFilter(cb);

        const itemOne = new FilterItem();
        const itemTwo = new FilterItem();
        itemTwo.forceOpen = true;

        subject.addFilterItems([ itemOne, itemTwo ]);

        // currentFilter emits the inital filter item on first call, we are not interested in that one.
        expect(cb.callCount).to.equal(2);
        expect(cb.getCall(1).args[0]).to.equal(itemTwo);
    });


    it ('should open first force opened filter item if multiple found', () => {
        const cb = sinon.fake();
        subject.currentFilter(cb);

        const itemOne = new FilterItem();
        itemOne.forceOpen = true;
        const itemTwo = new FilterItem();
        itemTwo.forceOpen = true;

        subject.addFilterItems([ itemOne, itemTwo ]);

        // currentFilter emits the inital filter item on first call, we are not interested in that one.
        expect(cb.callCount).to.equal(2);
        expect(cb.getCall(1).args[0]).to.equal(itemOne);
    });


    it ('should prioritize force opened items over opened by default', () => {
        const cb = sinon.fake();
        subject.currentFilter(cb);

        const itemOne = new FilterItem();
        itemOne.openByDefault = true;

        const itemTwo = new FilterItem();
        itemTwo.forceOpen = true;

        subject.addFilterItems([ itemOne, itemTwo ]);

        // currentFilter emits the inital filter item on first call, we are not interested in that one.
        expect(cb.callCount).to.equal(2);
        expect(cb.getCall(1).args[0]).to.equal(itemTwo);
    });

    /** @see TBMAP-79 */
    it ('should merge paths of 2 markers with same id', done => {
        const first = new Marker('foo');
        first.paths = [ '/first' ];
        first.coordinates = { latitude: 20, longitude: 20 };
        const second = new Marker('foo');
        second.paths = [ '/second' ];
        second.coordinates = { latitude: 20, longitude: 20 };

        subject.addMarkers([ first, second ]);
        subject.allMarkers(markers => {
            expect(markers.foo.paths.length).to.equal(2);
            expect(markers.foo.paths).to.contain('/first');
            expect(markers.foo.paths).to.contain('/second');
            done();
        });
    });

});
