// notes.js
// Copyright (C) 2006 Notefish. All rights reserved.
// notes and sections

// note profile class
// defines note look
function NoteProfile() {}

NoteProfile.prototype.name = "";
NoteProfile.prototype.foreColor = "";
NoteProfile.prototype.backColor = "";

// note class
// represents a single note and manages note html
// all parameters are optional and are intended
// for use with the attached mode
// for efficiency sake, in the attached mode,
// notes DO NOT maintain any properties besides x, y, w, h, id, profile
// this means that an attempt to read, for example, the note title
// is likely to return an emtpy string
function Note(id, h, w, profile)
{
	if (id) this.id = id;
	if (h)  this.h  = h;
	if (w)  this.w  = w;
	if (profile) this.profile = profile;
}

Note.prototype.id = 0;

// when true, the note state was not
// communicated to the server yet
Note.prototype.dirty = false;

// this attribute indicates that the note
// has not been positioned yet
Note.prototype.isnew = false;

// note coordinates on its section
// in section units
Note.prototype.x = -1;
Note.prototype.y = -1;

// note size, in section units
Note.prototype.w = 0;
Note.prototype.h = 0;

Note.prototype.section = null; // owner section

// note "meat"
Note.prototype.title    = "";
Note.prototype.src      = "";
Note.prototype.content  = "";
Note.prototype.modified = "";  // date modified

Note.prototype.profile = "";   // note profile
Note.prototype.html    = null; // note html

// init note
// it is assumed profile is already set
// html is created but not appended to any tree
Note.prototype.init =
	function(attach)
	{
		if (attach) this._attach();
		else{
	        	this._createHtml();
			this.refresh();
		}
	};

// uninit note
Note.prototype.uninit =
	function()
	{
		this._deactivateTools();
		this.html = null;
		this.section = null;
	};

// change note profile
Note.prototype.setProfile =
	function(profile)
	{
		this.profile = profile;
		this.refreshProfile();
	};

// enable/disable dragging support for the note
Note.prototype.enableDrag =
	function(enable)
	{
		var cell = this.html.rows[0].cells[0];
		var bs = document.body.style;
		if (enable){
			this._activateTools();
			cell.style.cursor = "";
		}
		else{
			cell.style.cursor = bs.cursor;
			cell.onmousedown = null;
		}
	};

// refresh note html
Note.prototype.refresh =
	function(refreshProfile)
	{
		var rows = this.html.rows;

		// refresh title
		rows[0].cells[0].innerHTML = this.title!=""?this.title:"&nbsp;";

		// refresh content
		rows[1].cells[0].innerHTML = this.content!=""?this.content:"&nbsp;";

		// refresh source
		// extract the host name for display
		var host = "";
		var re = /https?:\/\/(?:www\.)?([^\/]+)/i;
		var matches = this.src.match(re);
		if (matches && matches.length>1)
			host = matches[1];

		var src = "";
		if (this.src!=""){
			src = "<a href='"+this.src+"' target='_blank' class='note-link'>"+
			      host+"</a>";
		}
		rows[2].cells[0].innerHTML = src!=""?src:"&nbsp;";

		// refresh date
		rows[2].cells[1].innerHTML = this.modified!=""?this.modified:"&nbsp;";

		if (refreshProfile) this.refreshProfile();
	};

// refresh profile-driven note look
Note.prototype.refreshProfile =
	function()
	{
		// retrieve profile colors
		var pro = theApp.profiles[this.profile];
		if (!pro) return;

		var fore = pro.foreColor;
		var back = pro.backColor;

		var html = this.html;
		// now apply the colors to the note
		var s = html.parentNode.style;
		if (s){
			// ...apply to the external cell
			s.borderTopColor = fore;
			s.borderBottomColor = back;
		}

		// ...apply to title
		var cell = html.rows[0].cells[0];
		cell.style.color = fore;
		cell.style.backgroundColor = back;

		// ...apply to the source url
		var s = html.rows[2].cells[0].firstChild.style;
		if (s) s.color = fore;
	};

// create note html
Note.prototype._createHtml =
	function()
	{
		this.html = document.createElement("table");
		var html = this.html;
		html.id = "n-"+this.id;
		html.className = "note";
		html.cellSpacing = 0;
		html.cellPadding = 0;

		// forming the note caption
		var row = html.insertRow(-1);
		var cell = row.insertCell(-1);
		cell.className = "note-caption";
		if (ReadOnlyMode) cell.style.cursor = "default";
		cell.colSpan = 2;

		this._activateTools();

		// now the note content
		row = html.insertRow(-1);
		cell = row.insertCell(-1);
		cell.colSpan = 2;
		cell.className = "note-cont";

		// and finally, the note source and date
		row = html.insertRow(-1);
		cell = row.insertCell(-1);
		cell.className = "note-src";
		cell = row.insertCell(-1);
		cell.className = "note-date";
	};

// attach the note to existing html
Note.prototype._attach =
	function()
	{
		this.html = GetElem("n-"+this.id);
		this._activateTools();
	};

// activate dragging support
Note.prototype._activateTools =
	function()
	{
		if (ReadOnlyMode) return;

		var note = this; // to create closure

		this.html.ownerNote = this;

		// dragging support
		var cell = this.html.rows[0].cells[0];
		cell.onmousedown = function(e)
			{ theApp.noteMover.moveNote(note, e); };
	};

Note.prototype._deactivateTools =
	function()
	{
		if (ReadOnlyMode) return;
		if (!this.html) return;

		this.html.ownerNote = null;
		this.html.rows[0].cells[0].onmousedown = null;
	};

// cell class
// a single cell on a section
function Cell() {}

Cell.prototype.html = null;  // pointer to html cell (td)
Cell.prototype.note = null;  // pointer to the note (if any)

// a numeric range structure
function Range() {}

Range.prototype.t0 = 0;
Range.prototype.t1 = 0;

// section class
// a section of the document
function Section(id, idx, title)
{
	if (id) this.id = id;
	if (idx) this.idx = idx;
	if (title) this.title = title;
}

Section.prototype.title = ""; // section title
Section.prototype.id = 0;
Section.prototype.idx = 0;

// internal id, assigned by the application
// internal id is used until the server provides
// the real id
Section.prototype.intId = 0;

// section size
Section.prototype.width  = SectionWidth;
Section.prototype.height = MinSectionHeight;
Section.prototype.minHeight = MinSectionHeight;

Section.prototype.cells       = null; // 2D array that describes the section
Section.prototype.html        = null; // section's root html element
Section.prototype.htmlHeader  = null; // section header html
Section.prototype.htmlTitle   = null; // html that represents section title
Section.prototype.htmlBody    = null; // html that represents section body
Section.prototype.notes       = null; // all notes currently on the section

Section.prototype.colw = 0; // single column width

// new note insertion support
Section.prototype.insY = 0; // column at which a new note will be put to
Section.prototype.insX = 0; // row at which a new note will be put to

// initialization
// though this function creates section html
// it does not append it to any document tree
// caller is expected to do it
// when attach is true, the section attaches
// to the existing html, instead of generating one
Section.prototype.init =
	function(attach)
	{
		// init in-memory structures
		this.notes = new Array();
		this.cells = new Array();

		if (attach) this._attach();
		else this._init();
	};

// uninit the section
// should be called to avoid memory leaks in IE
Section.prototype.uninit =
	function()
	{
		this.uninitCtrls();

		var notes = this.notes;
		if (!notes) return;

		var len = notes.length;
		for (var i=0; i<len; ++i){
			if (notes[i]){
				notes[i].uninit();
				notes[i] = null;
			}
		}
		this.notes = null;

		this.html       = null;
		this.htmlHeader = null;
		this.htmlTitle  = null;
		this.htmlBody   = null;
	};

// set/change section title
Section.prototype.setTitle =
	function(title)
	{
		this.title = title;
		this.htmlTitle.innerHTML = title!=""?SafeHtml(title):"&nbsp;";
	};

// hide the section title if it's empty
Section.prototype.hideEmptyTitle =
	function()
	{
		if (this.title==""){
			this.htmlHeader.style.display = "none";
			this.html.style.marginTop = "1em";
		}
	};

// show the section title
Section.prototype.showTitle =
	function()
	{
		this.htmlHeader.style.display = "";
		this.html.style.marginTop = "";
	};

// tell if the section title is hidden
Section.prototype.isTitleHidden =
	function()
	{
		return this.htmlHeader.style.display == "none";
	};

// set the minial height for the section
Section.prototype.setMinHeight =
	function(h)
	{
		var oldh = this.minHeight;
		if (oldh==h) return;
		this.minHeight = h;
		if (h<oldh){
			this.delBlanks(h, oldh-h);
		}
		else this._insertBlanks(this.height, h-oldh);
	};

// add a note in the attached mode:
// when html is generated by server, and javascript
// objects need to attach to existing html
// the note is assumed to be initialized in attached mode
Section.prototype.attachNote =
	function(y, x, note)
	{
		this._storeNote(note);

		note.y = y;
		note.x = x;
		note.section = this;

		var cell = note.html.parentNode;
		this._setCells(y, x, y+note.h, x+note.w, cell, note);

		this.minInsPos(y+note.h);
	};

// insert note into the coordinates specified
// in case new position for a note
// overlaps with some other notes, insertNote will
// try to create more space by adding rows to the section, as needed
Section.prototype.insertNote =
	function(y, x, note)
	{
		if (!this.canPlaceNote(note, y, x)){
			// the space is occupied
			// check if the note can simply be put
			// below its hi notes or above its lo notes
			// and if not, let's create some space
			var rng = this.getLowHi(y, x, note);
			if (rng.t0!=-Infinity && this.canPlaceNote(note, rng.t0+1, x)){
				// put the note below its hi notes
				y = rng.t0+1;
			}
			else if (rng.t1!=Infinity &&  this.canPlaceNote(note, rng.t1-note.h-1, x)){
				// put the note above its lo notes
				y = rng.t1-note.h-1;
			}
			else{
				// we have to add space
				var hi = rng.t0!=-Infinity?rng.t0:y;
				var lo = rng.t1!= Infinity?rng.t1:y+note.h;
				var hiRows = hi-y;
				var loRows = y+note.h-lo;

				var rows = hiRows+loRows;
				// take into account 1 row margins for neighbour notes, if any
				if (rng.t0!=-Infinity) ++rows;
				if (rng.t1!= Infinity) ++rows;

				// at this point there is still the possbility
				// that there is not enough space to place the note
				// because the section lower border is too close
				if (rng.t1==Infinity && rng.t0<=y+note.h && y+note.h>this.height){
					var offset = 0;
					if (rng.t0!=-Infinity) offset = hiRows+1;
					if (y+offset+note.h>this.height-rows)
						rows = y+offset+note.h-this.height;
				}

				var rowhis = this._getRowHiNotes(y);
				var rowlos = this._getRowLoNotes(y);

				// temporarily delete the row hi notes
				// then insert blank rows, and insert hi notes again
				var hicnt = rowhis.length;
				for (var i=0; i<hicnt; ++i) this.removeNote(rowhis[i]);
				this._insertBlanks(y<=this.height?y:this.height, rows);
				if (rng.t0!=-Infinity) y += (hiRows+1);

				// it may happen at this point that the note
				// touches some of its lo notes with its bottom edge
				// the reason is transformation from pixel coords to rows and cols
				// if this is the case, let's insert another blank line
				var oneMore = false;
				if (y+note.h<this.height){
					var x1 = x+note.w;
					var bottom = this.cells[y+note.h];
					for (var i=x; i<x1; ++i)
						if (bottom[i].note && bottom[i].note!=note){
							oneMore = true;
							break;
						}
				}
				if (oneMore){
					this._insertBlanks(y, 1);
					++rows;
				}

				// insert hi notes again
				for (var i=0; i<hicnt; ++i){
					var n = rowhis[i];
					this._placeNote(n, n.y, n.x);
				}

				// adjusting coordiantes of lo notes
				var locnt = rowlos.length;
				for (var i=0; i<locnt; ++i){
					rowlos[i].y += rows;
					rowlos[i].dirty = true;
				}
			}
		}
		this._placeNote(note, y, x);
	};

// remove note from the section
Section.prototype.removeNote =
	function(note)
	{
		// remove the note from the note list
		var notes = this.notes;
		var cnt = this.notes.length;
		var i;
		for (i=0; i<cnt; ++i) if (notes[i]==note){
			notes[i] = null;
			break;
		}
		if (i==cnt) return false;

		var x0 = note.x;
		var y0 = note.y;
		var x1 = x0+note.w;
		var y1 = y0+note.h;

		var cells = this.cells;
		var noteCell = cells[y0][x0].html;
		var noteSpan = noteCell.colSpan;
		var noteCellIdx = noteCell.cellIndex; // may need this to consolidate blanks
		var row = noteCell.parentNode;

		// what we need to do here is to remove the note cell
		// and consolidate surrounding blank cells

		// first, remove the note cell
		row.removeChild(noteCell);

		for (i = y0; i<y1; ++i){
			row = this.htmlBody.rows[i+1]; // +1 factors the "guiding row" in

			// find left and right blank cells
			var cell0 = null; // left blank cell (if any)
			var cell1 = null; // right blank cell (if any)
			if (x0>0 && !cells[i][x0-1].note)
				cell0 = cells[i][x0-1];
			if (x1<this.width && !cells[i][x1].note)
				cell1 = cells[i][x1];

			// determine x coord and span of the resulting blank cell
			var xcoord = cell0?this._cellX(i, x0-1):x0;

			var span = noteSpan; // note column span
			if (cell0) span += cell0.html.colSpan;
			if (cell1) span += cell1.html.colSpan;
			// now the span has the total span for the blank cell

			// consolidate the blank cells
			var cell = 0;
			if (cell0){ // extend left cell, remove right cell if any
				cell0.html.colSpan = span;
				if (cell1) row.removeChild(cell1.html);
				cell = cell0.html;
			}
			else if (cell1){ // there is no cell0, so just extend cell1
				cell1.html.colSpan = span;
				cell = cell1.html;
			}
			else{ // there are no blank cells at all, insert one
				var cell_i = cells[i];
				// compute a new blank cell's index
				var idx = x0-1;
				for (; idx>=0; --idx){
					var n = cell_i[idx].note;
					if (n && n.y==i) break;
					if (!n) break;
				}
				var insIdx = 0;
				if (idx>=0) insIdx = cell_i[idx].html.cellIndex+1;

				// now insert a new blank cell
				cell = row.insertCell(insIdx);
				this._initBlank(cell, span);
			}

			this._setCells(i, xcoord, i+1, xcoord+span, cell, null);
		}

		return true;
	};

// check if a note can be placed at the coords specified
Section.prototype.canPlaceNote =
	function(note, y, x)
	{
		if (x<0) return false;
		if (y<0) return false;
		if (y+note.h>this.height) return false;
		if (x+note.w>this.width) return false;

		// make sure the space is not occupied
		var x1 = x+note.w;
		var y1 = y+note.h;
		var cells = this.cells;
		for (var i=y; i<y1; ++i)
			for (var j=x; j<x1; ++j){
				var c = cells[i][j];
				if (c.note && c.note!=note) return false;
			}

	        return true;
	};

// get a note from pixel coordinates
// note that unlike most methods, x preceds y
// in the parameter list
Section.prototype.noteFromPoint =
	function(x, y)
	{
		var notes = this.notes;
		var len = notes.length;
		for (var i=0; i<len; ++i){
			if (!notes[i]) continue;
			var nhtml = notes[i].html;
			nx = GetAbsLeft(nhtml);
			ny = GetAbsTop(nhtml);
			nw = nhtml.parentNode.offsetWidth;
			nh = nhtml.parentNode.offsetHeight;

			if (x>=nx && x<=nx+nw && y>=ny && y<=ny+nh)
				return notes[i];
		}
	};

// get the width of a single cell in the section
Section.prototype.getSingleCellWidth =
	function()
	{
		// I take the last cell since firefox sometimes 
		// makes the first cell larger than it should be
		return this.htmlBody.rows[0].cells[this.width - 1].offsetWidth;
	};

// left border of the section, in pixels
Section.prototype.getPxLeft = 
	function()
	{
		return GetAbsLeft(this.htmlBody.rows[0].cells[0]);
	};

// right border of the section, in pixels
Section.prototype.getPxRight = 
	function()
	{
		var c = this.htmlBody.rows[0].cells[SectionWidth - 1];
		return GetAbsLeft(c) + c.offsetWidth;
	};

// find the note by id
Section.prototype.findNote =
	function(nid)
	{
		var notes = this.notes;
		if (!notes) return null;
		var len = notes.length;
		for (var i=0; i<len; ++i)
			if (notes[i] && notes[i].id==nid)
				return notes[i];
		return null;
	};

// return the number of notes in the section
Section.prototype.getNoteCount =
	function()
	{
		var res = 0;
		var notes = this.notes;
		var len = notes.length;
		for (var i=0; i<len; ++i) if (notes[i]) ++res;
		return res;
	};

// set the minial insert Y position for new notes
// the function also resets the X position
Section.prototype.minInsPos =
	function(y)
	{
		if (this.insY < y+1){
			this.insY = y+1;
			this.insX = 0;
		}
	};

// set the maximal insert Y position for new notes
// the function also resets the X position
Section.prototype.maxInsPos =
	function(y)
	{
		if (this.insY > y+1){
			this.insY = y+1;
			this.insX = 0;
		}
	}

// make sure the insert position can be used
// to insert a note of the given width
Section.prototype.ensureInsPos =
	function(noteWidth)
	{
		var w = this.width;
		var h = this.height;
		var c = this.cells;

		// first, scan horizontally
		// to take into account the most recenly
		// inserted note
		var j = this.insX;
		for (;j<w && this.insY<h; ++j)
			if (!c[this.insY][j].note) break;
		if (j>0) ++j; // to make sure there is spacing between notes
		this.insX = j;

		if (this.insX+noteWidth<=w) return;

		// scan down until an empty line is found
		var i = this.insY;
		for (;i<h; ++i){
			if (c[i][0].html.colSpan==w) break;
		}
		if (i>0) ++i; // for an empty line
		this.insY = i;
		this.insX = 0;
	};

// attach section to existing html
Section.prototype._attach =
	function()
	{
		// init html
		this.html = GetElem("s-"+this.id);
		this.htmlHeader = this.html.firstChild;
		this.htmlTitle = this.htmlHeader.firstChild;
		this.htmlBody = this.htmlHeader.nextSibling;

		this.height = this.htmlBody.rows.length-1;

		this.initCtrls();

		// in-memory structures
		this._initCells();
		// attach them to html
		var row = this.htmlBody.rows[1];
		var i = 0;
		var j = 0;
		var cells = this.cells;
		var tw = this.width;
		while (row){
			j = 0;
			while(j<tw && cells[i][j].html) ++j;
			var c = row.cells[0];
			while (c){
				var w = c.colSpan;
				var h = c.rowSpan;
				this._setCells(i, j, i+h, j+w, c, null);
				j += w;
				c = c.nextSibling;
				while(j<tw && cells[i][j].html) ++j;
			}
			row = row.nextSibling;
			++i;
		}
	};

// init section controls
Section.prototype.initCtrls =
	function()
	{
		if (!ReadOnlyMode) this.htmlHeader.ownerSection = this;
	};

// uninit section controls
Section.prototype.uninitCtrls =
	function()
	{
		if (ReadOnlyMode) return;
		if (this.htmlHeader)
			this.htmlHeader.ownerSection = null;
	};

// init section html
Section.prototype._init =
	function()
	{
		// in-memory structures
		this._initCells();
		var c = this.cells;
		var w = this.width;
		var h = this.height;

		// init html
		this.html = document.createElement("div");

		// init the section header html
		var header = document.createElement("div");
		header.className = this.id!=0?"section-title":"virt-sec-title";
		var title = document.createElement("span");
		title.innerHTML = this.title!=""?this.title:"&nbsp;";
		header.appendChild(title);
		this.htmlHeader = header;
		this.htmlTitle = title;

		this.html.appendChild(header);

		// init the section body html
		var htmlBody = document.createElement("table");
		htmlBody.className = "section";
		htmlBody.cellSpacing = 0;
		htmlBody.cellPadding = 0;
		this.htmlBody = htmlBody;

		// insert a row with invisible cells that define table size
		// in ideal world, this would be solved with "col" but
		// neither IE nor FF do a good job with it

		var row = htmlBody.insertRow(-1);
		for (var i=0; i<w; ++i){
			var cell = row.insertCell(-1);
			cell.className = "ic";
			cell.innerHTML = "&nbsp;";
		}

		// adding rows with wide cells
		// cells will be broken down or consolidated
		// as notes get inserted/deleted/moved
		for (var i=0; i<h; ++i){
			var row = htmlBody.insertRow(-1);
			var cell = row.insertCell(-1);
			this._initBlank(cell, w);

			for (var j=0; j<w; ++j) c[i][j].html = cell;
		}

		this.html.appendChild(htmlBody);
	};

// init the in-memory section map
Section.prototype._initCells =
	function()
	{
		// init the in-memory section map
		var c = this.cells;
		var w = this.width;
		var h = this.height;
		for (var i=0; i<h; ++i){
			c[i] = new Array();
			for (var j=0; j<w; ++j)
				c[i][j] = new Cell();
		}
	};

// place a note to the coordinates specified
// note should have w, h set
// if the space is occupied by some other note
// the function fails (returns false)
Section.prototype._placeNote =
	function(note, y, x)
	{
		if (!this.canPlaceNote(note, y, x)) return false;

		this._storeNote(note);

		// blank cells are expected to be consolidated in rows
		// this means there is only one blank cell on each row
		// and this blank cell may need to be broken down

		var notey1 = y+note.h;
		var notex1 = x+note.w;

		// copy coordinates to the note
		note.x = x;
		note.y = y;

		note.section = this;

		var cells = this.cells;

		// first, insert the note
		var row = this.htmlBody.rows[y+1]; // +1 factors the "guiding row" in
		var noteCell = row.insertCell(cells[y][x].html.cellIndex+1);
		noteCell.colSpan = note.w;
		noteCell.rowSpan = note.h;
		noteCell.className = "note-cell";
		noteCell.appendChild(note.html);

		// now, breakdown blank cells
		var y1 = y+note.h;
		for (var i=y; i<y1; ++i){
			var cell0 = cells[i][x].html;
			var c0iSpan = cell0.colSpan;
			var c0x = this._cellX(i, x);
			var c0Span  = x - c0x;
			var c1Span  = c0x+c0iSpan-notex1;
			var row = cell0.parentNode;

			// adjusting cells, the order matters!
			// insert if needed the right blank cell
			var cell1 = null;
			if (c1Span!=0){
				var insIdx = cell0.cellIndex+1;
				if (i==y) insIdx++; // first row has the new note
				cell1 = row.insertCell(insIdx);
				this._initBlank(cell1, c1Span);
			}

			// adjust (or throw away) the left blank cell
			if (c0Span!=0) cell0.colSpan = c0Span;
			else row.removeChild(cell0);

			this._setCells(i, notex1, i+1, notex1+c1Span, cell1, null);
		}

		// final adjustment: note cells should point to the note
		this._setCells(y, x, notey1, notex1, noteCell, note);

		// refresh note profile
		// this is needed since note profiles affects
		// the newly added note cell
		note.refreshProfile();

		return true;
	};

// store a note in the internal note list
Section.prototype._storeNote =
	function(note)
	{
		var notes = this.notes;
		var cnt = notes.length;
		var idx = 0;
		for (; idx<cnt; ++idx){
			if (notes[idx]==null) break;
			if (notes[idx]==note) throw "Duplicate note!";
		}
		notes[idx] = note;
	};

// return the list of dirty notes
// the dirty notes become clean
Section.prototype.getDirtyNotes =
	function()
	{
		var res = new Array();

		var notes = this.notes;
		var len = notes.length;
		for (var i=0; i<len; ++i){
			var n = notes[i];
			if (n && n.dirty){
				n.dirty = false;
				res.push(n);
			}
		}

		return res;
	};

// initialize a blank cell
Section.prototype._initBlank =
    function(cell, w)
    {
		cell.className = "blank-cell";
		cell.innerHTML = "&nbsp;";
		cell.colSpan = w;
    };

// return left coordinate of the cell
// a cell may span multiple columns and
// x identifies one of these columns
Section.prototype._cellX =
	function(y, x)
	{
		var cells = this.cells;
		var html = cells[y][x].html;
		var i = x;
		for (; i>=0 && cells[y][i].html==html; --i);
		return i+1;
	};

// for the rectangle defined by the coords
// and the note's dimensions specified
// find the highest bottom and lowest top where
// highest bottom is the max bottom value for
// note rectangles that intersect the input note and
// whose top is less than y;
// lowest top is the minimum of top values for
// note rectangles that intersect the input note and
// whose top is greater than y
// (the algorithm is probably less obscure than this comment!)
Section.prototype.getLowHi =
	function(y, x, note)
	{
		var res = new Range();
		res.t0 = -Infinity;
		res.t1 =  Infinity;
		var cells = this.cells;
		var x1 = x+note.w;
		var y1 = y+note.h;
		if (y1>this.height) y1 = this.height;

		for (var i = y; i<y1; ++i){
			for (var j = x; j<x1; ++j){
				var n = cells[i][j].note;
				if (n){
					if (n==note) continue;
					if (n.y<=y){ // higher note
						if (n.y+n.h>res.t0) res.t0 = n.y+n.h;
					}
					else        // lower note
						if (n.y<res.t1) res.t1 = n.y;
				}
			}
		}

		return res;
	};

// return an array of hi notes for a row
// a note is hi for a row if it intersects the row
// and its top is located higher than the row
Section.prototype._getRowHiNotes =
	function(y)
	{
		var res = new Array();
		var notes = this.notes;
		var cnt = notes.length;
		for (var i=0; i<cnt; ++i){
			var note = notes[i];
			if (note && note.y<=y && note.y+note.h>y)
				res[res.length] = note;
		}

		return res;
	};

// return an array of lo notes for a row
// a note is lo for a row if its top lies
// below the row
Section.prototype._getRowLoNotes =
	function(y)
	{
		var res = new Array();
		var notes = this.notes;
		var cnt = notes.length;
		for (var i=0; i<cnt; ++i){
			var note = notes[i];
			if (note && note.y>y)
				res[res.length] = note;
		}

		return res;
	};

Section.prototype.logCells =
	function()
	{
		if (!EnableDebug) return;

		var cells = this.cells;
		debug.log("Cell dump:");
		for (var i=0; i<this.height; ++i) this.logRow(i);
		debug.log("");
	};

// log a single row
Section.prototype.logRow =
	function(i)
	{
		var cells = this.cells;
		var s = i.toString();
		if (s.length==1) s = "0"+s;
		if (s.length==2) s = "0"+s;
		s += ": ";
		for (j=0; j<this.width; ++j){
			if (cells[i][j].note){
				var id = cells[i][j].note.id.toString();
				if (id.length==1) id = "0"+id;
				//s += id;
				s += "XX";
			}
			else{
				span = cells[i][j].html.colSpan;
				s += "[";
				for (var x=0; x<span-1; ++x) s += "--";
				s += "]";
				j += span-1;
			}
		}
		debug.log(s);
	};

// set up in memory structure with values supplied
Section.prototype._setCells =
	function(y0, x0, y1, x1, html, note)
	{
		var cells = this.cells;
		for (var i=y0; i<y1; ++i)
			for (var j=x0; j<x1; ++j){
				var cell = cells[i][j];
				cell.html = html;
				cell.note = note;
			}
	};


// insert blank rows at the position specified by idx
// the assumption is there are no hi notes between idx and idx+rows
Section.prototype._insertBlanks =
	function(idx, rows)
	{
		var cells = this.cells;

		// first, extend the in-memory cell section
		var oldlen = cells.length;
		var newlen = oldlen+rows;
		var w = this.width;
		for (var i=newlen-1; i>=oldlen; --i)
			cells[i] = null;

		// now shift cells down to create a gap at idx
		for (var i=newlen-1; i>=idx; --i)
			cells[i] = cells[i-rows];

		// insert blank html rows
		// this is valid since there are no hi notes
		var html = this.htmlBody;
		var lastidx = idx+rows;
		for (var i=idx; i<lastidx; ++i){
			var row = html.insertRow(i+1); // +1 factors the "guiding row" in
			var cell = row.insertCell(-1);
			this._initBlank(cell, w);

			cells[i] = new Array();
			for (var j=w-1; j>=0; --j) cells[i][j] = new Cell();
			this._setCells(i, 0, i+1, w, cell, null);
		}

		this.height += rows;
	};

// delete blank rows from top down
// the functions makes sure one blank line is left
// the parameters specify the range to look for blank rows
// the function retuns the number of rows deleted
Section.prototype.delBlanks =
	function(row, cnt)
	{
		if (row>=this.height-1) return 0;
		if (cnt==0) return 0;

		if (row<0) row = 0;
		if (row+cnt>this.height) cnt = this.height-row;
		var fal = row+cnt; // 'first after last' index

		var cells = this.cells;
		var html  = this.htmlBody;

		var res = 0;

		var top = row;
		var btm = row;

		while (top<fal && this.height>this.minHeight){
			// searching for a range of blanks

			// first, find the top blank row in the range
			while (top<fal){
				var c = cells[top][0];
				if (!c.note && c.html.colSpan==SectionWidth) break;
				++top;
			}

			// now, find the bottom of the range
			btm = top;
			while (btm<fal){
				var c = cells[btm][0];
				if (c.note || c.html.colSpan!=SectionWidth) break;
				++btm;
			}

			// let's clean the range leaving only up to one row in it
			var dels = btm-top-1;
			if (!top) ++dels;
			if (dels>0 && this.height-dels<this.minHeight)
				dels = this.height-this.minHeight;

			if (dels>0){
				var lows = this._getRowLoNotes(top);
				cells.splice(top, dels);
				this.height -= dels;
				fal         -= dels;
				res         += dels;
				for (var i=0; i<dels; ++i)
					html.deleteRow(top+1); // +1 factors the guiding row in

				// correct the affected notes
				var lcnt = lows.length;
				for (var i=0; i<lcnt; ++i){
					lows[i].y -= dels;
					lows[i].dirty = true;
				}
			}
			++top;
			btm = top;
		}

		// update position for new notes
		var ipos = this.height-1;
		var w = this.width;
		while (ipos>0 && cells[ipos][0].html.colSpan==w) --ipos;
		this.maxInsPos(ipos?ipos+1:-1);
		return res;
	};
