/**
 * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
 * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
 */

import DocumentFragment from '../../src/view/documentfragment';
import Element from '../../src/view/element';
import Node from '../../src/view/node';
import Text from '../../src/view/text';
import TextProxy from '../../src/view/textproxy';
import Document from '../../src/view/document';
import { StylesProcessor } from '../../src/view/stylesmap';

describe( 'DocumentFragment', () => {
	let document;

	beforeEach( () => {
		document = new Document( new StylesProcessor() );
	} );

	describe( 'constructor()', () => {
		it( 'should create DocumentFragment without children', () => {
			const fragment = new DocumentFragment( document );

			expect( fragment ).to.be.an.instanceof( DocumentFragment );
			expect( fragment.childCount ).to.equal( 0 );
		} );

		it( 'should create DocumentFragment  document,with child node', () => {
			const child = new Element( document, 'p' );
			const fragment = new DocumentFragment( document, child );

			expect( fragment.childCount ).to.equal( 1 );
			expect( fragment.getChild( 0 ) ).to.have.property( 'name' ).that.equals( 'p' );
		} );

		it( 'should create DocumentFragment  document,with multiple nodes', () => {
			const children = [ new Element( document, 'p' ), new Element( document, 'div' ) ];
			const fragment = new DocumentFragment( document, children );

			expect( fragment.childCount ).to.equal( 2 );
			expect( fragment.getChild( 0 ) ).to.have.property( 'name' ).that.equals( 'p' );
			expect( fragment.getChild( 1 ) ).to.have.property( 'name' ).that.equals( 'div' );
		} );
	} );

	describe( 'iterator', () => {
		it( 'should iterate over all nodes added to document fragment', () => {
			const children = [ new Element( document, 'p' ), new Element( document, 'div' ) ];
			const fragment = new DocumentFragment( document, children );

			const arr = Array.from( fragment );

			expect( arr.length ).to.equal( 2 );
			expect( arr[ 0 ] ).to.have.property( 'name' ).that.equals( 'p' );
			expect( arr[ 1 ] ).to.have.property( 'name' ).that.equals( 'div' );
		} );
	} );

	describe( 'getRoot', () => {
		it( 'should return document fragment', () => {
			const fragment = new DocumentFragment( document );

			expect( fragment.root ).to.equal( fragment );
		} );
	} );

	describe( 'isEmpty', () => {
		it( 'should return true if there are no children in document fragment', () => {
			const fragment = new DocumentFragment( document );

			expect( fragment.isEmpty ).to.be.true;
		} );

		it( 'should return false if there are children in document fragment', () => {
			const fragment = new DocumentFragment( document, [ new Element( document, 'p' ) ] );

			expect( fragment.isEmpty ).to.be.false;
		} );
	} );

	describe( 'is()', () => {
		let frag;

		before( () => {
			frag = new DocumentFragment( document );
		} );

		it( 'should return true for documentFragment', () => {
			expect( frag.is( 'documentFragment' ) ).to.be.true;
			expect( frag.is( 'view:documentFragment' ) ).to.be.true;
		} );

		it( 'should return false for other accept values', () => {
			expect( frag.is( 'node' ) ).to.be.false;
			expect( frag.is( 'view:node' ) ).to.be.false;
			expect( frag.is( 'text' ) ).to.be.false;
			expect( frag.is( 'textProxy' ) ).to.be.false;
			expect( frag.is( 'element' ) ).to.be.false;
			expect( frag.is( 'view:element' ) ).to.be.false;
			expect( frag.is( 'containerElement' ) ).to.be.false;
			expect( frag.is( 'attributeElement' ) ).to.be.false;
			expect( frag.is( 'uiElement' ) ).to.be.false;
			expect( frag.is( 'emptyElement' ) ).to.be.false;
			expect( frag.is( 'rootElement' ) ).to.be.false;
		} );
	} );

	describe( 'children manipulation methods', () => {
		let fragment, el1, el2, el3, el4;

		beforeEach( () => {
			fragment = new DocumentFragment( document );
			el1 = new Element( document, 'el1' );
			el2 = new Element( document, 'el2' );
			el3 = new Element( document, 'el3' );
			el4 = new Element( document, 'el4' );
		} );

		describe( 'insertion', () => {
			it( 'should insert children', () => {
				const count1 = fragment._insertChild( 0, [ el1, el3 ] );
				const count2 = fragment._insertChild( 1, el2 );

				expect( fragment.childCount ).to.equal( 3 );
				expect( fragment.getChild( 0 ) ).to.have.property( 'name' ).that.equals( 'el1' );
				expect( fragment.getChild( 1 ) ).to.have.property( 'name' ).that.equals( 'el2' );
				expect( fragment.getChild( 2 ) ).to.have.property( 'name' ).that.equals( 'el3' );
				expect( count1 ).to.equal( 2 );
				expect( count2 ).to.equal( 1 );
			} );

			it( 'should accept strings', () => {
				fragment._insertChild( 0, 'abc' );

				expect( fragment.childCount ).to.equal( 1 );
				expect( fragment.getChild( 0 ) ).to.have.property( 'data' ).that.equals( 'abc' );

				fragment._removeChildren( 0, 1 );
				fragment._insertChild( 0, [ new Element( document, 'p' ), 'abc' ] );

				expect( fragment.childCount ).to.equal( 2 );
				expect( fragment.getChild( 1 ) ).to.have.property( 'data' ).that.equals( 'abc' );
			} );

			it( 'should append children', () => {
				const count1 = fragment._insertChild( 0, el1 );
				const count2 = fragment._appendChild( el2 );
				const count3 = fragment._appendChild( el3 );

				expect( fragment.childCount ).to.equal( 3 );
				expect( fragment.getChild( 0 ) ).to.have.property( 'name' ).that.equals( 'el1' );
				expect( fragment.getChild( 1 ) ).to.have.property( 'name' ).that.equals( 'el2' );
				expect( fragment.getChild( 2 ) ).to.have.property( 'name' ).that.equals( 'el3' );
				expect( count1 ).to.equal( 1 );
				expect( count2 ).to.equal( 1 );
				expect( count3 ).to.equal( 1 );
			} );

			it( 'should fire change event when inserting', done => {
				fragment.once( 'change:children', ( event, node ) => {
					expect( node ).to.equal( fragment );
					done();
				} );

				fragment._insertChild( 0, el1 );
			} );

			it( 'should fire change event when appending', done => {
				fragment.once( 'change:children', ( event, node ) => {
					expect( node ).to.equal( fragment );
					done();
				} );

				fragment._appendChild( el1 );
			} );

			it( 'should accept and correctly handle text proxies', () => {
				const frag = new DocumentFragment( document );
				const text = new Text( document, 'abcxyz' );
				const textProxy = new TextProxy( text, 2, 3 );

				frag._insertChild( 0, textProxy );

				expect( frag.childCount ).to.equal( 1 );
				expect( frag.getChild( 0 ) ).to.be.instanceof( Text );
				expect( frag.getChild( 0 ).data ).to.equal( 'cxy' );
			} );
		} );

		describe( 'getChildIndex', () => {
			it( 'should return child index', () => {
				fragment._appendChild( el1 );
				fragment._appendChild( el2 );
				fragment._appendChild( el3 );

				expect( fragment.childCount ).to.equal( 3 );
				expect( fragment.getChildIndex( el1 ) ).to.equal( 0 );
				expect( fragment.getChildIndex( el2 ) ).to.equal( 1 );
				expect( fragment.getChildIndex( el3 ) ).to.equal( 2 );
			} );
		} );

		describe( 'getChildren', () => {
			it( 'should renturn children iterator', () => {
				fragment._appendChild( el1 );
				fragment._appendChild( el2 );
				fragment._appendChild( el3 );

				const expected = [ el1, el2, el3 ];
				let i = 0;

				for ( const child of fragment.getChildren() ) {
					expect( child ).to.equal( expected[ i ] );
					i++;
				}

				expect( i ).to.equal( 3 );
			} );
		} );

		describe( '_removeChildren', () => {
			it( 'should remove children', () => {
				fragment._appendChild( el1 );
				fragment._appendChild( el2 );
				fragment._appendChild( el3 );
				fragment._appendChild( el4 );

				fragment._removeChildren( 1, 2 );

				expect( fragment.childCount ).to.equal( 2 );
				expect( fragment.getChild( 0 ) ).to.have.property( 'name' ).that.equals( 'el1' );
				expect( fragment.getChild( 1 ) ).to.have.property( 'name' ).that.equals( 'el4' );

				expect( el1.parent ).to.equal( fragment );
				expect( el2.parent ).to.be.null;
				expect( el3.parent ).to.be.null;
				expect( el4.parent ).equal( fragment );
			} );

			it( 'should remove one child when second parameter is not specified', () => {
				fragment._appendChild( el1 );
				fragment._appendChild( el2 );
				fragment._appendChild( el3 );

				const removed = fragment._removeChildren( 1 );

				expect( fragment.childCount ).to.equal( 2 );
				expect( fragment.getChild( 0 ) ).to.have.property( 'name' ).that.equals( 'el1' );
				expect( fragment.getChild( 1 ) ).to.have.property( 'name' ).that.equals( 'el3' );

				expect( removed.length ).to.equal( 1 );
				expect( removed[ 0 ] ).to.have.property( 'name' ).that.equals( 'el2' );
			} );

			it( 'should fire change event', done => {
				fragment._appendChild( el1 );

				fragment.once( 'change:children', ( event, node ) => {
					expect( node ).to.equal( fragment );
					done();
				} );

				fragment._removeChildren( 0 );
			} );
		} );
	} );

	describe( 'node methods when inserted to fragment', () => {
		it( 'index should return proper value', () => {
			const node1 = new Node( document );
			const node2 = new Node( document );
			const node3 = new Node( document );
			const fragment = new DocumentFragment( document, [ node1, node2, node3 ] );

			expect( node1.index ).to.equal( 0 );
			expect( node2.index ).to.equal( 1 );
			expect( node3.index ).to.equal( 2 );
			expect( node1.parent ).to.equal( fragment );
			expect( node2.parent ).to.equal( fragment );
			expect( node3.parent ).to.equal( fragment );
		} );

		it( 'nextSibling should return proper node', () => {
			const node1 = new Node( document );
			const node2 = new Node( document );
			const node3 = new Node( document );
			new DocumentFragment( document, [ node1, node2, node3 ] ); // eslint-disable-line no-new

			expect( node1.nextSibling ).to.equal( node2 );
			expect( node2.nextSibling ).to.equal( node3 );
			expect( node3.nextSibling ).to.be.null;
		} );

		it( 'previousSibling should return proper node', () => {
			const node1 = new Node( document );
			const node2 = new Node( document );
			const node3 = new Node( document );
			new DocumentFragment( document, [ node1, node2, node3 ] ); // eslint-disable-line no-new

			expect( node1.previousSibling ).to.be.null;
			expect( node2.previousSibling ).to.equal( node1 );
			expect( node3.previousSibling ).to.equal( node2 );
		} );

		it( '_remove() should remove node from fragment', () => {
			const node1 = new Node( document );
			const node2 = new Node( document );
			const node3 = new Node( document );
			const fragment = new DocumentFragment( document, [ node1, node2, node3 ] );

			node1._remove();
			node3._remove();

			expect( fragment.childCount ).to.equal( 1 );
			expect( node1.parent ).to.be.null;
			expect( node3.parent ).to.be.null;
			expect( fragment.getChild( 0 ) ).to.equal( node2 );
		} );
	} );
} );
