"use strict";

/* CellComment.js
*
* Author: Dmitry Vikulov
* Date:   06/02/2013
*/

//-----------------------------------------------------------------------------------
// CommentCoords
//-----------------------------------------------------------------------------------

//{ ASC Classes
/** @constructor */
function asc_CCommentCoords(obj) {

	this.nRow = null;
	this.nCol = null;

	this.nLeft = null;
	this.nLeftOffset = null;
	this.nTop = null;
	this.nTopOffset = null;
	this.nRight = null;
	this.nRightOffset = null;
	this.nBottom = null;
	this.nBottomOffset = null;

	this.dLeftMM = null;
	this.dTopMM = null;
	this.dLeftPX = null;
	this.dReverseLeftPX = null;
	this.dTopPX = null;

	this.dWidthMM = null;
	this.dHeightMM = null;
	this.dWidthPX = null;
	this.dHeightPX = null;

	this.bMoveWithCells = false;
	this.bSizeWithCells = false;

	if (obj) {
		this.nRow = obj.nRow;
		this.nCol = obj.nCol;

		this.nLeft = obj.nLeft;
		this.nLeftOffset = obj.nLeftOffset;
		this.nTop = obj.nTop;
		this.nTopOffset = obj.nTopOffset;
		this.nRight = obj.nRight;
		this.nRightOffset = obj.nRightOffset;
		this.nBottom = obj.nBottom;
		this.nBottomOffset = obj.nBottomOffset;

		this.dLeftMM = obj.dLeftMM;
		this.dTopMM = obj.dTopMM;
		this.dLeftPX = obj.dLeftPX;
		this.dReverseLeftPX = obj.dReverseLeftPX;
		this.dTopPX = obj.dTopPX;

		this.dWidthMM = obj.dWidthMM;
		this.dHeightMM = obj.dHeightMM;
		this.dWidthPX = obj.dWidthPX;
		this.dHeightPX = obj.dHeightPX;

		this.bMoveWithCells = obj.bMoveWithCells;
		this.bSizeWithCells = obj.bSizeWithCells;
	}
}

// Prototype
asc_CCommentCoords.prototype = {

	asc_setRow: function(val) { this.nRow = val; },
	asc_getRow: function() { return this.nRow; },

	asc_setCol: function(val) { this.nCol = val; },
	asc_getCol: function() { return this.nCol; },

	asc_setLeft: function(val) { this.nLeft = val; },
	asc_getLeft: function() { return this.nLeft; },

	asc_setLeftOffset: function(val) { this.nLeftOffset = val; },
	asc_getLeftOffset: function() { return this.nLeftOffset; },

	asc_setTop: function(val) { this.nTop = val; },
	asc_getTop: function() { return this.nTop; },

	asc_setTopOffset: function(val) { this.nTopOffset = val; },
	asc_getTopOffset: function() { return this.nTopOffset; },

	asc_setRight: function(val) { this.nRight = val; },
	asc_getRight: function() { return this.nRight; },

	asc_setRightOffset: function(val) { this.nRightOffset = val; },
	asc_getRightOffset: function() { return this.nRightOffset; },

	asc_setBottom: function(val) { this.nBottom = val; },
	asc_getBottom: function() { return this.nBottom; },

	asc_setBottomOffset: function(val) { this.nBottomOffset = val; },
	asc_getBottomOffset: function() { return this.nBottomOffset; },

	asc_setLeftMM: function(val) { this.dLeftMM = val; },
	asc_getLeftMM: function() { return this.dLeftMM; },

	asc_setLeftPX: function(val) { this.dLeftPX = val; },
	asc_getLeftPX: function() { return this.dLeftPX; },
	
	asc_setReverseLeftPX: function(val) { this.dReverseLeftPX = val; },
	asc_getReverseLeftPX: function() { return this.dReverseLeftPX; },

	asc_setTopMM: function(val) { this.dTopMM = val; },
	asc_getTopMM: function() { return this.dTopMM; },

	asc_setTopPX: function(val) { this.dTopPX = val; },
	asc_getTopPX: function() { return this.dTopPX; },

	asc_setWidthMM: function(val) { this.dWidthMM = val; },
	asc_getWidthMM: function() { return this.dWidthMM; },

	asc_setHeightMM: function(val) { this.dHeightMM = val; },
	asc_getHeightMM: function() { return this.dHeightMM; },

	asc_setWidthPX: function(val) { this.dWidthPX = val; },
	asc_getWidthPX: function() { return this.dWidthPX; },

	asc_setHeightPX: function(val) { this.dHeightPX = val; },
	asc_getHeightPX: function() { return this.dHeightPX; },

	asc_setMoveWithCells: function(val) { this.bMoveWithCells = val; },
	asc_getMoveWithCells: function() { return this.bMoveWithCells; },

	asc_setSizeWithCells: function(val) { this.bSizeWithCells = val; },
	asc_getSizeWithCells: function() { return this.bSizeWithCells; }
};

window["Asc"]["asc_CCommentCoords"] = window["Asc"].asc_CCommentCoords = asc_CCommentCoords;
var prot = asc_CCommentCoords.prototype;

prot["asc_setRow"] = prot.asc_setRow;
prot["asc_getRow"] = prot.asc_getRow;

prot["asc_setCol"] = prot.asc_setCol;
prot["asc_setCol"] = prot.asc_setCol;

prot["asc_setLeft"] = prot.asc_setLeft;
prot["asc_getLeft"] = prot.asc_getLeft;

prot["asc_setLeftOffset"] = prot.asc_setLeftOffset;
prot["asc_getLeftOffset"] = prot.asc_getLeftOffset;

prot["asc_setTop"] = prot.asc_setTop;
prot["asc_getTop"] = prot.asc_getTop;

prot["asc_setTopOffset"] = prot.asc_setTopOffset;
prot["asc_getTopOffset"] = prot.asc_getTopOffset;

prot["asc_setRight"] = prot.asc_setRight;
prot["asc_getRight"] = prot.asc_getRight;

prot["asc_setRightOffset"] = prot.asc_setRightOffset;
prot["asc_getRightOffset"] = prot.asc_getRightOffset;

prot["asc_setBottom"] = prot.asc_setBottom;
prot["asc_getBottom"] = prot.asc_getBottom;

prot["asc_setBottomOffset"] = prot.asc_setBottomOffset;
prot["asc_getBottomOffset"] = prot.asc_getBottomOffset;

prot["asc_setLeftMM"] = prot.asc_setLeftMM;
prot["asc_getLeftMM"] = prot.asc_getLeftMM;

prot["asc_setLeftPX"] = prot.asc_setLeftPX;
prot["asc_getLeftPX"] = prot.asc_getLeftPX;

prot["asc_setTopMM"] = prot.asc_setTopMM;
prot["asc_getTopMM"] = prot.asc_getTopMM;

prot["asc_setTopPX"] = prot.asc_setTopPX;
prot["asc_getTopPX"] = prot.asc_getTopPX;

prot["asc_setWidthMM"] = prot.asc_setWidthMM;
prot["asc_getWidthMM"] = prot.asc_getWidthMM;

prot["asc_setHeightMM"] = prot.asc_setHeightMM;
prot["asc_getHeightMM"] = prot.asc_getHeightMM;

prot["asc_setWidthPX"] = prot.asc_setWidthPX;
prot["asc_getWidthPX"] = prot.asc_getWidthPX;

prot["asc_setHeightPX"] = prot.asc_setHeightPX;
prot["asc_getHeightPX"] = prot.asc_getHeightPX;

prot["asc_setMoveWithCells"] = prot.asc_setMoveWithCells;
prot["asc_getMoveWithCells"] = prot.asc_getMoveWithCells;

prot["asc_setSizeWithCells"] = prot.asc_setSizeWithCells;
prot["asc_getSizeWithCells"] = prot.asc_getSizeWithCells;

//-----------------------------------------------------------------------------------
// CommentData
//-----------------------------------------------------------------------------------
/** @constructor */
function asc_CCommentData(obj) {
	this.Properties = {
		wsId: 0,
		nCol: 1,
		nRow: 2,
		nId: 3,
		nLevel: 5,
		sText: 6,
		sQuoteText: 7,
		sTime: 8,
		sUserId: 9,
		sUserName: 10,
		bDocument: 11,
		bSolved: 12,
		aReplies: 13,
		bHidden: 14
	};

	this.bHidden = false;
	this.wsId = null;
	this.nCol = 0;
	this.nRow = 0;
	this.nId = null;
	this.oParent = null;
	this.nLevel = 0;

	// Common
	this.sText = "";
	this.sQuoteText = "";
	this.sTime = "";
	this.sUserId = "";
	this.sUserName = "";
	this.bDocument = true; 	// For compatibility with 'Word Comment Control'
	this.bSolved = false;
	this.aReplies = [];

	if (obj) {
		this.bHidden = obj.bHidden;
		this.wsId = obj.wsId;
		this.nCol = obj.nCol;
		this.nRow = obj.nRow;
		this.nId = obj.nId;
		this.oParent = obj.oParent;
		this.nLevel = (null === this.oParent) ? 0 : this.oParent.asc_getLevel() + 1;

		// Common
		this.sText = obj.sText;
		this.sQuoteText = obj.sQuoteText;
		this.sTime = obj.sTime;
		this.sUserId = obj.sUserId;
		this.sUserName = obj.sUserName;
		this.bDocument = obj.bDocument;
		this.bSolved = obj.bSolved;
		this.aReplies = [];

		for (var i = 0; i < obj.aReplies.length; i++) {
			var item = new asc_CCommentData(obj.aReplies[i]);
			this.aReplies.push(item);
		}
	}
}

// Prototype
asc_CCommentData.prototype = {
	guid: function () {
		function S4() {
			return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
		}
		return (S4() + S4() + "-" + S4() + "-" + S4() + "-" + S4() + "-" + S4() + S4() + S4());
	},
	setId: function () {
		if (this.bDocument)
			this.nId = "doc_" + guid();
		else
			this.nId = "sheet" + this.wsId + "_" + this.guid();
	},

	asc_putQuoteText: function(val) { this.sQuoteText = val; },
	asc_getQuoteText: function() { return this.sQuoteText; },

	asc_putRow: function(val) { this.nRow = val; },
	asc_getRow: function() { return this.nRow; },

	asc_putCol: function(val) { this.nCol = val; },
	asc_getCol: function() { return this.nCol; },

	asc_putId: function(val) { this.nId = val; },
	asc_getId: function() { return this.nId; },

	asc_putLevel: function(val) { this.nLevel = val; },
	asc_getLevel: function() { return this.nLevel; },

	asc_putParent: function(obj) { this.oParent = obj; },
	asc_getParent: function() { return this.oParent; },

	asc_putText: function(val) { this.sText = val; },
	asc_getText: function() { return this.sText; },

	asc_putTime: function(val) { this.sTime = val; },
	asc_getTime: function() { return this.sTime; },

	asc_putUserId: function(val) { this.sUserId = val; },
	asc_getUserId: function() { return this.sUserId; },

	asc_putUserName: function(val) { this.sUserName = val; },
	asc_getUserName: function() { return this.sUserName; },

	asc_putDocumentFlag: function(val) { this.bDocument = val; },
	asc_getDocumentFlag: function() { return this.bDocument; },
	
	asc_putHiddenFlag: function(val) { this.bHidden = val; },
	asc_getHiddenFlag: function() { return this.bHidden; },

	asc_putSolved: function(val) { this.bSolved = val; },
	asc_getSolved: function() { return this.bSolved; },

	asc_getRepliesCount: function() { return this.aReplies.length; },
	asc_getReply: function(index) { return this.aReplies[index]; },

	asc_addReply: function(oReply) {

		oReply.asc_putParent(this);
		oReply.asc_putDocumentFlag(this.asc_getDocumentFlag());
		oReply.asc_putLevel((oReply.oParent == null) ? 0 : oReply.oParent.asc_getLevel() + 1);
		oReply.wsId = (oReply.oParent == null) ? -1 : oReply.oParent.wsId;
		oReply.setId();
		oReply.asc_putCol(this.nCol);
		oReply.asc_putRow(this.nRow);
		this.aReplies.push(oReply);

		return oReply;
	},

	asc_getMasterCommentId: function () {
		return this.wsId;
	},

	//	For collaborative editing
	getType: function() {
		return UndoRedoDataTypes.CommentData;
	},

	getProperties: function() {
		return this.Properties;
	},

	getProperty: function(nType) {
		switch (nType) {
			case this.Properties.wsId: return this.wsId; break;
			case this.Properties.nCol: return this.nCol; break;
			case this.Properties.nRow: return this.nRow; break;
			case this.Properties.nId: return this.nId; break;
			case this.Properties.nLevel: return this.nLevel; break;
			case this.Properties.sText: return this.sText; break;
			case this.Properties.sQuoteText: return this.sQuoteText; break;
			case this.Properties.sTime: return this.sTime; break;
			case this.Properties.sUserId: return this.sUserId; break;
			case this.Properties.sUserName: return this.sUserName; break;
			case this.Properties.bDocument: return this.bDocument; break;
			case this.Properties.bSolved: return this.bSolved; break;
			case this.Properties.aReplies: return this.aReplies; break;
			case this.Properties.bHidden: return this.bHidden; break;
		}
		return null;
	},

	setProperty: function(nType, value) {
		switch (nType) {
			case this.Properties.wsId: this.wsId = value; break;
			case this.Properties.nCol: this.nCol = value; break;
			case this.Properties.nRow: this.nRow = value; break;
			case this.Properties.nId: this.nId = value; break;
			case this.Properties.nLevel: this.nLevel = value; break;
			case this.Properties.sText: this.sText = value; break;
			case this.Properties.sQuoteText: this.sQuoteText = value; break;
			case this.Properties.sTime: this.sTime = value; break;
			case this.Properties.sUserId: this.sUserId = value; break;
			case this.Properties.sUserName: this.sUserName = value; break;
			case this.Properties.bDocument: this.bDocument = value; break;
			case this.Properties.bSolved: this.bSolved = value; break;
			case this.Properties.aReplies: this.aReplies = value; break;
			case this.Properties.bHidden: this.bHidden = value; break;
		}
	},
	
	applyCollaborative: function (nSheetId, collaborativeEditing) {
		if ( !this.bDocument ) {
			this.nCol = collaborativeEditing.getLockMeColumn2(nSheetId, this.nCol);
			this.nRow = collaborativeEditing.getLockMeRow2(nSheetId, this.nRow);
		}
	}
};

window["Asc"]["asc_CCommentData"] = window["Asc"].asc_CCommentData = asc_CCommentData;
prot = asc_CCommentData.prototype;

prot["asc_putRow"] = prot.asc_putRow;
prot["asc_getRow"] = prot.asc_getRow;

prot["asc_putCol"] = prot.asc_putCol;
prot["asc_getCol"] = prot.asc_getCol;

prot["asc_putId"] = prot.asc_putId;
prot["asc_getId"] = prot.asc_getId;

prot["asc_putLevel"] = prot.asc_putLevel;
prot["asc_getLevel"] = prot.asc_getLevel;

prot["asc_putParent"] = prot.asc_putParent;
prot["asc_getParent"] = prot.asc_getParent;

prot["asc_putText"] = prot.asc_putText;
prot["asc_getText"] = prot.asc_getText;

prot["asc_putQuoteText"] = prot.asc_putQuoteText;
prot["asc_getQuoteText"] = prot.asc_getQuoteText;

prot["asc_putTime"] = prot.asc_putTime;
prot["asc_getTime"] = prot.asc_getTime;

prot["asc_putUserId"] = prot.asc_putUserId;
prot["asc_getUserId"] = prot.asc_getUserId;

prot["asc_putUserName"] = prot.asc_putUserName;
prot["asc_getUserName"] = prot.asc_getUserName;

prot["asc_putDocumentFlag"] = prot.asc_putDocumentFlag;
prot["asc_getDocumentFlag"] = prot.asc_getDocumentFlag;

prot["asc_putHiddenFlag"] = prot.asc_putHiddenFlag;
prot["asc_getHiddenFlag"] = prot.asc_getHiddenFlag;

prot["asc_putSolved"] = prot.asc_putSolved;
prot["asc_getSolved"] = prot.asc_getSolved;

prot["asc_getRepliesCount"] = prot.asc_getRepliesCount;
prot["asc_getReply"] = prot.asc_getReply;

prot["asc_addReply"] = prot.asc_addReply;

prot["asc_getMasterCommentId"] = prot.asc_getMasterCommentId;

//}

//-----------------------------------------------------------------------------------
// CompositeCommentData
//-----------------------------------------------------------------------------------

function CompositeCommentData() {
	this.commentBefore = null;
	this.commentAfter = null;

	this.Properties = {
		commentBefore: 0,
		commentAfter: 1
	};
}

CompositeCommentData.prototype = {
	//	For collaborative editing
	getType: function() {
		return UndoRedoDataTypes.CompositeCommentData;
	},

	getProperties: function() {
		return this.Properties;
	},

	getProperty: function(nType) {
		switch (nType) {
			case this.Properties.commentBefore: return this.commentBefore; break;
			case this.Properties.commentAfter: return this.commentAfter; break;
		}
		return null;
	},

	setProperty: function(nType, value) {
		switch (nType) {
			case this.Properties.commentBefore: this.commentBefore = value; break;
			case this.Properties.commentAfter: this.commentAfter = value; break;
		}
	}
};

//-----------------------------------------------------------------------------------
// CellCommentator
//-----------------------------------------------------------------------------------
/** @constructor */
function asc_CCellCommentator(currentSheet) {

	var _this = this;
	var asc = window["Asc"];
	var asc_applyFunction = asc.applyFunction;
	var asc_CCollaborativeRange = asc.asc_CCollaborativeRange;
	var isViewerMode =  function() { return _this.worksheet.handlers.trigger("getViewerMode"); };
	
	_this.worksheet = currentSheet;
	_this.overlayCtx = currentSheet.overlayCtx;
	_this.drawingCtx = currentSheet.drawingCtx;

	// Drawing settings
	_this.bShow = true;
	_this.commentIconColor = new CColor(255, 144, 0);
	_this.commentFillColor = new CColor(255, 255, 0);
	_this.commentBorderWidth = 1; // px
	_this.commentPadding = 4; 	// px
	_this.minAreaWidth = 160; 	// px
	_this.minAreaHeight = 80; 	// px

	_this.aComments = [];
	_this.aCommentCoords = [];
	_this.lastSelectedId = null;
	_this.bSaveHistory = true;

	//-----------------------------------------------------------------------------------
	// Public methods
	//-----------------------------------------------------------------------------------
	
	_this.isLockedComment = function(oComment, lockByDefault, callbackFunc) {
	
		var _this = this;
		var lockInfo;

		var model = _this.worksheet.model;
		var sheetId = model.getId();

		var objectGuid = oComment.asc_getId();
		if (objectGuid) {

			if (false === _this.worksheet.collaborativeEditing.isCoAuthoringExcellEnable()) {
				// Запрещено совместное редактирование
				asc_applyFunction(callbackFunc, true);
				return;
			}

			if ( lockByDefault )
				_this.worksheet.collaborativeEditing.onStartCheckLock();

			// Комментарий к документу блокируем как Range
			if ( !oComment.asc_getDocumentFlag() ) {
				var c = oComment.asc_getCol();
				var r = oComment.asc_getRow();
				var c1, r1, c2, r2;
				var mergedRange = model.getMergedByCell(r, c);
				if (mergedRange) {
					c1 = mergedRange.c1;
					r1 = mergedRange.r1;
					c2 = mergedRange.c2;
					r2 = mergedRange.r2;
				} else {
					c1 = c2 = c;
					r1 = r2 = r;
				}

				lockInfo = _this.worksheet.collaborativeEditing.getLockInfo(c_oAscLockTypeElem.Range, /*subType*/null, sheetId, new asc_CCollaborativeRange(c1, r1, c2, r2));
			} else
				lockInfo = _this.worksheet.collaborativeEditing.getLockInfo(c_oAscLockTypeElem.Object, /*subType*/null, sheetId, objectGuid);

			if (false === _this.worksheet.collaborativeEditing.getCollaborativeEditing()) {
				// Пользователь редактирует один: не ждем ответа, а сразу продолжаем редактирование
				asc_applyFunction(callbackFunc, true);
				callbackFunc = undefined;
			}
			if (false !== _this.worksheet.collaborativeEditing.getLockIntersection(lockInfo, c_oAscLockTypes.kLockTypeMine, /*bCheckOnlyLockAll*/false)) {
				// Редактируем сами
				asc_applyFunction(callbackFunc, true);
				return;
			}
			else if (false !== _this.worksheet.collaborativeEditing.getLockIntersection(lockInfo, c_oAscLockTypes.kLockTypeOther, /*bCheckOnlyLockAll*/false)) {
				// Уже ячейку кто-то редактирует
				asc_applyFunction(callbackFunc, false);
				return;
			}

			// Блокируем
			if ( lockByDefault ) {
				_this.worksheet.collaborativeEditing.addCheckLock(lockInfo);
				_this.worksheet.collaborativeEditing.onEndCheckLock(callbackFunc);
			}
			else
				asc_applyFunction(callbackFunc, true);
		}
	};
	
	_this.callLockComments = function(range) {
		
		if ( range ) {
			for (var i = 0; i < _this.aComments.length; i++) {
				var comment = _this.aComments[i];
				if ( (comment.nCol >= range.c1) && (comment.nCol <= range.c2) && (comment.nRow >= range.r1) && (comment.nRow <= range.r2) )
					_this.worksheet.model.workbook.handlers.trigger("asc_onLockComment", comment.asc_getId(), comment.asc_getUserId());
			}
		}
	};
	
	_this.unlockComments = function() {
		for (var i = 0; i < _this.aComments.length; i++) {
			_this.worksheet.model.workbook.handlers.trigger("asc_onUnLockComment", _this.aComments[i].asc_getId());
		}
	};
	
	_this.tryUnlockComment = function(id) {
		for (var i = 0; i < _this.aComments.length; i++) {
			if ( _this.aComments[i].asc_getId() == id ) {
				_this.worksheet.model.workbook.handlers.trigger("asc_onUnLockComment", id);
				break;
			}
		}
	};

	_this.moveRangeComments = function(rangeFrom, rangeTo) {
		if ( rangeFrom && rangeTo ) {
			var colOffset = rangeTo.c1 - rangeFrom.c1;
			var rowOffset = rangeTo.r1 - rangeFrom.r1;
			
			_this.worksheet.model.workbook.handlers.trigger("asc_onHideComment");
			
			for (var i = 0; i < _this.aComments.length; i++) {
				var comment = _this.aComments[i];
				
				if ( (comment.nCol >= rangeFrom.c1) && (comment.nCol <= rangeFrom.c2) && (comment.nRow >= rangeFrom.r1) && (comment.nRow <= rangeFrom.r2) ) {

					var commentBefore = new asc_CCommentData(comment);
				
					comment.nCol += colOffset;
					comment.nRow += rowOffset;
					var cellAddress = new CellAddress(comment.nRow, comment.nCol, 0);
					comment.sQuoteText = cellAddress.getID() + " : " + _this.worksheet.model.getCell(cellAddress).getValueWithFormat();
					_this.worksheet.model.workbook.handlers.trigger("asc_onChangeCommentData", comment.asc_getId(), comment);
					
					var commentAfter = new asc_CCommentData(comment);
					
					var compositeComment = new CompositeCommentData();
					compositeComment.commentBefore = commentBefore;
					compositeComment.commentAfter = commentAfter;

					History.Create_NewPoint();
					History.Add(g_oUndoRedoComment, historyitem_Comment_Change, _this.worksheet.model.getId(), null, compositeComment);
				}
			}
		}
	};
	
	_this.deleteCommentsRange = function(range) {
		if ( range ) {
			var aCommentId = [], i;
			for (i = 0; i < _this.aComments.length; ++i) {
				var comment = _this.aComments[i];
				if ( (comment.nCol >= range.c1) && (comment.nCol <= range.c2) && (comment.nRow >= range.r1) && (comment.nRow <= range.r2) ) {
					aCommentId.push(comment.asc_getId());
				}
			}
			History.StartTransaction();
			for (i = 0; i < aCommentId.length; i++) {
				_this.asc_removeComment(aCommentId[i]);
			}
			History.EndTransaction();
		}
	};

	_this.prepareComments = function(arrComments) {
		var commentList = [];
		for (var i = 0; i < arrComments.length; ++i) {
			var comment = {"Id": arrComments[i].asc_getId(), "Comment": arrComments[i]};
			_this.addCommentSerialize(comment["Comment"]);
			commentList.push(comment);
		}
		return commentList;
	};
	
	_this.addCommentSerialize = function(oComment) {

		var _this = this;

		if (oComment) {
			if ( !oComment.bDocument && (oComment.nCol != null) && (oComment.nRow != null) ) {
				var cellAddress = new CellAddress(oComment.nRow, oComment.nCol, 0);
				oComment.sQuoteText = cellAddress.getID() + " : " + _this.worksheet.model.getCell(cellAddress).getValueWithFormat();
			}
			_this.aComments.push(oComment);
		}
	};

	_this.getCommentsXY = function(x, y) {

		var _this = this;

		var findCol = _this.worksheet._findColUnderCursor(_this.pxToPt(x), true);
		var findRow = _this.worksheet._findRowUnderCursor(_this.pxToPt(y), true);

		return (findCol && findRow) ? _this.asc_getComments(findCol.col, findRow.row) : [];
	};

	_this.drawCommentCells = function() {
	
		if ( isViewerMode() || !_this.bShow )
			return;

		var drawCells = []; 	// Associative array
		function getCellId(col, row) { return (col + "_" + row); }

		for (var n = 0; n < _this.worksheet.drawingArea.frozenPlaces.length; n++) {
			var frozenPlace = _this.worksheet.drawingArea.frozenPlaces[n];
			var fv = frozenPlace.getFirstVisible();
			var lvr = _this.worksheet.getLastVisibleRow();
			var lvc = _this.worksheet.getLastVisibleCol();
			
			for (var i = 0; i < _this.aComments.length; i++) {
				// Get cell metrics
				var commentCell = _this.aComments[i];
				if (commentCell.asc_getDocumentFlag() || commentCell.asc_getHiddenFlag() || commentCell.asc_getSolved())
					continue;
				
				var mergedRange = _this.worksheet.model.getMergedByCell(commentCell.nRow, commentCell.nCol);
				var drawCol = mergedRange ? mergedRange.c2 : commentCell.nCol;
				var drawRow = mergedRange ? mergedRange.r1 : commentCell.nRow;
				if (drawCol < fv.col || drawRow < fv.row || drawCol > lvc || drawRow > lvr)
					continue;

				if ( !frozenPlace.isCellInside({ col: drawCol, row: drawRow }) )
					continue;

				var cellId = getCellId(commentCell.nCol, commentCell.nRow);
				if (drawCells[cellId])
					continue;

				var metrics = _this.getCellMetrics(drawCol, drawRow);
				if ( !metrics.result || (metrics.width <= 0) || (metrics.height <= 0) )
					continue;

				_this.drawingCtx.beginPath();
				_this.drawingCtx.setFillStyle(_this.commentIconColor);

				_this.drawingCtx.moveTo(metrics.left + metrics.width - _this.pxToPt(7), metrics.top);
				_this.drawingCtx.lineTo(metrics.left + metrics.width - _this.pxToPt(1), metrics.top);
				_this.drawingCtx.lineTo(metrics.left + metrics.width - _this.pxToPt(1), metrics.top + _this.pxToPt(6));
				_this.drawingCtx.closePath();
				_this.drawingCtx.fill();

				drawCells[cellId] = cellId;
			}
		}
		//if (_this.lastSelectedId)
		//	_this.asc_selectComment(_this.lastSelectedId, false);
	};

	_this.getTextMetrics = function(text, units) {

		var _this = this;
		var metrics = { width: 0, height: 0 };

		if (text && text.length && ((typeof (text) == 'string') || (typeof (text) == 'number'))) {
			var textOptions = _this.overlayCtx.measureText(text, units);
			metrics.width = textOptions.width;
			metrics.height = textOptions.lineHeight;
		}
		return metrics;
	};

	_this.getCellMetrics = function(col, row) {

		var metrics = { top: 0, left: 0, width: 0, height: 0, result: false }; 	// px

		for (var n = 0; n < _this.worksheet.drawingArea.frozenPlaces.length; n++) {
			var frozenPlace = _this.worksheet.drawingArea.frozenPlaces[n];
			if ( !frozenPlace.isCellInside({ col: col, row: row }) )
				continue;
			
			var fv = frozenPlace.getFirstVisible();
			var mergedRange = _this.worksheet.model.getMergedByCell(row, col);

			if (mergedRange && (fv.col < mergedRange.c2) && (fv.row < mergedRange.r2)) {

				var startCol = (mergedRange.c1 > fv.col) ? mergedRange.c1 : fv.col;
				var startRow = (mergedRange.r1 > fv.row) ? mergedRange.r1 : fv.row;

				metrics.top = _this.worksheet.getCellTop(startRow, 1) /*- _this.worksheet.getCellTop(fv.row, 1) + _this.worksheet.getCellTop(0, 1)*/ + _this.pxToPt(frozenPlace.getVerticalScroll()) - _this.worksheet.getCellTop(0, 1);
				metrics.left = _this.worksheet.getCellLeft(startCol, 1) /*- _this.worksheet.getCellLeft(fv.col, 1) + _this.worksheet.getCellLeft(0, 1)*/ + _this.pxToPt(frozenPlace.getHorizontalScroll()) - _this.worksheet.getCellLeft(0, 1);

				var i;
				for (i = startCol; i <= mergedRange.c2; i++) {
					metrics.width += _this.worksheet.getColumnWidth(i, 1)
				}
				for (i = startRow; i <= mergedRange.r2; i++) {
					metrics.height += _this.worksheet.getRowHeight(i, 1)
				}
				metrics.result = true;
			}
			else if ((fv.row <= row) && (fv.col <= col)) {

				metrics.top = _this.worksheet.getCellTop(row, 1) /*- _this.worksheet.getCellTop(fv.row, 1) + _this.worksheet.getCellTop(0, 1)*/ + _this.pxToPt(frozenPlace.getVerticalScroll()) - _this.worksheet.getCellTop(0, 1);
				metrics.left = _this.worksheet.getCellLeft(col, 1) /*- _this.worksheet.getCellLeft(fv.col, 1) + _this.worksheet.getCellLeft(0, 1)*/ + _this.pxToPt(frozenPlace.getHorizontalScroll()) - _this.worksheet.getCellLeft(0, 1);
				metrics.width = _this.worksheet.getColumnWidth(col, 1);
				metrics.height = _this.worksheet.getRowHeight(row, 1);
				metrics.result = true;
			}
		}

		return metrics;
	};

	_this.updateCommentPosition = function() {
		var _this = this;

		if (_this.lastSelectedId) {
			var comment = _this.asc_findComment(_this.lastSelectedId);
			if (comment) {

				var commentList = _this.asc_getComments(comment.asc_getCol(), comment.asc_getRow());
				if (commentList.length) {

					_this.drawCommentCells();
					var coords = _this.getCommentsCoords(commentList);

					var indexes = [];
					for (var i = 0; i < commentList.length; i++) {
						indexes.push(commentList[i].asc_getId());
					}
					var metrics = _this.getCellMetrics(comment.asc_getCol(), comment.asc_getRow());

					_this.worksheet.model.workbook.handlers.trigger( "asc_onUpdateCommentPosition", indexes,
																									(metrics.result ? coords.asc_getLeftPX() : -1),
																									(metrics.result ? coords.asc_getTopPX() : -1),
																									(metrics.result ? coords.asc_getReverseLeftPX() : -1) );
				}
			}
		}
	};
	
	_this.updateCommentsDependencies = function(bInsert, operType, updateRange) {
		
		var UpdatePair = function(comment, bChange) {
			this.comment = comment;
			this.bChange = bChange;
		};
		var aChangedComments = [];		// Array of UpdatePair
		
		function updateCommentsList(aComments) {
			if ( aComments.length ) {
				
				_this.bSaveHistory = false;
				var changeArray = [];
				var removeArray = [];
				
				for (var i = 0; i < aComments.length; i++) {
					if ( aComments[i].bChange ) {
						_this.asc_changeComment(aComments[i].comment.asc_getId(), aComments[i].comment, /*bChangeCoords*/true, /*bNoEvent*/true);
						changeArray.push({"Id": aComments[i].comment.asc_getId(), "Comment": aComments[i].comment});
					}
					else {
						_this.asc_removeComment(aComments[i].comment.asc_getId(), /*bNoEvent*/true);
						removeArray.push(aComments[i].comment.asc_getId());
					}
				}
				
				if ( changeArray.length )
					_this.worksheet.model.workbook.handlers.trigger("asc_onChangeComments", changeArray);
				if ( removeArray.length )
					_this.worksheet.model.workbook.handlers.trigger("asc_onRemoveComments", removeArray);
				
				_this.bSaveHistory = true;
				_this.drawCommentCells();
			}
		}	

		var i, comment;
		if ( bInsert ) {
			switch (operType) {
			
				case c_oAscInsertOptions.InsertCellsAndShiftDown: {
					for (i = 0; i < _this.aComments.length; i++) {
						comment = new asc_CCommentData(_this.aComments[i]);
						if ( (comment.nRow >= updateRange.r1) && (comment.nCol >= updateRange.c1) && (comment.nCol <= updateRange.c2) ) {
							comment.nRow += updateRange.r2 - updateRange.r1 + 1;
							aChangedComments.push( new UpdatePair(comment, true) );
						}
					}
					updateCommentsList(aChangedComments);
				}
				break;
				
				case c_oAscInsertOptions.InsertCellsAndShiftRight: {
					for (i = 0; i < _this.aComments.length; i++) {
						comment = new asc_CCommentData(_this.aComments[i]);
						if ( (comment.nCol >= updateRange.c1) && (comment.nRow >= updateRange.r1) && (comment.nRow <= updateRange.r2) ) {
							comment.nCol += updateRange.c2 - updateRange.c1 + 1;
							aChangedComments.push( new UpdatePair(comment, true) );
						}
					}
					updateCommentsList(aChangedComments);
				}
				break;
				
				case c_oAscInsertOptions.InsertColumns: {
					for (i = 0; i < _this.aComments.length; i++) {
						comment = new asc_CCommentData(_this.aComments[i]);
						if (comment.nCol >= updateRange.c1) {
							comment.nCol += updateRange.c2 - updateRange.c1 + 1;
							aChangedComments.push( new UpdatePair(comment, true) );
						}
					}
					updateCommentsList(aChangedComments);
				}
				break;
				
				case c_oAscInsertOptions.InsertRows: {
					for (i = 0; i < _this.aComments.length; i++) {
						comment = new asc_CCommentData(_this.aComments[i]);
						if (comment.nRow >= updateRange.r1) {
							comment.nRow += updateRange.r2 - updateRange.r1 + 1;
							aChangedComments.push( new UpdatePair(comment, true) );
						}
					}
					updateCommentsList(aChangedComments);
				}
				break;
			}
		}
		else {
			switch (operType) {
				
				case "deleteAllComments": {
					for (i = 0; i < _this.aComments.length; i++) {
						comment = new asc_CCommentData(_this.aComments[i]);
						if ( (updateRange.c1 <= comment.nCol) && (updateRange.c2 >= comment.nCol) && (comment.nRow >= updateRange.r1) && (comment.nRow <= updateRange.r2) ) {
							aChangedComments.push( new UpdatePair(comment, false) );
						}
					}
					updateCommentsList(aChangedComments);
				}
				break;
				
				case c_oAscDeleteOptions.DeleteCellsAndShiftTop: {
					for (i = 0; i < _this.aComments.length; i++) {
						comment = new asc_CCommentData(_this.aComments[i]);
						if ( (comment.nRow > updateRange.r1) && (comment.nCol >= updateRange.c1) && (comment.nCol <= updateRange.c2) ) {
							comment.nRow -= updateRange.r2 - updateRange.r1 + 1;
							aChangedComments.push( new UpdatePair(comment, true) );
						}
						else if ( (updateRange.c1 <= comment.nCol) && (updateRange.c2 >= comment.nCol) && (comment.nRow >= updateRange.r1) && (comment.nRow <= updateRange.r2) ) {
							aChangedComments.push( new UpdatePair(comment, false) );
						}
					}
					updateCommentsList(aChangedComments);
				}
				break;
				
				case c_oAscDeleteOptions.DeleteCellsAndShiftLeft: {
					for (i = 0; i < _this.aComments.length; i++) {
						comment = new asc_CCommentData(_this.aComments[i]);
						if ( (comment.nCol > updateRange.c2) && (comment.nRow >= updateRange.r1) && (comment.nRow <= updateRange.r2) ) {
							comment.nCol -= updateRange.c2 - updateRange.c1 + 1;
							aChangedComments.push( new UpdatePair(comment, true) );
						}
						else if ( (updateRange.c1 <= comment.nCol) && (updateRange.c2 >= comment.nCol) && (comment.nRow >= updateRange.r1) && (comment.nRow <= updateRange.r2) ) {
							aChangedComments.push( new UpdatePair(comment, false) );
						}
					}
					updateCommentsList(aChangedComments);
				}
				break;
				
				case c_oAscDeleteOptions.DeleteColumns: {
					for (i = 0; i < _this.aComments.length; i++) {
						comment = new asc_CCommentData(_this.aComments[i]);
						if (comment.nCol > updateRange.c2) {
							comment.nCol -= updateRange.c2 - updateRange.c1 + 1;
							aChangedComments.push( new UpdatePair(comment, true) );
						}
						else if ( (updateRange.c1 <= comment.nCol) && (updateRange.c2 >= comment.nCol) ) {
							aChangedComments.push( new UpdatePair(comment, false) );
						}
					}
					updateCommentsList(aChangedComments);
				}
				break;
				
				case c_oAscDeleteOptions.DeleteRows: {
					for (i = 0; i < _this.aComments.length; i++) {
						comment = new asc_CCommentData(_this.aComments[i]);
						if (comment.nRow > updateRange.r2) {
							comment.nRow -= updateRange.r2 - updateRange.r1 + 1;
							aChangedComments.push( new UpdatePair(comment, true) );
						}
						else if ( (updateRange.r1 <= comment.nRow) && (updateRange.r2 >= comment.nRow) ) {
							aChangedComments.push( new UpdatePair(comment, false) );
						}
					}
					updateCommentsList(aChangedComments);
				}
				break;
			}
		}		
	};

	_this.showHideComments = function(bHide, bColumn, start, stop) {
		
		var aChangedComments = [];
		
		function updateCommentsList(aComments) {
			if ( aComments.length ) {
			
				History.StartTransaction();
				for (var i = 0; i < aComments.length; i++) {
					_this.asc_changeComment(aComments[i].asc_getId(), aComments[i]);
				}
				History.EndTransaction();
				_this.drawCommentCells();
			}
		}
		
		
		for (var i = 0; i < _this.aComments.length; i++) {
			var comment = new asc_CCommentData(_this.aComments[i]);
			
			if ( bColumn ) {
				if ( (comment.nCol >= start) && (comment.nCol <= stop) ) {
					comment.asc_putHiddenFlag(bHide);
					aChangedComments.push(comment);
				}
			}
			else {
				if ( (comment.nRow >= start) && (comment.nRow <= stop) ) {
					comment.asc_putHiddenFlag(bHide);
					aChangedComments.push(comment);
				}
			}
		}
		updateCommentsList(aChangedComments);
	};
	
	_this.sortComments = function(activeRange, changes) {
		
		if (changes && activeRange) {
		
			var updateCommentsList = function(aComments) {
				if ( aComments.length ) {
					History.StartTransaction();
					
					for (var i = 0; i < aComments.length; i++) {
						_this.asc_changeComment(aComments[i].asc_getId(), aComments[i], true);
					}
					
					History.EndTransaction();
					_this.drawCommentCells();
				}
			};
			
			var aChangedComments = [];
			for (var i = 0; i < changes.places.length; i++) {
				
				var list = _this.asc_getComments(activeRange.c1, changes.places[i].from);
				for (var j = 0; j < list.length; j++) {
					var comment = new asc_CCommentData(list[j]);
					comment.nRow = changes.places[i].to;
					aChangedComments.push(comment);
				}
			}			
			updateCommentsList(aChangedComments);
		}
	};
	
	_this.resetLastSelectedId = function() {
		_this.cleanLastSelection();
		_this.lastSelectedId = null;
	};
	
	_this.cleanLastSelection = function() {
		if ( _this.lastSelectedId ) {
			var lastComment = _this.asc_findComment(_this.lastSelectedId);
			if ( lastComment ) {
				var lastMetrics = _this.getCellMetrics(lastComment.nCol, lastComment.nRow);
				if ( lastMetrics.result ) {
					var extraOffset = _this.pxToPt(1);
					_this.overlayCtx.clearRect(lastMetrics.left, lastMetrics.top, lastMetrics.width - extraOffset, lastMetrics.height - extraOffset);
				}
			}
		}
	};

	_this.calcCommentsCoords = function(bSave) {

		_this.aCommentCoords = [];

		for (var i = 0; i < _this.aComments.length; i++) {

			var commentCell = _this.aComments[i];
			if (commentCell.asc_getDocumentFlag() || !_this.commentCoordsExist(commentCell.asc_getCol(), commentCell.asc_getRow())) {

				var commentList = _this.asc_getComments(commentCell.asc_getCol(), commentCell.asc_getRow());

				// Calculate coords for document comments
				if (bSave && (commentCell.asc_getCol() == 0) && (commentCell.asc_getRow() == 0)) {
					var documentComments = _this.asc_getDocumentComments();
					for (var j = 0; j < documentComments.length; j++) {
						commentList.push(documentComments[j]);
					}
				}

				if (commentList.length)
					_this.aCommentCoords.push(_this.getCommentsCoords(commentList));
			}
		}
	};

	_this.getCommentsCoords = function(comments) {

		// bWithScroll - учитывать вертикальный и горизонтальный скроллы

		var _this = this;
		var coords = new asc_CCommentCoords();

		function calcCommentArea(comment, coords) {

			/*	User name
			*	Time
			*	Text
			*/

			var originalFont = _this.overlayCtx.getFont();
			var outputFont = originalFont.clone();

			// Set to bold
			outputFont.Bold = true;
			outputFont.FontSize = 9;
			_this.overlayCtx.setFont(outputFont);

			// Title
			var txtMetrics = _this.getTextMetrics(comment.sUserName, 1);
			coords.dHeightPX += _this.ptToPx(txtMetrics.height);
			var userWidth = _this.ptToPx(txtMetrics.width);

			if (coords.dWidthPX < userWidth)
				coords.dWidthPX = userWidth;

			txtMetrics = _this.getTextMetrics(comment.sTime, 1);
			coords.dHeightPX += _this.ptToPx(txtMetrics.height);
			var timeWidth = _this.ptToPx(txtMetrics.width);

			if (coords.dWidthPX < timeWidth)
				coords.dWidthPX = timeWidth;

			// Set to normal
			outputFont.Bold = false;
			outputFont.FontSize = 9;
			_this.overlayCtx.setFont(outputFont);

			// Comment text
			var commentSpl = comment.sText.split('\n'), i;
			for (i = 0; i < commentSpl.length; i++) {
			
				txtMetrics = _this.getTextMetrics(commentSpl[i], 1);
				coords.dHeightPX += _this.ptToPx(txtMetrics.height);
				var lineWidth = _this.ptToPx(txtMetrics.width);
				if (coords.dWidthPX < lineWidth)
					coords.dWidthPX = lineWidth;
			}

			for (i = 0; i < comment.aReplies.length; i++) {
				calcCommentArea(comment.aReplies[i], coords);
			}

			// Min values
			if (coords.dWidthPX < _this.minAreaWidth)
				coords.dWidthPX = _this.minAreaWidth;

			if (coords.dHeightPX < _this.minAreaHeight)
				coords.dHeightPX = _this.minAreaHeight;

			// Calc other coords
			coords.dWidthMM = _this.pxToMm(coords.dWidthPX);
			coords.dHeightMM = _this.pxToMm(coords.dHeightPX);

			var headerRowOffPx = _this.worksheet.getCellTop(0, 0);
			var headerColOffPx = _this.worksheet.getCellLeft(0, 0);

			coords.nCol = comment.nCol;
			coords.nRow = comment.nRow;

			var mergedRange = _this.worksheet.model.getMergedByCell(comment.nRow, comment.nCol);

			coords.nLeft = (mergedRange ? mergedRange.c2 : comment.nCol) + 1;
			if ( !_this.worksheet.cols[coords.nLeft] ) {
				_this.worksheet.expandColsOnScroll(true);
				_this.worksheet.handlers.trigger("reinitializeScrollX");
			}
			
			coords.nTop = mergedRange ? mergedRange.r1 : comment.nRow;
			coords.nLeftOffset = 0;
			coords.nTopOffset = 0;
			
			var fvr = _this.worksheet.getFirstVisibleRow(true);
			var fvc = _this.worksheet.getFirstVisibleCol(true);
			
			var frozenOffset = _this.getFrozenOffset();
			if ( _this.worksheet.topLeftFrozenCell ) {
				if ( comment.nCol >= _this.worksheet.getFirstVisibleCol(false) )
					frozenOffset.offsetX = 0;
				if ( comment.nRow >= _this.worksheet.getFirstVisibleRow(false) )
					frozenOffset.offsetY = 0;
			}

			// Tooltip coords
			coords.dReverseLeftPX = _this.worksheet.getCellLeft(comment.nCol, 0) - _this.worksheet.getCellLeft(fvc, 0) + headerColOffPx + _this.ptToPx(frozenOffset.offsetX);
			coords.dLeftPX = _this.worksheet.getCellLeft(coords.nLeft, 0) - _this.worksheet.getCellLeft(fvc, 0) + headerColOffPx + _this.ptToPx(frozenOffset.offsetX);
			coords.dTopPX = _this.worksheet.getCellTop(coords.nTop, 0) - _this.worksheet.getCellTop(fvr, 0) + headerRowOffPx + _this.ptToPx(frozenOffset.offsetY);

			// Correction for merged cell
			var fvrPx = _this.worksheet.getCellTop(0, 0);
			if (coords.dTopPX < fvrPx)
				coords.dTopPX = fvrPx;

			coords.dLeftMM = _this.worksheet.getCellLeft(coords.nLeft, 3) - _this.worksheet.getCellLeft(fvc, 3);
			coords.dTopMM = _this.worksheet.getCellTop(coords.nTop, 3) - _this.worksheet.getCellTop(fvr, 3);

			var findCol = _this.worksheet._findColUnderCursor(_this.worksheet.getCellLeft(coords.nLeft, 1) + _this.pxToPt(coords.dWidthPX + headerColOffPx) - _this.worksheet.getCellLeft(fvc, 1), true);
			var findRow = _this.worksheet._findRowUnderCursor(_this.worksheet.getCellTop(coords.nTop, 1) + _this.pxToPt(coords.dHeightPX + headerRowOffPx) - _this.worksheet.getCellTop(fvr, 1), true);

			coords.nRight = findCol ? findCol.col : 0;
			coords.nBottom = findRow ? findRow.row : 0;

			coords.nRightOffset = _this.worksheet.getCellLeft(coords.nLeft, 0) + coords.nLeftOffset + coords.dWidthPX + headerColOffPx - _this.worksheet.getCellLeft(coords.nRight, 0);
			coords.nBottomOffset = _this.worksheet.getCellTop(coords.nTop, 0) + coords.nTopOffset + coords.dHeightPX + headerRowOffPx - _this.worksheet.getCellTop(coords.nBottom, 0);

			// Return original font
			_this.overlayCtx.setFont(originalFont);
		}

		for (var i = 0; i < comments.length; i++) {
			calcCommentArea(comments[i], coords);
		}

		if (comments.length) {
			coords.dWidthPX += _this.commentPadding * 2;
			coords.dWidthMM = _this.pxToMm(coords.dWidthPX);

			coords.dHeightPX += _this.commentPadding * 2;
			coords.dHeightMM = _this.pxToMm(coords.dHeightPX);
		}

		return coords;
	};

	_this.commentCoordsExist = function(col, row) {

		var result = false;
		for (var i = 0; i < _this.aCommentCoords.length; i++) {
			if ((col == _this.aCommentCoords[i].nCol) && (row == _this.aCommentCoords[i].nRow))
				return true;
		}
		return result;
	};

	_this.prepareCommentsToSave = function() {

		/*	Calculate the coords of comments for:
		*	first visible col = 0
		*	first visible row = 0
		*	+ document comments -> A1
		*/

		_this.calcCommentsCoords(true);
	};

	_this.cleanSelectedComment = function() {
		if ( _this.lastSelectedId ) {
			var comment = _this.asc_findComment(_this.lastSelectedId);
			if ( comment && !comment.asc_getDocumentFlag() && !comment.asc_getSolved() ) {
				var metrics = _this.getCellMetrics(comment.asc_getCol(), comment.asc_getRow());
				if (metrics.result) 
					_this.overlayCtx.clearRect(metrics.left, metrics.top, metrics.width, metrics.height);
			}
		}
	};
	
	_this.getFrozenOffset = function() {
		
		var offsetX = 0, offsetY = 0, cFrozen = 0, rFrozen = 0, diffWidth = 0, diffHeight = 0;
		if ( _this.worksheet.topLeftFrozenCell ) {
			cFrozen = _this.worksheet.topLeftFrozenCell.getCol0();
			rFrozen = _this.worksheet.topLeftFrozenCell.getRow0();
			diffWidth = _this.worksheet.cols[cFrozen].left - _this.worksheet.cols[0].left;
			diffHeight = _this.worksheet.rows[rFrozen].top - _this.worksheet.rows[0].top;
			
			offsetX = _this.worksheet.cols[_this.worksheet.visibleRange.c1].left - _this.worksheet.cellsLeft - diffWidth;
			offsetY = _this.worksheet.rows[_this.worksheet.visibleRange.r1].top - _this.worksheet.cellsTop - diffHeight;
		}
		return { offsetX: offsetX, offsetY: offsetY };
	};
	
	//-----------------------------------------------------------------------------------
	// Misc methods
	//-----------------------------------------------------------------------------------

	_this.pxToPt = function(val) {
		return val * _this.ascCvtRatio(0, 1);
	};

	_this.ptToPx = function(val) {
		return val * _this.ascCvtRatio(1, 0);
	};

	_this.mmToPx = function(val) {
		return val * _this.ascCvtRatio(3, 0);
	};

	_this.pxToMm = function(val) {
		return val * _this.ascCvtRatio(0, 3);
	};

	_this.ascCvtRatio = function(fromUnits, toUnits) {
		return Asc.getCvtRatio(fromUnits, toUnits, _this.overlayCtx.getPPIX());
	}
}

// Prototype
asc_CCellCommentator.prototype = {

	// Show/Hide

	asc_showComments: function() {
		var _this = this;
		if ( !_this.bShow ) {
			_this.bShow = true;
			_this.drawCommentCells();
		}
	},

	asc_hideComments: function() {
		var _this = this;
		_this.bShow = false;
		_this.drawCommentCells();
		_this.worksheet.model.workbook.handlers.trigger("asc_onHideComment");
	},

	// Main

	asc_showComment: function(id, bNew) {

		var _this = this;
		var comment = _this.asc_findComment(id);

		if (comment) {
			
			var callbackFunc = function(result) {
			
				if ( !result )
					_this.worksheet.model.workbook.handlers.trigger("asc_onLockComment", comment.asc_getId(), comment.asc_getUserId());
				else
					_this.worksheet.model.workbook.handlers.trigger("asc_onUnLockComment", comment.asc_getId());
				
				var commentList = _this.asc_getComments(comment.asc_getCol(), comment.asc_getRow());
				var coords = _this.getCommentsCoords(commentList);

				var indexes = [];
				for (var i = 0; i < commentList.length; i++) {
					indexes.push(commentList[i].asc_getId());
				}

				// Second click - hide comment
				if (indexes.length) {
					if ( _this.lastSelectedId != id )
						_this.worksheet.model.workbook.handlers.trigger("asc_onHideComment");
					
					_this.worksheet.model.workbook.handlers.trigger("asc_onShowComment", indexes, coords.asc_getLeftPX(), coords.asc_getTopPX(), coords.asc_getReverseLeftPX(), bNew);
					_this.drawCommentCells();
				}
				_this.lastSelectedId = id;
			};
			
			_this.isLockedComment(comment, false, callbackFunc);
		}
		else
			_this.lastSelectedId = null;
	},

	asc_selectComment: function(id, bMove) {

		var _this = this;
		var comment = _this.asc_findComment(id);
		
		// Чистим предыдущий селект
		_this.cleanLastSelection();
		_this.lastSelectedId = null;

		if (comment && !comment.asc_getDocumentFlag() && !comment.asc_getSolved()) {
			
			_this.lastSelectedId = id;
			
			var col = comment.asc_getCol();
			var row = comment.asc_getRow();
			var fvc = _this.worksheet.getFirstVisibleCol(true);
			var fvr = _this.worksheet.getFirstVisibleRow(true);
			var lvc = _this.worksheet.getLastVisibleCol();
			var lvr = _this.worksheet.getLastVisibleRow();

			var offset;
			if ( bMove ) {
				if ( (row < fvr) || (row > lvr) ) {
					offset = row - fvr - Math.round(( lvr - fvr ) / 2);
					_this.worksheet.scrollVertical(offset);
					_this.worksheet.handlers.trigger("reinitializeScrollY");
				}
				if ( (col < fvc) || (col > lvc) ) {
					offset = col - fvc - Math.round(( lvc - fvc ) / 2);
					_this.worksheet.scrollHorizontal(offset);
					_this.worksheet.handlers.trigger("reinitializeScrollX");
				}
			}
			
			var metrics = _this.getCellMetrics(col, row);
			if (metrics.result) {
				var extraOffset = _this.pxToPt(1);
				_this.overlayCtx.ctx.globalAlpha = 0.2;
				_this.overlayCtx.beginPath();
				_this.overlayCtx.clearRect(metrics.left, metrics.top, metrics.width - extraOffset, metrics.height - extraOffset);
				_this.overlayCtx.setFillStyle(_this.commentFillColor);
				_this.overlayCtx.fillRect(metrics.left, metrics.top, metrics.width - extraOffset, metrics.height - extraOffset);
				_this.overlayCtx.ctx.globalAlpha = 1;
			}
		}
	},

	asc_findComment: function(id) {

		var _this = this;

		function checkCommentId(id, commentObject) {

			if (commentObject.asc_getId() == id)
				return commentObject;

			for (var i = 0; i < commentObject.aReplies.length; i++) {
				var comment = checkCommentId(id, commentObject.aReplies[i]);
				if (comment)
					return comment;
			}
			return null;
		}

		for (var i = 0; i < _this.aComments.length; i++) {
			var commentCell = _this.aComments[i];
			var obj = checkCommentId(id, commentCell);
			if (obj)
				return obj;
		}
		return null;
	},

	asc_addComment: function(comment) {

		var _this = this;
		var oComment = comment;
		var bChange = false;
		oComment.wsId = _this.worksheet.model.getId();
		oComment.setId();

		if (!oComment.bDocument) {
			oComment.asc_putCol(_this.worksheet.getSelectedColumnIndex());
			oComment.asc_putRow(_this.worksheet.getSelectedRowIndex());

			var existComments = _this.asc_getComments(oComment.nCol, oComment.nRow);
			if ( existComments.length ) {
				oComment = existComments[0];
				bChange = true;
			}
			else {
				if ((oComment.nCol != null) && (oComment.nRow != null)) {
					var cellAddress = new CellAddress(oComment.nRow, oComment.nCol, 0);
					oComment.sQuoteText = cellAddress.getID() + " : " + _this.worksheet.model.getCell(cellAddress).getValueWithFormat();
				}
			}
		}
		
		function callbackFunc(result) {
			if ( !result ) {
				_this.worksheet.model.workbook.handlers.trigger("asc_onLockComment", oComment.asc_getId(), oComment.asc_getUserId());
			}
			else {
				_this.worksheet.model.workbook.handlers.trigger("asc_onUnLockComment", oComment.asc_getId());
				
				// Add new comment
				if ( !bChange ) {
					History.Create_NewPoint();
					History.Add(g_oUndoRedoComment, historyitem_Comment_Add, _this.worksheet.model.getId(), null, new asc_CCommentData(oComment));

					_this.aComments.push(oComment);
					_this.drawCommentCells();
				}
				_this.worksheet.model.workbook.handlers.trigger("asc_onAddComment", oComment.asc_getId(), oComment);
			}
		}
		
		_this.isLockedComment(oComment, true, callbackFunc);
	},

	asc_changeComment: function(id, oComment, bChangeCoords, bNoEvent) {

		var _this = this;
		var comment = _this.asc_findComment(id);
		if (null === comment)
			return;
		
		function callbackFunc(result) {
			if ( !result ) {
				_this.worksheet.model.workbook.handlers.trigger("asc_onLockComment", comment.asc_getId(), comment.asc_getUserId());
			} else {
				_this.worksheet.model.workbook.handlers.trigger("asc_onUnLockComment", comment.asc_getId());
				
				var commentBefore = new asc_CCommentData(comment);
				if (comment) {
					if ( bChangeCoords ) {
						comment.asc_putCol(oComment.asc_getCol());
						comment.asc_putRow(oComment.asc_getRow());
					}
					comment.asc_putText(oComment.asc_getText());
					comment.asc_putQuoteText(oComment.asc_getQuoteText());
					comment.asc_putUserId(oComment.asc_getUserId());
					comment.asc_putUserName(oComment.asc_getUserName());
					comment.asc_putTime(oComment.asc_getTime());
					comment.asc_putSolved(oComment.asc_getSolved());
					comment.asc_putHiddenFlag(oComment.asc_getHiddenFlag());
					comment.aReplies = [];
					
					if ( !comment.bDocument && (comment.nCol != null) && (comment.nRow != null) ) {
						var cellAddress = new CellAddress(comment.nRow, comment.nCol, 0);
						comment.sQuoteText = cellAddress.getID() + " : " + _this.worksheet.model.getCell(cellAddress).getValueWithFormat();
					}

					var count = oComment.asc_getRepliesCount();
					for (var i = 0; i < count; i++) {
						comment.asc_addReply(oComment.asc_getReply(i));
					}
					if ( !bNoEvent )
						_this.worksheet.model.workbook.handlers.trigger("asc_onChangeCommentData", comment.asc_getId(), comment);
				}

				if ( _this.bSaveHistory ) {
					var commentAfter = new asc_CCommentData(comment);

					var compositeComment = new CompositeCommentData();
					compositeComment.commentBefore = commentBefore;
					compositeComment.commentAfter = commentAfter;

					History.Create_NewPoint();
					History.Add(g_oUndoRedoComment, historyitem_Comment_Change, _this.worksheet.model.getId(), null, compositeComment);
				}
				
				_this.drawCommentCells();
			}
		}

		_this.isLockedComment(comment, true, callbackFunc);
	},

	asc_removeComment: function(id, bNoEvent) {

		var _this = this;
		var comment = _this.asc_findComment(id);
		if (null === comment)
			return;
		
		function callbackFunc(result) {
			if ( !result ) {
				_this.worksheet.model.workbook.handlers.trigger("asc_onLockComment", comment.asc_getId(), oComment.asc_getUserId());
			} else {
				_this.worksheet.model.workbook.handlers.trigger("asc_onUnLockComment", comment.asc_getId());
				
				if (comment) {
					var i;
					if (comment.oParent) {
						for (i = 0; i < comment.oParent.aReplies.length; i++) {
							if (comment.asc_getId() == comment.oParent.aReplies[i].asc_getId()) {

								if ( _this.bSaveHistory ) {
									History.Create_NewPoint();
									History.Add(g_oUndoRedoComment, historyitem_Comment_Remove, _this.worksheet.model.getId(), null, new asc_CCommentData(comment.oParent.aReplies[i]));
								}

								comment.oParent.aReplies.splice(i, 1);
								break;
							}
						}
					} else {
						for (i = 0; i < _this.aComments.length; i++) {
							if (comment.asc_getId() == _this.aComments[i].asc_getId()) {

								if ( _this.bSaveHistory ) {
									History.Create_NewPoint();
									History.Add(g_oUndoRedoComment, historyitem_Comment_Remove, _this.worksheet.model.getId(), null, new asc_CCommentData(_this.aComments[i]));
								}

								_this.aComments.splice(i, 1);
								break;
							}
						}
						_this.worksheet.draw();
					}
					_this.drawCommentCells();
					if ( !bNoEvent ) 
						_this.worksheet.model.workbook.handlers.trigger("asc_onRemoveComment", id);
				}
			}
		}

		_this.isLockedComment(comment, true, callbackFunc);
	},

	// Extra functions

	asc_getComments: function(col, row) {

		// Array of root items
		var comments = [];
		var _this = this;
		var _col = col, _row = row, mergedRange = null;
		var length = _this.aComments.length;

		if (!this.bShow)
			return comments;

		if (0 < length) {
			if (null == _col || null == _row) {
				var selectedCell = _this.worksheet.getSelectedRange();
				var oFirst = selectedCell.getFirst();
				_col = oFirst.col - 1;
				_row = oFirst.row - 1;
			} else
				mergedRange = _this.worksheet.model.getMergedByCell(row, col);

			for (var i = 0; i < length; i++) {
				var commentCell = _this.aComments[i];

				if ( !commentCell.asc_getDocumentFlag() /*&& !commentCell.asc_getSolved()*/ && !commentCell.asc_getHiddenFlag() && (commentCell.nLevel == 0) ) {
					if ( !mergedRange ) {
						if ( (_col == commentCell.nCol) && (_row == commentCell.nRow) )
							comments.push(commentCell);
					}
					else {
						if ( (commentCell.nCol >= mergedRange.c1) && (commentCell.nRow >= mergedRange.r1) && (commentCell.nCol <= mergedRange.c2) && (commentCell.nRow <= mergedRange.r2) )
							comments.push(commentCell);
					}
				}
			}
		}
		return comments;
	},

	asc_getDocumentComments: function() {

		// Array of root items
		var comments = [];

		for (var i = 0; i < this.aComments.length; i++) {
			var commentCell = this.aComments[i];
			if ((commentCell.nLevel == 0) && commentCell.asc_getDocumentFlag())
				comments.push(commentCell);
		}
		return comments;
	},

	// Undo/Redo

	Undo: function(type, data) {

		var _this = this, i, parentComment;
		switch (type) {

			case historyitem_Comment_Add:
				if (data.oParent) {
					parentComment = _this.asc_findComment(data.oParent.asc_getId());
					for (i = 0; i < parentComment.aReplies.length; i++) {
						if (parentComment.aReplies[i].asc_getId() == data.asc_getId()) {
							parentComment.aReplies.splice(i, 1);
							break;
						}
					}
				} else {
					for (i = 0; i < _this.aComments.length; i++) {
						if (_this.aComments[i].asc_getId() == data.asc_getId()) {
							_this.aComments.splice(i, 1);
							_this.worksheet.model.workbook.handlers.trigger("asc_onRemoveComment", data.asc_getId());
							break;
						}
					}
				}
				break;

			case historyitem_Comment_Remove:
				if (data.oParent) {
					parentComment = _this.asc_findComment(data.oParent.asc_getId());
					parentComment.aReplies.push(data);
				} else {
					_this.aComments.push(data);
					_this.worksheet.model.workbook.handlers.trigger("asc_onAddComment", data.asc_getId(), data);
				}
				break;

			case historyitem_Comment_Change:
				if (data.commentAfter.oParent) {
					parentComment = _this.asc_findComment(data.commentAfter.oParent.asc_getId());
					for (i = 0; i < parentComment.aReplies.length; i++) {
						if (parentComment.aReplies[i].asc_getId() == data.asc_getId()) {
							parentComment.aReplies.splice(i, 1);
							parentComment.aReplies.push(data.commentBefore);
							break;
						}
					}
				} else {
					for (i = 0; i < _this.aComments.length; i++) {
						if (_this.aComments[i].asc_getId() == data.commentAfter.asc_getId()) {
							_this.aComments.splice(i, 1);
							_this.aComments.push(data.commentBefore);
							_this.worksheet.model.workbook.handlers.trigger("asc_onChangeCommentData", data.commentBefore.asc_getId(), data.commentBefore);
							break;
						}
					}
				}
				break;
		}
	},

	Redo: function(type, data) {

		var _this = this, parentComment, i;
		switch (type) {

			case historyitem_Comment_Add:
				if (data.oParent) {
					parentComment = _this.asc_findComment(data.oParent.asc_getId());
					parentComment.aReplies.push(data);
				} else {
					_this.aComments.push(data);
					_this.worksheet.model.workbook.handlers.trigger("asc_onAddComment", data.asc_getId(), data);
				}
				break;

			case historyitem_Comment_Remove:
				if (data.oParent) {
					parentComment = _this.asc_findComment(data.oParent.asc_getId());
					for (i = 0; i < parentComment.aReplies.length; i++) {
						if (parentComment.aReplies[i].asc_getId() == data.asc_getId()) {
							parentComment.aReplies.splice(i, 1);
							break;
						}
					}
				} else {
					for (i = 0; i < _this.aComments.length; i++) {
						if (_this.aComments[i].asc_getId() == data.asc_getId()) {
							_this.aComments.splice(i, 1);
							_this.worksheet.model.workbook.handlers.trigger("asc_onRemoveComment", data.asc_getId());
							break;
						}
					}
				}
				break;

			case historyitem_Comment_Change:
				if (data.commentBefore.oParent) {
					parentComment = _this.asc_findComment(data.commentBefore.oParent.asc_getId());
					for (i = 0; i < parentComment.aReplies.length; i++) {
						if (parentComment.aReplies[i].asc_getId() == data.asc_getId()) {
							parentComment.aReplies.splice(i, 1);
							parentComment.aReplies.push(data.commentAfter);
							break;
						}
					}
				} else {
					for (i = 0; i < _this.aComments.length; i++) {
						if (_this.aComments[i].asc_getId() == data.commentBefore.asc_getId()) {
							_this.aComments.splice(i, 1);
							_this.aComments.push(data.commentAfter);
							_this.worksheet.model.workbook.handlers.trigger("asc_onChangeCommentData", data.commentAfter.asc_getId(), data.commentAfter);
							break;
						}
					}
				}
				break;
		}
	}
};

window["Asc"]["asc_CCellCommentator"] = window["Asc"].asc_CCellCommentator = asc_CCellCommentator;
prot = asc_CCellCommentator.prototype;

prot["asc_showComments"] = prot.asc_showComments;
prot["asc_hideComments"] = prot.asc_hideComments;

prot["asc_selectComment"] = prot.asc_selectComment;
prot["asc_showComment"] = prot.asc_showComment;
prot["asc_findComment"] = prot.asc_findComment;
prot["asc_addComment"] = prot.asc_addComment;
prot["asc_changeComment"] = prot.asc_changeComment;
prot["asc_removeComment"] = prot.asc_removeComment;

prot["asc_getComments"] = prot.asc_getComments;
prot["asc_getDocumentComments"] = prot.asc_getDocumentComments;