/**
 * @author Sergey Chikuyonok (sc@design.ru)
 * @copyright Art.Lebedev Studio (http://www.artlebedev.ru)
 */

/*
 * @include "/ictinus/src/ictinus.js"
 */

/**
 * Класс, содержащий описание формы для рисования. Он не предназначен для 
 * создания напрямую через <code>new IctinusShape()</code>, его нужно создавать
 * через <code>ictinus.shape()</code>, так как при инициализации классу нужен 
 * адаптер, через который будет рисоваться форма.
 * @param {Object} adapter Адаптер для рисования векторных форм (canvas, vml...)
 * @param {Array} [paths] Пути, описывающие контур (см. {@link ictinus.draw_type})  
 */
function IctinusShape(adapter, paths){
	if (!adapter || !adapter.draw) {
		throw new Error('Класс не предназначен для вызова напрямую через new IctinusShape(), используй ictinus.shape()');
	}
	
	/** @private */
	this._adapter = adapter;
	
	/** 
	 * Кэш, устанавливаемый адаптером, для ускорения перерисовки
	 * @private 
	 */
	this._cache = {};
	
	/** 
	 * Сессионные параметры,устанавливаемые на время рисования формы
	 * @private 
	 */
	this._session = {};
	
	/** 
	 * Векторные пути, описывающие форму
	 * @private 
	 */
	this.paths = paths || [];
	
	/** Толщина обводки, в пикселях */
	this.strokeWidth = 1;
	
	/** Цвет обводки */
	this.strokeColor = '#000000';
	
	/** Цвет заливки */
	this.fillColor = '#ff0000';
	
	/** Сетка масшатбирования (аналог 9-grid scale из Adobe Flash) */
	this.scaleGrid = {
		x1: 0,
		x2: 0,
		y1: 0,
		y2: 0
	};
	
	
	/** @private */
	this._width = 100;
	/** @private */
	this._height = 100;
	
	/** 
	 * Область, в которой находится контент. Используется внешними механизмами
	 * для определения масштабирования (scaleX и scaleY) формы под новый 
	 * контент, а также для позиционирования контента относительно формы
	 */
	this.contentBox = {
		x: 0,
		y: 0,
		width: 0,
		height: 0
	};
	
	/** @private */
	this._scaleX = 1;
	/** @private */
	this._scaleY = 1;
}

IctinusShape.prototype = {
	/**
	 * Возвращает пути, описывающие форму. Важно помнить, что это не ссылка на
	 * private-свойство <code>this.paths</code>, а именно новый массив путей,
	 * которые были пересчитаны с учетом масштаба и сетки масштабирования 
	 * @return {Array}
	 */
	getPaths: function(){
		
		if (this._session.paths) {
			/*
			 * В метода draw() отдали другие пути — вернем их
			 */
			return this._session.paths;
		}
		
		var result = [];
		
		/**
		 * Делает копию объекта
		 * @param {Object} obj
		 * @return {Object}
		 */
		function copy(obj){
			var result = {};
			for (var a in obj) if (obj.hasOwnProperty(a)) {
				result[a] = obj[a];
			}
			
			return result;
		}
		
		/**
		 * Выполняет на каждом элементе объекта <code>obj</code> функцию 
		 * <code>func</code> и присваивает элементу объекта результат работы 
		 * функции
		 * @param {Object} obj
		 * @param {Function} func
		 * @param {String} [token] Символы, на которые должно оканчиваться название свойства, для которого нужно выполнить маппинг
		 */
		function map(obj, func, token) {
			for (var p in obj) 
				if (obj.hasOwnProperty(p)) {
					if ((!token || (p != 'type' && p.indexOf(token) != -1))) {
						obj[p] = func(obj[p]);
					}
				}
		}
		
		var sx = this.scaleX();
		var sy = this.scaleY();
		
		// считаем, на сколько изменились габариты формы и сетки
		var dw = this._width * (sx - 1);
		var dh = this._height * (sy - 1);
		
		var mapFunc = {
			moveX: function(value){
				return value + dw;
			},
			
			moveY: function(value){
				return value + dh;
			},
			
			scaleX: function(value){
				return value * sx;
			},
			
			scaleY: function(value){
				return value * sy;
			}
		};
		
		if (!this.isEmptyGrid()) {
			// считаем пути согласно сетке масштабирования
			
			// FIXME сам по себе метод работает не совсем правильно: не корректно масштабируются кривые Безье, если из контрольне точки находятся в другой ячейке
			
			/*
			 * Сетка масштабирования по сути представляет собой матрицу 3х3, для
			 * каждой фчейки которой соответствует свой способ масштабирования:
			 * — для 11, 13, 31, 33 масштабирование не применяется
			 * — для 12, 32 масштабирование по горизонтали
			 * — для 21, 23 масштабирование по вертикали
			 * — для 22 масштабирование по горизонтали и вертикали
			 * 
			 * Алгоритм выглядит так: сначала все компоненты рисования 
			 * (ictinus.draw_type) виртуально распихаем по этим ячейкам, затем 
			 * масштабировуем все все координаты в нужных ячейках
			 */
			
			// !!! наверно, не нужно копировать
			var newGrid = copy(this.scaleGrid);
			
			/**
			 * Возвращает ячейку сетки масштабирования (11, 31, 22 и т.д.), 
			 * в которой находится точка с указанными координатами
			 * @param {Number} x
			 * @param {Number} y
			 * @return {Number}
			 */
			function getCell(x, y) {
				var row, col;
				
				if (x <= newGrid.x1)
					col = '1';
				else if (x >= newGrid.x2)
					col = '3';
				else
					col = '2';
				
				if (y <= newGrid.y1)
					row = '1';
				else if (y >= newGrid.y2)
					row = '3';
				else
					row = '2';
				return parseInt(row + col, 10);
			}
			
			for (var i = 0; i < this.paths.length; i++) {
				var p = copy(this.paths[i]);
				var cell;
				
				// у оператора CLOSE нет координат
				if (p.type == ictinus.DRAW_TYPE.CLOSE) {
					result.push(p);
					continue;
				}
				
				switch (getCell(p.x, p.y)) {
					case 12:
						map(p, mapFunc.scaleX, 'x');
						break;
					case 13:
						map(p, mapFunc.moveX, 'x');
						break;
					case 21:
						map(p, mapFunc.scaleY, 'y');
						break;
					case 22:
						map(p, mapFunc.scaleX, 'x');
						map(p, mapFunc.scaleY, 'y');
						break;
					case 23:
						map(p, mapFunc.moveX, 'x');
						map(p, mapFunc.scaleY, 'y');
						break;
					case 31:
						map(p, mapFunc.moveY, 'y');
						break;
					case 32:
						map(p, mapFunc.scaleX, 'x');
						map(p, mapFunc.moveY, 'y');
						break;
					case 33:
						map(p, mapFunc.moveX, 'x');
						map(p, mapFunc.moveY, 'y');
						break;
				}
				
				result.push(p);
				
			}
		} else {
			// просто масштабируем все точки
			for (var i = 0; i < this.paths.length; i++) {
				var p = copy(this.paths[i]);
				map(p, mapFunc.scaleX, 'x');
				map(p, mapFunc.scaleY, 'y');
				result.push(p);
			}
		}
		
		return result;
	},
	
	/**
	 * Добавляет путь к описанию формы
	 * @param {ictunus.draw_type.line()} Один или несколько путей
	 */
	addPath: function(path){
		for (var i = 0; i < arguments.length; i++) {
			this.paths.push(arguments[i]);
		}
	},
	
	/**
	 * Устанавливает или возвращает масштаб по горизонтали
	 * @param {Number|String} [value] Новое значение масштаба
	 * @return {Number}
	 */
	scaleX: function(value){
		if (arguments.length) {
			this._scaleX = parseFloat(value, 10);
		}
		return this._scaleX;
	},
	
	/**
	 * Устанавливает или возвращает масштаб по вертикали
	 * @param {Number|String} [value] Новое значение масштаба
	 * @return {Number}
	 */
	scaleY: function(value){
		if (arguments.length) {
			this._scaleY = parseFloat(value, 10);
		}
		return this._scaleY;
	},
	
	/**
	 * Устанавливает или возвращает ширину
	 * @param {Number|String} [value] Новое значение ширины
	 * @return {Number}
	 */
	width: function(value){
		if (arguments.length) {
			this._width = parseFloat(value, 10);
		}
		return this._width * this.scaleX();
	},
	
	/**
	 * Устанавливает или возвращает высоту
	 * @param {Number|String} [value] Новое значение высоты
	 * @return {Number}
	 */
	height: function(value){
		if (arguments.length) {
			this._height = parseFloat(value, 10);
		}
		return this._height * this.scaleY();
	},
	
	/**
	 * Устанавливает сетку масштабирования
	 * @param {Number} x1
	 * @param {Number} x2
	 * @param {Number} y1
	 * @param {Number} y2
	 */
	setScaleGrid: function(x1, x2, y1, y2){
		this.scaleGrid.x1 = Math.min(x1, x2);
		this.scaleGrid.x2 = Math.max(x1, x2);
		this.scaleGrid.y1 = Math.min(y1, y2);
		this.scaleGrid.y2 = Math.max(y1, y2);
	},
	
	/**
	 * Проверяет, является ли сетка масштабирования пустой. Используется для 
	 * проверки необходимости применения такого масштабирования.
	 * @return {Boolean}
	 */
	isEmptyGrid: function(){
		var g = this.scaleGrid;
		return (!g.x1 && !g.x2 && !g.y1 && !g.y2);
	},
	
	/**
	 * Отрисовывет текущую форму. Если передан параметр <code>paths</code>, то
	 * именно этот массив инструкций по рисованию будет использоваться для 
	 * отрисовки, а не внутренний массив. Это полезно использовать, например, 
	 * при создании анимаций, когда нет простого и понятного способа сохранить
	 * оригинал формы и при этом учитывать сетку масшатбирования
	 * @param {Element} target В каком элементе рисовать
	 * @param {Array} [paths] Инструкции для рисования 
	 */
	draw: function (target, paths) {
		this._session.paths = paths;
		ictinus.draw(target, this);
		delete this._session.paths;
	},
	
	/**
	 * Украшает. Если передан параметр <code>paths</code>, то
	 * именно этот массив инструкций по рисованию будет использоваться для 
	 * отрисовки, а не внутренний массив. Это полезно использовать, например, 
	 * при создании анимаций, когда нет простого и понятного способа сохранить
	 * оригинал формы и при этом учитывать сетку масшатбирования
	 * @param {Element} img Картинка, которую нужно украсить
	 * @param {Array} [paths] Инструкции для рисования 
	 */
	decorate: function (img, paths) {
		this._session.paths = paths;
		var cv = ictinus.decorate(img, this);
		delete this._session.paths;
		return cv;
	}
};
