import { PriorityQueue } from '../src/utilities/PriorityQueue.js';

const nextFrame = () => new Promise( resolve => requestAnimationFrame( resolve ) );
const nextTick = () => new Promise( resolve => process.nextTick( resolve ) );

describe( 'PriorityQueue', () => {

	it( 'should run jobs automatically in the correct order.', async () => {

		const queue = new PriorityQueue();
		queue.maxJobs = 6;
		queue.priorityCallback = item => item.priority;
		queue.add( { priority : 6 }, () => new Promise( () => {} ) );
		queue.add( { priority : 3 }, () => new Promise( () => {} ) );
		queue.add( { priority : 4 }, () => new Promise( () => {} ) );
		queue.add( { priority : 0 }, () => new Promise( () => {} ) );
		queue.add( { priority : 8 }, () => new Promise( () => {} ) );
		queue.add( { priority : 2 }, () => new Promise( () => {} ) );
		queue.add( { priority : 1 }, () => new Promise( () => {} ) );

		await nextFrame();

		expect( queue.items.map( item => item.priority ) ).toEqual( [ 0 ] );
		expect( queue.currJobs ).toEqual( 6 );

	} );

	it( 'should run jobs in the correct order.', async () => {

		const result = [];
		const cb = item => new Promise( resolve => {

			result.push( item.priority );
			resolve();

		} );

		const queue = new PriorityQueue();
		queue.maxJobs = 1;
		queue.priorityCallback = item => item.priority;
		queue.add( { priority : 6 }, cb );
		queue.add( { priority : 3 }, cb );
		queue.add( { priority : 4 }, cb );
		queue.add( { priority : 0 }, cb );
		queue.add( { priority : 8 }, cb );
		queue.add( { priority : 2 }, cb );
		queue.add( { priority : 1 }, cb );
		expect( queue.items.length ).toEqual( queue.callbacks.size );

		await nextTick();

		expect( result ).toEqual( [ 8, 6, 4, 3, 2, 1, 0 ] );
		expect( queue.items.length ).toEqual( queue.callbacks.size );

	} );

	it( 'should remove an item from the queue correctly.', () => {

		const A = { priority : 0 };
		const B = { priority : 1 };
		const C = { priority : 2 };
		const D = { priority : 3 };
		const queue = new PriorityQueue();
		queue.priorityCallback = item => item.priority;
		queue.add( A, () => new Promise( () => {} ) );
		queue.add( B, () => new Promise( () => {} ) );
		queue.add( C, () => new Promise( () => {} ) );
		queue.add( D, () => new Promise( () => {} ) );
		queue.sort();

		expect( queue.items ).toEqual( [ A, B, C, D ] );

		queue.remove( C );
		expect( queue.items ).toEqual( [ A, B, D ] );

		queue.remove( A );
		expect( queue.items ).toEqual( [ B, D ] );

		queue.remove( B );
		expect( queue.items ).toEqual( [ D ] );

		queue.remove( D );
		expect( queue.items ).toEqual( [] );
		expect( queue.items.length ).toEqual( queue.callbacks.size );

	} );

	it( 'should automatically run new jobs when one is finished.', async () => {

		let called = 0;
		let resolveFunc = null;
		const queue = new PriorityQueue();
		queue.maxJobs = 1;
		queue.priorityCallback = item => item.priority;

		queue.add( { priority : 1 }, () => new Promise( resolve => {

			resolveFunc = resolve;
			called ++;

		} ) );

		queue.add( { priority : 0 }, () => new Promise( () => {

			called ++;

		} ) );

		expect( queue.currJobs ).toEqual( 0 );

		await nextFrame();

		expect( queue.currJobs ).toEqual( 1 );
		expect( resolveFunc ).not.toEqual( null );
		expect( called ).toEqual( 1 );

		resolveFunc();
		await nextFrame();

		expect( queue.currJobs ).toEqual( 1 );
		expect( called ).toEqual( 2 );

	} );

	it( 'should fire the callback with the item and priority.', async () => {

		const A = { priority : 100 };
		const queue = new PriorityQueue();
		queue.priorityCallback = item => item.priority;

		queue.add( A, item => new Promise( () => {

			expect( item ).toEqual( A );

		} ) );

		await nextFrame();

	} );

	it( 'should return a promise that resolves from the add function.',  async () => {

		const queue = new PriorityQueue();
		queue.priorityCallback = item => item.priority;

		let result = null;
		queue.add( { priority : 0 }, item => Promise.resolve( 1000 ) ).then( res => result = res );

		expect( result ).toEqual( null );

		await nextFrame();

		expect( result ).toEqual( 1000 );

	} );

	it( 'should not automatically run if autoUpdate is false.', async () => {

		const queue = new PriorityQueue();
		queue.priorityCallback = () => 0;
		queue.autoUpdate = false;
		queue.maxJobs = 1;

		queue.add( {}, async () => {} );
		queue.add( {}, async () => {} );

		expect( queue.items ).toHaveLength( 2 );

		await nextTick();

		expect( queue.items ).toHaveLength( 2 );

		queue.scheduleJobRun();
		await nextTick();

		expect( queue.items ).toHaveLength( 1 );

		await nextTick();

		expect( queue.items ).toHaveLength( 1 );

		queue.scheduleJobRun();
		await nextTick();

		expect( queue.items ).toHaveLength( 0 );

	});

} );
