var assert = require('assert');
var sinon = require('sinon');

var DocumentContext = require('../src/documentContext');
var PageElementWriter = require('../src/pageElementWriter');

describe('PageElementWriter', function() {
	var pew, ctx, tracker, pageSize;

	var DOCUMENT_WIDTH = 600;
	var DOCUMENT_HEIGHT = 1100;
	var DOCUMENT_ORIENTATION = 'portrait';


	var MARGINS = {
		left: 40,
		right: 60,
		top: 30,
		bottom: 70
	};

	AVAILABLE_HEIGHT = 1000;
	AVAILABLE_WIDTH = 500;

	function buildLine(height, alignment, x, y) {
		return {
			getHeight: function() { return height; },
			getWidth: function() { return 60; },
			inlines: [
				{
					alignment: alignment,
					x: 0,
				},
				{
					x: 30,
				},
				{
					x: 50
				}
			],
			x: x,
			y: y
		};
	}

	function addOneTenthLines(count) {
    var lastPosition;
		for(var i = 0; i < count; i++) {
			var line = buildLine(AVAILABLE_HEIGHT/10);
			lastPosition = pew.addLine(line);
		}
    return lastPosition;
	}

	function createRepeatable(marker, height) {
		var rep = { items: [] };
		rep.height = height;

		var repLine = buildLine(height);
		repLine.marker = marker;

		rep.items.push({
            type: 'line',
            item: repLine
        });
		return rep;
	}
	beforeEach(function() {
		pageSize = {width: DOCUMENT_WIDTH, height: DOCUMENT_HEIGHT, orientation: DOCUMENT_ORIENTATION};
    ctx = new DocumentContext(pageSize, MARGINS);
		tracker = { emit: sinon.spy() };
		pew = new PageElementWriter(ctx, tracker);
	});

	describe('addLine', function() {
		it('should add new lines if there\'s enough space left', function() {
			var position = addOneTenthLines(10);

			assert.equal(ctx.pages.length, 1);
			assert.deepEqual(position, {pageNumber:1,left:MARGINS.left, top: (9/10 * AVAILABLE_HEIGHT) + MARGINS.top, verticalRatio: 0.9, horizontalRatio: 0, pageOrientation: 'portrait', pageInnerHeight:1000, pageInnerWidth:500});
		});

		it('should add new pages if there\'s not enough space left', function() {
			var position = addOneTenthLines(11);

			assert.equal(ctx.pages.length, 2);
			assert.equal(ctx.pages[0].items.length, 10);
			assert.equal(ctx.pages[1].items.length, 1);
      assert.deepEqual(position, {pageNumber:2,left: MARGINS.left, top: MARGINS.top, verticalRatio: 0, horizontalRatio: 0, pageOrientation: 'portrait', pageInnerHeight:1000, pageInnerWidth:500});
		});

		it('should subtract line height from availableHeight when adding a line and update current y position', function() {
			pew.addLine(buildLine(40));

			assert.equal(ctx.y, MARGINS.top + 40);
			assert.equal(ctx.availableHeight, AVAILABLE_HEIGHT - 40);
		});

		it('should add repeatable fragments if they exist and a new page is created before adding the line', function() {
			addOneTenthLines(10);

			assert.equal(ctx.pages.length, 1);

			var rep = createRepeatable('rep', 30);
			pew.repeatables.push(rep);

			var anotherLine = buildLine(20);
			anotherLine.marker = 'another';
			pew.addLine(anotherLine);

			assert.equal(ctx.pages.length, 2);
			assert.equal(ctx.pages[1].items[0].item.marker, 'rep');
			assert.equal(ctx.pages[1].items[1].item.marker, 'another');
		});
	});

	describe('beginUnbreakableBlock', function() {
		it('should begin a new transaction only once', function() {
			assert(pew.transactionLevel === 0);
			pew.beginUnbreakableBlock();
			var transactionCtx = pew.transactionContext;

			pew.beginUnbreakableBlock();

			assert.equal(transactionCtx, pew.transactionContext);
		});

		it('should make all further calls to addLine add lines to the transaction context instead of the page', function(){
			pew.beginUnbreakableBlock();
			pew.addLine(buildLine(30));
			pew.addLine(buildLine(30));
			var uCtx = pew.writer.context;

			assert.equal(ctx.pages.length, 1);
			assert.equal(ctx.pages[0].items.length, 0);
			assert.equal(uCtx.pages.length, 1);
			assert.equal(uCtx.pages[0].items.length, 2);
		});
	});

	describe('commitUnbreakableBlock', function() {
		it('should commit transaction if it was called exactly the same number of time as beginUnbreakableBlock', function() {
			pew.beginUnbreakableBlock();
			pew.beginUnbreakableBlock();
			pew.addLine(buildLine(30));
			pew.commitUnbreakableBlock();

			assert.equal(ctx.pages.length, 1);
			assert.equal(ctx.pages[0].items.length, 0);
			pew.commitUnbreakableBlock();
			assert.equal(ctx.pages[0].items.length, 1);
		});

		it('should copy all elements to the current page if there\'s enough space for the whole block', function() {
			pew.addLine(buildLine(30));

			pew.beginUnbreakableBlock();
			pew.addLine(buildLine(30));
			pew.addLine(buildLine(30));

			assert.equal(ctx.pages[0].items.length, 1);

			pew.commitUnbreakableBlock();
			assert.equal(ctx.pages[0].items.length, 3);
		});

		it('should add a new page and copy elements there if there\'s not enough space on the current page', function() {
			addOneTenthLines(8);

			pew.beginUnbreakableBlock();

			addOneTenthLines(3);

			pew.commitUnbreakableBlock();

			assert.equal(ctx.pages.length, 2);
			assert.equal(ctx.pages[0].items.length, 8);
			assert.equal(ctx.pages[1].items.length, 3);
		});

		it('should move elements to the top of the page if they are added to a new page', function() {
			addOneTenthLines(8);

			pew.beginUnbreakableBlock();

			addOneTenthLines(3);

			pew.commitUnbreakableBlock();

			assert.equal(ctx.pages[1].items[0].item.x, MARGINS.left);
			assert.equal(ctx.pages[1].items[0].item.y, MARGINS.top);
			assert.equal(ctx.pages[1].items[1].item.x, MARGINS.left);
			assert.equal(ctx.pages[1].items[1].item.y, MARGINS.top + AVAILABLE_HEIGHT/10);
		});

		it('should add lines below any repeatable fragments if they exist and a new page is created', function() {
			addOneTenthLines(10);

			assert.equal(ctx.pages.length, 1);

			var rep = createRepeatable('rep', 50);
			pew.repeatables.push(rep);

			var anotherLine = buildLine(20);
			anotherLine.marker = 'another';
			pew.addLine(anotherLine);

			assert.equal(ctx.pages.length, 2);
			assert.equal(ctx.pages[1].items[0].item.marker, 'rep');
			assert.equal(ctx.pages[1].items[1].item.marker, 'another');
			assert.equal(ctx.pages[1].items[1].item.x, MARGINS.left);
			assert.equal(ctx.pages[1].items[1].item.y, MARGINS.top + 50);
		});

		it('should make all further calls to addLine add lines again to the page when transaction finishes', function() {
			pew.beginUnbreakableBlock();
			pew.commitUnbreakableBlock();
			pew.addLine(buildLine(30));

			assert.equal(ctx.pages.length, 1);
			assert.equal(ctx.pages[0].items.length, 1);
		});
	});

	describe('currentBlockToRepeatable', function() {
		it('should return a copy of all elements from unbreakableBlock', function() {
			pew.beginUnbreakableBlock();
			pew.addLine(buildLine(30));
			pew.addLine(buildLine(30));
			var rep = pew.currentBlockToRepeatable();
			pew.pushToRepeatables(rep);

			assert.equal(rep.items.length, 2);

			pew.addLine(buildLine(30));
			assert.equal(rep.items.length, 2);
		});
	});

	describe('creating a new page', function() {
		it('should add a repeatable fragment to the top', function() {
			addOneTenthLines(6);

			pew.beginUnbreakableBlock();
			var uCtx = pew.writer.context;

			addOneTenthLines(3);
			uCtx.pages[0].items.forEach(function(item) { item.item.marker = 'rep'; });
			var rep = pew.currentBlockToRepeatable();
			pew.pushToRepeatables(rep);
			pew.commitUnbreakableBlock();

			addOneTenthLines(3);

			assert.equal(ctx.pages.length, 2);
			assert.equal(ctx.pages[0].items.length, 10);
			assert.equal(ctx.pages[0].items[6].item.marker, 'rep');
			assert.equal(ctx.pages[0].items[7].item.marker, 'rep');
			assert.equal(ctx.pages[0].items[8].item.marker, 'rep');
			assert(!ctx.pages[0].items[9].item.marker);

			assert.equal(ctx.pages[1].items[0].item.marker, 'rep');
			assert.equal(ctx.pages[1].items[1].item.marker, 'rep');
			assert.equal(ctx.pages[1].items[2].item.marker, 'rep');
			assert.equal(ctx.pages[1].items[2].item.y, MARGINS.top + 2 * AVAILABLE_HEIGHT/10);
			assert(!ctx.pages[1].items[3].item.marker);
			assert.equal(ctx.pages[1].items[3].item.y, ctx.pages[1].items[2].item.y + AVAILABLE_HEIGHT/10);
		});

		it('should reserve space for repeatable fragment to the top when reusing page', function() {
			addOneTenthLines(6);

			pew.beginUnbreakableBlock();
			var uCtx = pew.writer.context;

			addOneTenthLines(3);
			uCtx.pages[0].items.forEach(function(item) { item.item.marker = 'rep'; });
			var rep = pew.currentBlockToRepeatable();
			pew.pushToRepeatables(rep);
			pew.commitUnbreakableBlock();


      ctx.pages.push({items: [], pageSize: pageSize});

			addOneTenthLines(2);

			assert.equal(ctx.pages.length, 2);

			assert.equal(ctx.pages[1].items[0].item.y, MARGINS.top + 3 * AVAILABLE_HEIGHT/10);
		});

		it('should add repeatable fragments in the same order they have been added to the repeatable fragments collection', function() {
			addOneTenthLines(9);
			pew.repeatables.push(createRepeatable('rep1', 50));
			pew.repeatables.push(createRepeatable('rep2', 60));
			addOneTenthLines(2);

			assert.equal(ctx.pages.length, 2);
			assert.equal(ctx.pages[1].items.length, 3);
			assert.equal(ctx.pages[1].items[0].item.marker, 'rep1');
			assert.equal(ctx.pages[1].items[0].item.y, MARGINS.top);
			assert.equal(ctx.pages[1].items[1].item.marker, 'rep2');
			assert.equal(ctx.pages[1].items[1].item.y, MARGINS.top + 50);
			assert(!ctx.pages[1].items[2].item.marker);
			assert.equal(ctx.pages[1].items[2].item.y, MARGINS.top + 50 + 60);
		});

		it('should switch width and height if page changes from portrait to landscape', function() {
			addOneTenthLines(6);
			assert.equal(ctx.getCurrentPage().pageSize.width, DOCUMENT_WIDTH);
			assert.equal(ctx.getCurrentPage().pageSize.height, DOCUMENT_HEIGHT);
			assert.equal(ctx.getCurrentPage().pageSize.orientation, DOCUMENT_ORIENTATION);

			pew.moveToNextPage('landscape');

			assert.equal(ctx.pages.length, 2);
			assert.equal(ctx.getCurrentPage().pageSize.width, DOCUMENT_HEIGHT);
			assert.equal(ctx.getCurrentPage().pageSize.height, DOCUMENT_WIDTH);
			assert.equal(ctx.getCurrentPage().pageSize.orientation, 'landscape');
		});

		it('should switch width and height if page changes from landscape to portrait', function() {
			ctx.getCurrentPage().pageSize.orientation = 'landscape';
			ctx.getCurrentPage().pageSize.width = DOCUMENT_WIDTH;
			ctx.getCurrentPage().pageSize.height = DOCUMENT_HEIGHT;
			
			addOneTenthLines(6);
			pew.moveToNextPage('portrait');

			assert.equal(ctx.pages.length, 2);
			assert.equal(ctx.getCurrentPage().pageSize.width, DOCUMENT_HEIGHT);
			assert.equal(ctx.getCurrentPage().pageSize.height, DOCUMENT_WIDTH);
			assert.equal(ctx.getCurrentPage().pageSize.orientation, 'portrait');
		});

		it('should not switch width and height if page changes from landscape to landscape', function() {
			ctx.pageOrientation = undefined;
			addOneTenthLines(6);
			pew.moveToNextPage('landscape');
			pew.moveToNextPage('landscape');

			assert.equal(ctx.pages.length, 3);
			assert.equal(ctx.getCurrentPage().pageSize.width, DOCUMENT_HEIGHT);
			assert.equal(ctx.getCurrentPage().pageSize.height, DOCUMENT_WIDTH);
			assert.equal(ctx.getCurrentPage().pageSize.orientation, 'landscape');
		});

		it('should create new page', function () {
			addOneTenthLines(1);
			pew.moveToNextPage();

			assert.equal(ctx.pages.length, 2);
			assert.equal(ctx.y, MARGINS.top);
			assert.equal(ctx.availableHeight, AVAILABLE_HEIGHT);
			assert.equal(ctx.availableWidth, AVAILABLE_WIDTH);
			assert.equal(tracker.emit.callCount, 2); // move to first page to write a line, and then move to next page
			assert.deepEqual(tracker.emit.getCall(1).args, ['pageChanged', {prevPage: 0, prevY: MARGINS.top + AVAILABLE_HEIGHT / 10, y: MARGINS.top}]);
		});

		it('should use existing page', function () {
			addOneTenthLines(1);
			ctx.pages.push({items: [], pageSize: pageSize});
			ctx.availableWidth = 'garbage';
			ctx.availableHeight = 'garbage';

			pew.moveToNextPage();

			assert.equal(ctx.page, 1);
			assert.equal(ctx.y, MARGINS.top);
			assert.equal(ctx.availableHeight, AVAILABLE_HEIGHT);
			assert.equal(ctx.availableWidth, AVAILABLE_WIDTH);
			assert.equal(tracker.emit.callCount, 2);
			assert.deepEqual(tracker.emit.getCall(1).args, ['pageChanged', {prevPage: 0, prevY: MARGINS.top + AVAILABLE_HEIGHT / 10, y: MARGINS.top}]);
		});
	});
});
