text: 'use strict';
  
  var isUndefined = require('./helpers').isUndefined;
  var ElementWriter = require('./elementWriter');
  
  /**
   * Creates an instance of PageElementWriter - an extended ElementWriter
   * which can handle:
   * - page-breaks (it adds new pages when there's not enough space left),
   * - repeatable fragments (like table-headers, which are repeated everytime
   *                         a page-break occurs)
   * - transactions (used for unbreakable-blocks when we want to make sure
   *                 whole block will be rendered on the same page)
   */
  function PageElementWriter(context, tracker) {
  	this.transactionLevel = 0;
  	this.repeatables = [];
  	this.tracker = tracker;
  	this.writer = new ElementWriter(context, tracker);
  }
  
  function fitOnPage(self, addFct) {
  	var position = addFct(self);
  	if (!position) {
  		self.moveToNextPage();
  		position = addFct(self);
  	}
  	return position;
  }
  
  PageElementWriter.prototype.addLine = function (line, dontUpdateContextPosition, index) {
  	return fitOnPage(this, function (self) {
  		return self.writer.addLine(line, dontUpdateContextPosition, index);
  	});
  };
  
  PageElementWriter.prototype.addImage = function (image, index) {
  	return fitOnPage(this, function (self) {
  		return self.writer.addImage(image, index);
  	});
  };
  
  PageElementWriter.prototype.addSVG = function (image, index) {
  	return fitOnPage(this, function (self) {
  		return self.writer.addSVG(image, index);
  	});
  };
  
  PageElementWriter.prototype.addQr = function (qr, index) {
  	return fitOnPage(this, function (self) {
  		return self.writer.addQr(qr, index);
  	});
  };
  
  PageElementWriter.prototype.addVector = function (vector, ignoreContextX, ignoreContextY, index) {
  	return this.writer.addVector(vector, ignoreContextX, ignoreContextY, index);
  };
  
  PageElementWriter.prototype.beginClip = function (width, height) {
  	return this.writer.beginClip(width, height);
  };
  
  PageElementWriter.prototype.endClip = function () {
  	return this.writer.endClip();
  };
  
  PageElementWriter.prototype.alignCanvas = function (node) {
  	this.writer.alignCanvas(node);
  };
  
  PageElementWriter.prototype.addFragment = function (fragment, useBlockXOffset, useBlockYOffset, dontUpdateContextPosition) {
  	if (!this.writer.addFragment(fragment, useBlockXOffset, useBlockYOffset, dontUpdateContextPosition)) {
  		this.moveToNextPage();
  		this.writer.addFragment(fragment, useBlockXOffset, useBlockYOffset, dontUpdateContextPosition);
  	}
  };
  
  PageElementWriter.prototype.moveToNextPage = function (pageOrientation) {
  
  	var nextPage = this.writer.context.moveToNextPage(pageOrientation);
  
  	// moveToNextPage is called multiple times for table, because is called for each column
  	// and repeatables are inserted only in the first time. If columns are used, is needed
  	// call for table in first column and then for table in the second column (is other repeatables).
  	this.repeatables.forEach(function (rep) {
  		if (isUndefined(rep.insertedOnPages[this.writer.context.page])) {
  			rep.insertedOnPages[this.writer.context.page] = true;
  			this.writer.addFragment(rep, true);
  		} else {
  			this.writer.context.moveDown(rep.height);
  		}
  	}, this);
  
  	this.writer.tracker.emit('pageChanged', {
  		prevPage: nextPage.prevPage,
  		prevY: nextPage.prevY,
  		y: this.writer.context.y
  	});
  };
  
  PageElementWriter.prototype.beginUnbreakableBlock = function (width, height) {
  	if (this.transactionLevel++ === 0) {
  		this.originalX = this.writer.context.x;
  		this.writer.pushContext(width, height);
  	}
  };
  
  PageElementWriter.prototype.commitUnbreakableBlock = function (forcedX, forcedY) {
  	if (--this.transactionLevel === 0) {
  		var unbreakableContext = this.writer.context;
  		this.writer.popContext();
  
  		var nbPages = unbreakableContext.pages.length;
  		if (nbPages > 0) {
  			// no support for multi-page unbreakableBlocks
  			var fragment = unbreakableContext.pages[0];
  			fragment.xOffset = forcedX;
  			fragment.yOffset = forcedY;
  
  			//TODO: vectors can influence height in some situations
  			if (nbPages > 1) {
  				// on out-of-context blocs (headers, footers, background) height should be the whole DocumentContext height
  				if (forcedX !== undefined || forcedY !== undefined) {
  					fragment.height = unbreakableContext.getCurrentPage().pageSize.height - unbreakableContext.pageMargins.top - unbreakableContext.pageMargins.bottom;
  				} else {
  					fragment.height = this.writer.context.getCurrentPage().pageSize.height - this.writer.context.pageMargins.top - this.writer.context.pageMargins.bottom;
  					for (var i = 0, l = this.repeatables.length; i < l; i++) {
  						fragment.height -= this.repeatables[i].height;
  					}
  				}
  			} else {
  				fragment.height = unbreakableContext.y;
  			}
  
  			if (forcedX !== undefined || forcedY !== undefined) {
  				this.writer.addFragment(fragment, true, true, true);
  			} else {
  				this.addFragment(fragment);
  			}
  		}
  	}
  };
  
  PageElementWriter.prototype.currentBlockToRepeatable = function () {
  	var unbreakableContext = this.writer.context;
  	var rep = { items: [] };
  
  	unbreakableContext.pages[0].items.forEach(function (item) {
  		rep.items.push(item);
  	});
  
  	rep.xOffset = this.originalX;
  
  	//TODO: vectors can influence height in some situations
  	rep.height = unbreakableContext.y;
  
  	rep.insertedOnPages = [];
  
  	return rep;
  };
  
  PageElementWriter.prototype.pushToRepeatables = function (rep) {
  	this.repeatables.push(rep);
  };
  
  PageElementWriter.prototype.popFromRepeatables = function () {
  	this.repeatables.pop();
  };
  
  PageElementWriter.prototype.context = function () {
  	return this.writer.context;
  };
  
  module.exports = PageElementWriter;
  