/*
 * @include "/js-libs/canvas-doc.js"
 * @include "/ictinus/src/IctinusShape.js"
 */
 
/**
 * Декоратор картинок. Использует Canvas или VML для рисования декораций вокруг
 * картинок, например, уголки причудливой формы. 
 * @author Sergey Chikuyonok (sc@design.ru)
 * @copyright Art.Lebedev Studio (http://www.artlebedev.ru)
 */
var ictinus = (function(){
	
	var userAgent = navigator.userAgent.toLowerCase();

	/** @type {Boolean} Говорит, что текущий браузер — Internet Explorer */
	var is_msie = /msie/.test( userAgent ) && !/opera/.test( userAgent );
	
	/** Варианты рисования */
	var DRAW_TYPE = {
		/** Переместиться в указанную точку */
		MOVETO: 'moveto',
		
		/** Закрыть текущий подпуть и начать новый */
		CLOSE: 'close',
		
		/** Прямая линия */
		LINE: 'line',
		
		/** Кривая Безье, дополнительно содержит две контрольные точки */
		BEZIER: 'bezier',
		
		/** Квадратичная кривая Безье, дополнительно содержит контрольныeую точку */
		QUADRATIC: 'quad'
	};
	
	/**
	 * Параметры рисования декорации по умолчанию
	 */
	var default_params = {
		line_width: 1,
		line_color: '#ff0000'
	};
	
	/**
	 * Объединяет несколько объектов-хэшей в один
	 * @param {Object} ... Объекты, которые нужно объединить
	 * @return {Object}
	 */
	function merge(){
		var result = {};
		for (var i = 0; i < arguments.length; i++) {
			var obj = arguments[i];
			if (!obj)
				continue;
				
			for (var a in obj)
				result[a] = obj[a];
		}
		
		return result;
	}
	
	/**
	 * Проверяет, существует ли указанный класс у элемента
	 * @param {HTMLElement} elem
	 * @param {String} className
	 */
	function hasClass(elem, className){
		return elem.nodeType == 1 && elem.className.indexOf(className) != -1;
	}
	
	/** Команда рисования */
	function command(type){
		return {type: type};
	}
	
	/**
	 * Создает объект, характеризующий рисование обычной линией
	 * @param {Number} x X-координата точки
	 * @param {Number} y Y-координата точки
	 */
	function line(x, y){
		return merge(command(DRAW_TYPE.LINE), {
			x: x,
			y: y
		});
	}
	
	/**
	 * Перемещение пера в указанной точку
	 * @param {Number} x X-координата точки
	 * @param {Number} y Y-координата точки
	 */
	function move(x, y){
		return merge(command(DRAW_TYPE.MOVETO), {
			x: x,
			y: y
		});
	}
	
	/**
	 * Создает объект, характеризующий кривую Безье
	 * @param {Number} x X-координата точки
	 * @param {Number} y Y-координата точки
	 * @param {Number} cpx1 X-координата первой контрольной точки
	 * @param {Number} cpy1 Y-координата первой контрольной точки
	 * @param {Number} cpx2 X-координата второй контрольной точки
	 * @param {Number} cpy2 Y-координата второй контрольной точки
	 */
	function bezier(x, y, cpx1, cpy1, cpx2, cpy2){
		return merge( line(x, y), {
			type: DRAW_TYPE.BEZIER,
			cpx1: cpx1,
			cpy1: cpy1,
			cpx2: cpx2,
			cpy2: cpy2
		} );
	}
	
	/**
	 * Создает объект, характеризующий квадратичную кривую Безье
	 * @param {Number} x X-координата точки
	 * @param {Number} y Y-координата точки
	 * @param {Number} cpx X-координата контрольной точки
	 * @param {Number} cpy Y-координата контрольной точки
	 */
	function quadratic(x, y, cpx, cpy){
		return merge( line(x, y), {
			type: DRAW_TYPE.QUADRATIC,
			cpx: cpx,
			cpy: cpy
		} );
	}
	
	/**
	 * Функция читает параметры декорации и при необходимости подставляет значения по умолчанию
	 * @param {Object} params
	 * @return {Object}
	 */
	function readParams(params){
		return merge(default_params, params);
	}
	
	/**
	 * Адаптер для рисования через canvas
	 */
	function canvasAdapter(){
		var created_canvas;
		/**
		 * Создает холст для рисования для переданного объекта. Элемент холста
		 * будет добавлен <i>перед</i> элементом <code>elem</code>, если элемент
		 * является картинкой (<code>tagName == 'IMG'</code>), либо добавлен 
		 * в сам элемент. Если холст уже был создан, он заново не создается, 
		 * а возвращается ссылка на уже созданный
		 * @param {Element} elem Элемент, для которого нужно создать холст
		 * @return {CanvasRenderingContext2D}
		 */
		function createCanvas(elem){
			/** @type {HTMLCanvasElement} */
			var canvas;
			/*
			if (elem.className.indexOf('ictinus-init') != -1) {
				// ищем созданный холст
				if (elem.nodeName == 'IMG' && elem.previousSibling.nodeName == 'CANVAS') {
					canvas = elem.previousSibling;
				} else if (elem.getElementsByTagName('canvas').length) {
					canvas = elem.getElementsByTagName('canvas')[0];
				}
			}
			*/
			//if (!canvas) {
				canvas = document.createElement('canvas');
				
				// поправка в 1 пиксель нужна для того, чтобы точнее рисовались нечетные линии
				canvas.width = elem.offsetWidth + 1;
				canvas.height = elem.offsetHeight + 1;
				canvas.className = 'ictinus';
				
				if (elem.tagName == 'IMG') {
					elem.parentNode.insertBefore(canvas, elem);
				} else {
					elem.appendChild(canvas);
				}
				
				elem.className = (elem.className ? elem.className + ' ' : '') + 'ictinus-init';
			//}
			created_canvas = canvas;
			return canvas.getContext('2d');
		}
		
		/**
		 * Создает контур на холсте
		 * @param {CanvasRenderingContext2D} ctx
		 * @param {IctinusShape} shape
		 */
		function createPath(ctx, shape){
			ctx.beginPath();
			var paths = shape.getPaths();
			
			for (var i = 0; i < paths.length; i++) {
				/** @type {line()} */
				var s = paths[i];
				switch (s.type) {
					case DRAW_TYPE.MOVETO:
						ctx.moveTo(s.x, s.y);
						break;
					case DRAW_TYPE.LINE:
						ctx.lineTo(s.x, s.y);
						break;
					case DRAW_TYPE.BEZIER:
						ctx.bezierCurveTo(s.cpx1, s.cpy1, s.cpx2, s.cpy2, s.x, s.y);
						break;
					case DRAW_TYPE.QUADRATIC:
						ctx.quadraticCurveTo(s.cpx, s.cpy, s.x, s.y);
						break;
					case DRAW_TYPE.CLOSE:
						ctx.closePath();
						ctx.beginPath();
						break;
				}
			}
			
			ctx.closePath();
		}
		
		/**
		 * Рисует вспомогательные линии для кривых. Полезен для отладки внешнего
		 * вида формы.
		 * @param {CanvasRenderingContext2D} ctx
		 */
		function drawHull(ctx){
			// TODO дописать метод
			// ставим новые параметры рисования для вспомогательных линий
			var stroke_width = 1;
			var stroke_color = '#cccccc';
			var fill_color = 'red';
			updateLook();
	
			/**
			 * Вспомогательный метод для рисования точки
			 * @param {Point} point Точка, которую нужно нарисовать
			 * @param {Number} size Размер точки в пикселях
			 */
			function drawPoint(point, size){
				canvas.fillRect(point.x - size / 2 , point.y - size / 2, size, size);
			};
	
			// рисование первой точки кривой
			drawPoint(shape.anchor, 6);
			canvas.beginPath();
	
			canvas.moveTo(shape.anchor.x, shape.anchor.y);
			var segments = shape.getSegments();
			for(var i=0; i < segments.length; i++){
				/** @type {BezierSegment} */
				var s = segments[i];
	
				canvas.lineTo(s.cp1.x, s.cp1.y);
				canvas.stroke();
	
				canvas.beginPath();
				canvas.moveTo(s.cp2.x, s.cp2.y);
				canvas.lineTo(s.anchor.x, s.anchor.y);
				canvas.stroke();
	
				drawPoint(s.anchor, 6);
				drawPoint(s.cp1, 2);
				drawPoint(s.cp2, 2);
	
				canvas.beginPath();
				canvas.moveTo(s.anchor.x, s.anchor.y);
			}
	
			// возвращаем прежние параметры рисования
			restoreLook();
		};
		
		return {
			/**
			 * Рисует декорации для картинки
			 * @param {Element} img Картинка, для которой нужно нарисовать декорации
			 * @param {IctinusShape} shape Форма декорации (массив, состоящий из объектов типа line())
			 */
			decorate: function(img, shape){
				var ctx = createCanvas(img);
				
				ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
				
				// сначала рисую форму декорации, которая будет служить контуром отсечения
				ctx.save();
				createPath(ctx, shape);
				ctx.clip();
				ctx.drawImage(img, 0, 0, img.offsetWidth, img.offsetHeight);
				ctx.restore();
				
				// еще раз создам контур, чтобы нормально отработал stroke в Сафари
				ctx.save();
				// FIXME это быстрый хак, чтобы рисовались линии нечетной толщины, придумать более надежный механизм
				var offset = (shape.strokeWidth % 2) / 2;
				// делаю небольшую поправку для координат, чтобы 
				// прямые линии не были смазанными
				ctx.translate(offset, offset);
				
				createPath(ctx, shape);
				if (shape.strokeWidth) {
					ctx.strokeStyle = shape.strokeColor;
					ctx.lineWidth = shape.strokeWidth;
					ctx.stroke();
				}
				ctx.restore();
				return created_canvas;
			},
			
			/**
			 * Рисует форму
			 * @param {Element} target Элемент, в который нужно добавить холст
			 * @param {IctinusShape} shape Форма, которую нужно нарисовать
			 */
			draw: function(target, shape){
				var ctx = createCanvas(target);
				
				ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
				
				ctx.save();
				// debug only
//				ctx.translate(100, 100);
				
				createPath(ctx, shape);
				ctx.fillStyle = shape.fillColor;
				ctx.fill();
				
				if (shape.strokeWidth) {
					ctx.strokeStyle = shape.strokeColor;
					ctx.lineWidth = shape.strokeWidth;
					ctx.stroke();
				}
				
				ctx.restore();
			}
		}
	};
	
	/**
	 * Адаптер для рисования через VML
	 */
	function vmlAdapter(){
		// TODO добавить поддержку qb (quadraticbezier)
		
		/** Повышающий коэффициент для координатного пространства */
		var multiplier = 10;
		
		/**
		 * Исправляет форму рисования.
		 * Похоже, что VML в IE не поддерживает дробные коэффициенты,
		 * потому что сейчас они кардинально меняют отрисованную форму.
		 * Этот метод умнажает каждую координату формы рисования на 
		 * <code>multiplier</code> и округляет ее, тем самым избавляет линии
		 * от дробных коэффициентов.
		 * Надо помнить, что просле применения этого метода нужно в том числе
		 * изменить координатное пространство элемента shape, умножив его 
		 * габариты на <code>multiplier</code>
		 * @param {Array} shape Форма для рисования
		 * @return {Array} Новая форма рисования (старая не меняется) 
		 */
		function fixShape(shape){
			var new_shape = [];
			for (var i = 0; i < shape.length; i++) {
				var new_obj = {}, obj = shape[i];
				switch (obj.type) {
					// TODO тестовая задача
					case DRAW_TYPE.MOVETO:
					case DRAW_TYPE.LINE:
					case DRAW_TYPE.BEZIER:
						for (var a in obj) {
							new_obj[a] = (typeof obj[a] == 'number') 
								? Math.round(obj[a] * multiplier)
								: obj[a];
						}
						new_shape.push(new_obj);
						break;
					default:
						// пришел какой-то не понятный объект, скопирую его
						new_shape.push(obj);
				}
				
			}
			return new_shape;
		}
		
		if (!document.namespaces["v"]) {
			document.namespaces.add("v", "urn:schemas-microsoft-com:vml");
			// setup default css
			var ss = document.createStyleSheet();
			ss.cssText = "v\\:* {behavior:url(#default#VML);display:inline-block;}";
		}
		
		/**
		 * Создает импровизированный холст для рисования для переданного объекта. 
		 * Холст представляет собой элемент div с классом ictinus
		 * Элемент холста будет добавлен <i>перед</i> элементом <code>elem</code>
		 * @param {Element} elem Элемент, для которого нужно создать холст
		 * @return {HTMLElement}
		 */
		function createCanvas(elem){
			/** @type {HTMLElement} */
			var canvas;
			
			if (elem.className.indexOf('ictinus-init') != -1) {
				// ищем созданный холст
				if (elem.nodeName == 'IMG' && hasClass(elem.previousSibling, 'ictinus')) {
					canvas = elem.previousSibling;
				} else if (elem.getElementsByTagName('div').length) {
					var children = elem.getElementsByTagName('div');
					for (var i = 0; i < children.length; i++) {
						if (hasClass(children[i], 'ictinus')) {
							canvas = children[i];
							break;
						}
					}
				}
			}
			
			if (!canvas) {
				canvas = document.createElement('div');
				
				canvas.style.width = elem.offsetWidth + 'px';
				canvas.style.height = elem.offsetHeight + 'px';
				canvas.className = 'ictinus';
				
				if (elem.tagName == 'IMG') {
					elem.parentNode.insertBefore(canvas, elem);
				} else {
					elem.appendChild(canvas);
				}
				
				elem.className = (elem.className ? elem.className + ' ' : '') + 'ictinus-init';
			}
			return canvas;
		}
		
		return {
			/**
			 * Рисует декорации для картинки
			 * @param {Element} img Картинка, для которой нужно нарисовать декорации
			 * @param {IctinusShape} shape Форма декорации
			 * @param {Object} params Параметры декорации: цвет (line_color) и толщина (line_width) линии
			 */
			decorate: function(img, shape){
				var paths = fixShape(shape.getPaths());

				// создаем путь, описывающий контур
				var path = [];
				for (var i = 0; i < paths.length; i++) {
					/** @type {line()} */
					var s = paths[i];
					switch (s.type) {
						case DRAW_TYPE.MOVETO:
							path.push('m ' + s.x + ',' + s.y);
							break;
						case DRAW_TYPE.LINE:
							path.push('l ' + s.x + ',' + s.y);
							break;
						case DRAW_TYPE.BEZIER:
							path.push('c ' +
									s.cpx1 + ',' + s.cpy1 + ', ' +
									s.cpx2 + ',' + s.cpy2 + ', ' +
									   s.x + ',' + s.y);
							break;
					}
				}
				path.push('x e');
				
				/** @type {Element} */
				var elem;
				
				/** @type {Element} */
				var fill;
				
				//var cached = Boolean(shape._cache.shape_elem);
				var cached = false;
				if (cached) {
					elem = shape._cache.shape_elem;
					fill = shape._cache.fill_elem;
				} else {
					// создаю элемент контура
					elem = document.createElement('v:shape');
					var cv = createCanvas(img);
					
					// создаю заливку формы				
					fill = document.createElement('v:fill');
					fill.type = 'tile';
					elem.appendChild(fill);
					
					shape._cache.shape_elem = elem;
					shape._cache.fill_elem = fill;
				}
				
				
				elem.strokeweight = shape.strokeWidth;
				elem.strokecolor = shape.strokeColor;
				
				if (!shape.strokeWidth) {
					elem.stroked = false;
				}
				
				elem.style.width = img.offsetWidth + 'px';
				elem.style.height = img.offsetHeight + 'px';
				elem.coordsize = (img.offsetWidth * multiplier) + ' ' + (img.offsetHeight * multiplier);
				elem.path = path.join(' ');
				
				if (!cached) {
					cv.appendChild(elem);
				}
				
				/*
				 * очень важно, чтобы координаты определялись до того, как будет
				 * присвоен src элементу, иначе IE будет периодически тупить
				 * и возвращать неправильные значения
				 */
				fill.origin = (elem.offsetLeft / img.offsetWidth) + ' ' + (elem.offsetTop / img.offsetHeight);
				fill.src = img.src;
				return elem;
			},
			
			draw: function(){
				//TODO: implement
			}
		}

	};
	
	var painter = is_msie ? vmlAdapter() : canvasAdapter();
	
	return {
		/**
		 * Рисует декорации для картинки
		 * @param {Element} img Картинка, для которой нужно нарисовать декорации
		 * @param {Array|IctinusShape} shape Форма рисования
		 * @param {Object} [params] Параметры рисования. Атрибут не используется, если параметр <code>shape</code> является объектом класса <code>IctinusShape</code>
		 */
		decorate: function(img, shape, params){
			if (!(shape instanceof IctinusShape)) {
				var old_shape = shape;
				params = readParams(params);
				shape = ictinus.shape();
				shape.addPath.apply(shape, old_shape);
				shape.strokeColor = params.line_color;
				shape.strokeWidth = params.line_width;
			}
			
			return painter.decorate(img, shape);
		},
		
		/**
		 * Рисует форму <code>shape</code> внутри элемента
		 * @param {Element} target Контейнер, в котором нужно рисовать
		 * @param {IctinusShape} shape Форма, которую нужно нарисовать
		 */
		draw: function(target, shape){
			painter.draw(target, shape);
		},
		
		/**
		 * Типы рисования
		 */
		draw_type: {
			move: move,
			close: function(){
				return command(DRAW_TYPE.CLOSE);
			},
			line: line, 
			bezier: bezier,
			quadratic: quadratic
		},
		
		DRAW_TYPE: DRAW_TYPE,
		
		/**
		 * Создает новый экземпляр класса <code>IctinusShape</code>
		 * @param {Array} [paths] Пути, описывающие контур
		 * @return {IctinusShape} 
		 */
		shape: function(paths){
			return new IctinusShape(painter, paths);
		}
	};
})();
