this.author = "cim";
this.copyright = "� 2011-2014 cim.";
this.licence = "CC-BY-SA 3.0";
this.version = "0.8.2";
this.name = "Ships Library";
this.description = "An e-book library readable on the F4 screen";

/* Public */
this._registerBook = function (key, title, contents) {
	this.$books[key] = { title: title, contents: contents };
}

this._unregisterBook = function (key) {
	if (key == "ingame-manual") {
		return false; // can't remove this one
	}
	if (this.$currentBook == key) {
		if (mission.screenID == "ships-library") {
			// book is currently loaded and mission screen active
			return false;
		}
		this._setBook("ingame-manual");
	}
	delete this.$books[key];
	return true;
}

this.startUp = function () {
	delete this.startUp;

	this.$fcbM = false;
	this.$hudHidden = false;
	this.$manualActive = false;
	this.$currentBook = false;
	this.$spaceWidth = defaultFont.measureString(" ");

	// settings for mission screen reading
	this.$chapter = 0;
	this.$offset = 0;
	this.$msStore = "offsets";
	this.$msRows = 15;
	this.$msCols = 32;

	// settings for MFD in-flight reading
	this.$mfdChapter = 0;
	this.$mfdOffset = 0;
	this.$mfdStore = "miniOffsets";
	this.$mfdRows = 10;
	this.$mfdCols = 14.75;

	this.$mfdMode = 0;
	this.$mfdOn = false;

	// initialise
	this.$minblock = 3;
	this.$chaptersperpage = 20;

	this.$books = new Object;
	this._registerIngameManual();
	this.$mode = "firstrun";

	if (missionVariables.ships_library_bookmark) {
		this.$bookmark = JSON.parse(missionVariables.ships_library_bookmark);
	} else {
		this.$bookmark = { "ingame-manual": [0, 0, 0, 0] };
	}

	this._setBook("ingame-manual");


}

this.startUpComplete = function () {
	this._initInterface(system.mainStation);
	this._initInterface(player.ship.dockedStation);
}


/* Private and handlers */

this.playerWillSaveGame = function () {
	missionVariables.ships_library_bookmark = JSON.stringify(this.$bookmark);
}

this.shipDockedWithStation = function (station) {
	this._initInterface(station);
}

this._initInterface = function (station) {
	station.setInterface(this.name, {
		title: expandMissionText("ships-library-title"),
		category: expandDescription("[interfaces-category-ship-systems]"),
		summary: expandMissionText("ships-library-summary"),
		callback: this._showLibrary.bind(this)
	});
}

this._registerIngameManual = function () {
	var contents = [
		{ level: 0, key: "ingame-manual-frontpage", params: [this._getTip], opts: { model: "[" + player.ship.dataKey + "]", spinModel: false } },
		{ level: 0, key: "ingame-manual-welcome" },
		{ level: 0, key: "ingame-manual-controls" },
		{ level: 1, key: "ingame-manual-ship-systems" },
		{ level: 2, key: "ingame-manual-ship-systems-status", backgrounds: ["ingame-manual-f5.png", ""] },
		{ level: 2, key: "ingame-manual-ship-systems-manifest", backgrounds: ["ingame-manual-f5f5.png", ""] },
		{ level: 2, key: "ingame-manual-ship-systems-src", backgrounds: ["ingame-manual-f6.png", ""] },
		{ level: 2, key: "ingame-manual-ship-systems-lrc", backgrounds: ["ingame-manual-f6f6.png", ""] },
		{ level: 2, key: "ingame-manual-ship-systems-planet", backgrounds: ["ingame-manual-f7.png", ""] },
		{ level: 2, key: "ingame-manual-ship-systems-commodity", params: [function () { return player.ship.cargoSpaceCapacity; }], backgrounds: ["ingame-manual-f8.png", ""] },
		{ level: 1, key: "ingame-manual-station-systems" },
		{ level: 2, key: "ingame-manual-station-systems-launch", backgrounds: ["ingame-manual-f1.png"] },
		{ level: 2, key: "ingame-manual-station-systems-equipment", backgrounds: ["ingame-manual-f3.png", ""] },
		{ level: 2, key: "ingame-manual-station-systems-shipyard", backgrounds: ["ingame-manual-f3f3.png", ""] },
		{ level: 2, key: "ingame-manual-station-systems-interfaces", backgrounds: ["ingame-manual-f4.png", ""] },
		{ level: 1, key: "ingame-manual-inflight" },
		{ level: 2, key: "ingame-manual-inflight-view" },
		{ level: 2, key: "ingame-manual-inflight-hud", params: [function () { return Math.max(1, Math.floor(player.ship.maxEnergy / 64)); }], backgrounds: ["ingame-manual-hud.png"] },
		{ level: 3, key: "ingame-manual-inflight-hud-scanner", backgrounds: ["ingame-manual-hud.png"] },
		{ level: 2, key: "ingame-manual-inflight-flight" },
		{ level: 2, key: "ingame-manual-inflight-docking", backgrounds: ["ingame-manual-dock0.png", "ingame-manual-dock1.png", "ingame-manual-dock2.png", "ingame-manual-dock3.png", "ingame-manual-dock4.png"] },
		{ level: 2, key: "ingame-manual-inflight-navigation", params: [function () { if (player.ship.hasHyperspaceMotor) { return expandDescription("[ingame-manual-hyperspaceinfo]", { "spintime": player.ship.hyperspaceSpinTime }); } else { return "" } }, function () { if (player.ship.hasHyperspaceMotor) { return expandDescription("[ingame-manual-outoffuel]"); } else { return expandDescription("[ingame-manual-nohyperdrive]") } }] },
		{ level: 2, key: "ingame-manual-inflight-combat" },
		{ level: 3, key: "ingame-manual-inflight-combat-laser", params: [function () { var m = player.ship.weaponFacings; return (m & 1) + ((m & 2) / 2) + ((m & 4) / 4) + ((m & 8) / 8); }], backgrounds: ["ingame-manual-laser0.png"] },
		{ level: 3, key: "ingame-manual-inflight-combat-missile", params: [function () { return player.ship.missileCapacity; }], backgrounds: ["ingame-manual-missile0.png"] },
		{ level: 0, key: "ingame-manual-equipment" },
		{ level: 1, key: "ingame-manual-equipment-standard" },
		{ level: 2, key: "ingame-manual-equipment-standard-hull" },
		{ level: 2, key: "ingame-manual-equipment-standard-generators" },
		{ level: 2, key: "ingame-manual-equipment-standard-capacitors", params: [function () { return Math.ceil(player.ship.maxEnergy / 64); }] },
		{ level: 2, key: "ingame-manual-equipment-standard-support" },
		{ level: 2, key: "ingame-manual-equipment-standard-crew" },
		{ level: 2, key: "ingame-manual-equipment-standard-engines" },
		{ level: 2, key: "ingame-manual-equipment-standard-shields" },
		{ level: 2, key: "ingame-manual-equipment-standard-sensors" },
		{ level: 2, key: "ingame-manual-equipment-standard-cargo" },
		{ level: 2, key: "ingame-manual-equipment-standard-witchdrive" },
		{ level: 1, key: "ingame-manual-equipment-trading" },
		{ level: 2, key: "ingame-manual-equipment-trading-cargo" },
		{ level: 2, key: "ingame-manual-equipment-trading-scoops", backgrounds: ["ingame-manual-scoops0.png", "ingame-manual-scoops1.png"] },
		{ level: 2, key: "ingame-manual-equipment-trading-heat", backgrounds: ["ingame-manual-heat.png"] },
		{ level: 2, key: "ingame-manual-equipment-trading-passenger" },
		{ level: 2, key: "ingame-manual-equipment-trading-ana", backgrounds: ["ingame-manual-ana.png"] },
		{ level: 2, key: "ingame-manual-equipment-trading-asc", backgrounds: ["ingame-manual-asc.png"] },
		{ level: 2, key: "ingame-manual-equipment-trading-docking" },
		{ level: 2, key: "ingame-manual-equipment-trading-galactic" },
		{ level: 1, key: "ingame-manual-equipment-combat" },
		{ level: 2, key: "ingame-manual-equipment-combat-lasers", backgrounds: ["ingame-manual-laser1.png"] },
		{ level: 3, key: "ingame-manual-equipment-combat-lasers-pulse" },
		{ level: 3, key: "ingame-manual-equipment-combat-lasers-beam" },
		{ level: 3, key: "ingame-manual-equipment-combat-lasers-mining" },
		{ level: 3, key: "ingame-manual-equipment-combat-lasers-military" },
		{ level: 2, key: "ingame-manual-equipment-combat-pylons", backgrounds: ["ingame-manual-pylons0.png"] },
		{ level: 3, key: "ingame-manual-equipment-combat-pylons-missile", backgrounds: ["ingame-manual-missile1.png"] },
		{ level: 3, key: "ingame-manual-equipment-combat-pylons-hardhead", opts: { model: "[ecm-proof-missile]" } },
		{ level: 3, key: "ingame-manual-equipment-combat-pylons-qcm", opts: { model: "[qbomb]" } },
		{ level: 2, key: "ingame-manual-equipment-combat-ecm" },
		{ level: 2, key: "ingame-manual-equipment-combat-mts", backgrounds: ["ingame-manual-pylons1.png"] },
		{ level: 2, key: "ingame-manual-equipment-combat-escape", opts: { model: "[escape-capsule]" } },
		{ level: 2, key: "ingame-manual-equipment-combat-eeu" },
		{ level: 2, key: "ingame-manual-equipment-combat-tsme" },
		{ level: 2, key: "ingame-manual-equipment-combat-inject", backgrounds: ["ingame-manual-inject.png"] },
		{ level: 2, key: "ingame-manual-equipment-combat-wormhole", backgrounds: ["ingame-manual-wormhole.png"] },
		{ level: 2, key: "ingame-manual-equipment-combat-ste", backgrounds: ["ingame-manual-ste.png"] },
		{ level: 2, key: "ingame-manual-equipment-combat-its" },
		{ level: 2, key: "ingame-manual-equipment-combat-shield" },
		{ level: 1, key: "ingame-manual-equipment-extra" },
		{ level: 2, key: "ingame-manual-equipment-extra-displays" },
		{ level: 0, key: "ingame-manual-sociology" },
		{ level: 1, key: "ingame-manual-sociology-galcop" },
		{ level: 2, key: "ingame-manual-sociology-galcop-facilities", backgrounds: ["ingame-manual-station.png"] },
		{ level: 2, key: "ingame-manual-sociology-galcop-legal", backgrounds: ["ingame-manual-police0.png", "ingame-manual-police1.png", "ingame-manual-police0.png"] },
		{ level: 2, key: "ingame-manual-sociology-galcop-combat", backgrounds: ["ingame-manual-combat0.png"] },
		{ level: 1, key: "ingame-manual-sociology-pilots" },
		{ level: 2, key: "ingame-manual-sociology-pilots-traders", backgrounds: ["ingame-manual-shiptrader.png"] },
		{ level: 2, key: "ingame-manual-sociology-pilots-hunters", backgrounds: ["ingame-manual-shiphunter.png"] },
		{ level: 2, key: "ingame-manual-sociology-pilots-pirates", backgrounds: ["ingame-manual-shippirate.png"] },
		{ level: 2, key: "ingame-manual-sociology-pilots-miners", backgrounds: ["ingame-manual-shipminer.png"] },
		{ level: 2, key: "ingame-manual-sociology-pilots-military" },
		{ level: 2, key: "ingame-manual-sociology-pilots-deep" },
		{ level: 2, key: "ingame-manual-sociology-pilots-salvage" },
		{ level: 1, key: "ingame-manual-sociology-govt" },
		{ level: 2, key: "ingame-manual-sociology-govt-corp" },
		{ level: 2, key: "ingame-manual-sociology-govt-demo" },
		{ level: 2, key: "ingame-manual-sociology-govt-conf" },
		{ level: 2, key: "ingame-manual-sociology-govt-comm" },
		{ level: 2, key: "ingame-manual-sociology-govt-dict" },
		{ level: 2, key: "ingame-manual-sociology-govt-mult" },
		{ level: 2, key: "ingame-manual-sociology-govt-feud" },
		{ level: 2, key: "ingame-manual-sociology-govt-anar" },
		{ level: 1, key: "ingame-manual-sociology-econ" },
		{ level: 2, key: "ingame-manual-sociology-econ-agri" },
		{ level: 2, key: "ingame-manual-sociology-econ-ind" },
		{ level: 1, key: "ingame-manual-sociology-species" },
		{ level: 1, key: "ingame-manual-sociology-thargoids" },
		{ level: 1, key: "ingame-manual-sociology-history" },
		{ level: 2, key: "ingame-manual-sociology-history-before" },
		{ level: 2, key: "ingame-manual-sociology-history-eight" },
		{ level: 2, key: "ingame-manual-sociology-history-interspecies" },
		{ level: 2, key: "ingame-manual-sociology-history-founding" },
		{ level: 2, key: "ingame-manual-sociology-history-thargoids" },
		{ level: 2, key: "ingame-manual-sociology-history-modern" },
		{ level: 0, key: "ingame-manual-ships" },
		{ level: 1, key: "ingame-manual-ships-adder", opts: { model: "[adder]" } },
		{ level: 1, key: "ingame-manual-ships-anaconda", opts: { model: "[anaconda]" } },
		{ level: 1, key: "ingame-manual-ships-asp", opts: { model: "[asp]" } },
		{ level: 1, key: "ingame-manual-ships-boa", opts: { model: "[boa]" } },
		{ level: 1, key: "ingame-manual-ships-boa2", opts: { model: "[boa-mk2]" } },
		{ level: 1, key: "ingame-manual-ships-cobra", opts: { model: "[cobramk1]" } },
		{ level: 1, key: "ingame-manual-ships-cobra3", opts: { model: "[cobra3-trader]" } },
		{ level: 1, key: "ingame-manual-ships-fdl", opts: { model: "[ferdelance]" } },
		{ level: 1, key: "ingame-manual-ships-gecko", opts: { model: "[gecko]" } },
		{ level: 1, key: "ingame-manual-ships-krait", opts: { model: "[krait]" } },
		{ level: 1, key: "ingame-manual-ships-mamba", opts: { model: "[mamba]" } },
		{ level: 1, key: "ingame-manual-ships-moray", opts: { model: "[moray]" } },
		{ level: 1, key: "ingame-manual-ships-shuttle", opts: { model: "[shuttle]" } },
		{ level: 1, key: "ingame-manual-ships-python", opts: { model: "[python]" } },
		{ level: 1, key: "ingame-manual-ships-sidewinder", opts: { model: "[sidewinder]" } },
		{ level: 1, key: "ingame-manual-ships-transporter", opts: { model: "[transporter-miner]" } },
		{ level: 1, key: "ingame-manual-ships-viper", opts: { model: "[viper]" } },
		{ level: 1, key: "ingame-manual-ships-interceptor", opts: { model: "[viper-interceptor]" } },
		{ level: 1, key: "ingame-manual-ships-worm", opts: { model: "[worm]" } },
		{ level: 1, key: "ingame-manual-ships-thargoid", opts: { model: "[thargoid]" } },
		{ level: 1, key: "ingame-manual-ships-tharglet", opts: { model: "[tharglet]" } },
		{ level: 0, key: "ingame-manual-end" } // temp entry
	];

	this._registerBook("ingame-manual", expandMissionText("ingame-manual-title"), contents);

}

this._setBook = function (key) {
	if (!this.$books[key]) {
		return;
	}
	this.$contents = this.$books[key].contents;
	if (!this.$fcb) {
		this.$fcb = addFrameCallback(this._precalculateOffsets.bind(this));
	}

	if (this.$bookmark[key]) {
		while (this.$bookmark[key].length < 4) {
			this.$bookmark[key].push(0);
		}
		this.$chapter = this.$bookmark[key][0];
		this.$offset = this.$bookmark[key][1];
		this.$mfdChapter = this.$bookmark[key][2];
		this.$mfdOffset = this.$bookmark[key][3];
	} else {
		this.$chapter = 0;
		this.$offset = 0;
		this.$mfdChapter = 0;
		this.$mfdOffset = 0;
		this.$bookmark[key] = [0, 0, 0, 0];
	}
	this.$currentBook = key;

	// reset MFD if active
	if (!player.ship.docked) {
		this.$mfdOn = false;
		this.$mfdMode = 0;
		player.consoleMessage("Ship's Library interface reset", 6);
		this._hideMFD();
	}

}

this._getBookListAlpha = function () {
	var keys = Object.keys(this.$books);
	keys.sort(function (a, b) {
		return this.$books[a].title.localeCompare(this.$books[b].title);
	}.bind(this));
	return keys;
}

/** Mission screen management **/

this._showLibrary = function () {
	this.$mode = "firstrun";
	// init and enter loop
	this._showManual();
}

this._showManual = function () {
	this.$hudHidden = player.ship.hudHidden;
	this.$manualActive = true;
	player.ship.hudHidden = true;
	if (this.$mode == "firstrun") {
		if (Object.keys(this.$books).length > 1) {
			this.$mode = "book";
			this.$offset = 0;
		} else {
			this.$mode = "read";
		}
	}
	if (this.$mode == "contents") {
		this._showChapterList();
	} else if (this.$mode == "read") {
		this._showPage();
	} else if (this.$mode == "book") {
		this._showBookList();
	}
}

this.guiScreenChanged = function (to, from) {
	if (from == "GUI_SCREEN_MISSION" && this.$manualActive) {
		this.$manualActive = false;
		player.ship.hudHidden = this.$hudHidden;
		if (this.$fcbM) {
			removeFrameCallback(this.$fcbM);
			delete this.$fcbM;
		}
	}
}

this._showBookList = function () {
	var blank = { unselectable: true, text: "" };
	var listchoices = new Object;
	if (this.$offset >= this.$chaptersperpage) {
		listchoices["51_BOOK_BACK"] = {
			text: expandMissionText("ships-library-contents-back"),
			alignment: "LEFT",
			color: "orangeColor"
		}
	} else {
		listchoices["51_BOOK_BACK"] = blank;
	}
	listchoices["53_SPACER"] = blank;
	var books = this._getBookListAlpha();
	var start = this.$offset - (this.$offset % this.$chaptersperpage);
	for (var i = start; i < start + this.$chaptersperpage; i++) {
		istr = String(i);
		if (i < 10) {
			istr = "0" + String(i);
		}
		if (books.length > i) {
			listchoices["55_BOOK_SELECT_" + istr + "_" + books[i]] = {
				text: this.$books[books[i]].title,
				alignment: "LEFT"
			}
		} else {
			listchoices["55_BOOK_SELECT_" + istr + "_"] = blank;
		}
	}
	listchoices["57_SPACER"] = blank;
	if (start + this.$chaptersperpage < books.length) {
		listchoices["59_BOOK_FORWARD"] = {
			text: expandMissionText("ships-library-contents-forward"),
			alignment: "RIGHT",
			color: "orangeColor"
		}
	} else {
		listchoices["59_CONTENTS_FORWARD"] = blank;
	}
	for (var i = 24; i < 26; i++) {
		listchoices["98_SPACER" + i] = blank;
	}
	//		listchoices["50_BOOK"] = expandMissionText("ships-library-changebook");
	listchoices["99_EXIT"] = expandMissionText("ships-library-exit");

	mission.runScreen({
		screenID: "ships-library",
		titleKey: "ships-library-bookselect",
		allowInterrupt: true,
		exitScreen: "GUI_SCREEN_INTERFACES",
		choices: listchoices,
		initialChoicesKey: this.$lastchoice ? this.$lastchoice : "55_CONTENTS_SELECT_" + this.$offset
	}, this._manualHandler, this);

}

this._showChapterList = function () {
	// 0 = back in list (?)
	// 2-11 = chapter headings (read chapter)
	// 13 = forward in list (?)
	// 20 = exit
	var blank = { unselectable: true, text: "" };
	var listchoices = new Object;
	if (this.$chapter >= this.$chaptersperpage) {
		listchoices["11_CONTENTS_BACK"] = {
			text: expandMissionText("ships-library-contents-back"),
			alignment: "LEFT",
			color: "orangeColor"
		}
	} else {
		listchoices["11_CONTENTS_BACK"] = blank;
	}
	listchoices["13_SPACER"] = blank;
	var start = this.$chapter - (this.$chapter % this.$chaptersperpage);
	for (var i = start; i < start + this.$chaptersperpage; i++) {
		istr = String(i);
		if (i < 10) {
			istr = "0" + String(i);
		}
		if (this.$contents.length > i) {
			var indent = "";
			if (this.$contents[i].level > 0) {
				indent += Array(this.$contents[i].level + 1).join("... ");
			}
			listchoices["15_CONTENTS_SELECT_" + istr] = {
				text: indent + expandMissionText(this.$contents[i].key + "-title"),
				alignment: "LEFT"
			}
		} else {
			listchoices["15_CONTENTS_SELECT_" + istr] = blank;
		}
	}
	listchoices["17_SPACER"] = blank;
	if (start + this.$chaptersperpage < this.$contents.length) {
		listchoices["19_CONTENTS_FORWARD"] = {
			text: expandMissionText("ships-library-contents-forward"),
			alignment: "RIGHT",
			color: "orangeColor"
		}
	} else {
		listchoices["19_CONTENTS_FORWARD"] = blank;
	}
	for (var i = 24; i < 25; i++) {
		listchoices["48_SPACER" + i] = blank;
	}
	listchoices["50_BOOK"] = expandMissionText("ships-library-changebook");
	listchoices["99_EXIT"] = expandMissionText("ships-library-exit");

	mission.runScreen({
		screenID: "ships-library",
		titleKey: "ships-library-chapterselect",
		allowInterrupt: true,
		exitScreen: "GUI_SCREEN_INTERFACES",
		choices: listchoices,
		initialChoicesKey: this.$lastchoice ? this.$lastchoice : "15_CONTENTS_SELECT_" + this.$chapter
	}, this._manualHandler, this);

}


this._showPage = function () {
	var chapter = this.$contents[this.$chapter];

	var text = this._textFromOffset(this.$chapter, this.$offset, this.$msStore, this.$msRows, this.$msCols);

	var opts = {
		screenID: "ships-library",
		titleKey: chapter.key + "-title",
		allowInterrupt: true,
		exitScreen: "GUI_SCREEN_INTERFACES",
		choices: {
			"01_PREV": expandMissionText("ships-library-page-back"),
			"09_NEXT": expandMissionText("ships-library-page-next"),
			"10_CONTENTS": expandMissionText("ships-library-contents"),
			"99_EXIT": expandMissionText("ships-library-exit"),
		},
		initialChoicesKey: this.$lastchoice ? this.$lastchoice : "09_NEXT",
		message: text
	};
	if (chapter.opts) {
		var extras = Object.keys(chapter.opts)
		for (var k = 0; k < extras.length; k++) {
			opts[extras[k]] = chapter.opts[extras[k]];
		}
	}
	if (chapter.backgrounds) {
		var page = this._pageOfOffset(chapter, this.$offset, this.$msStore);
		if (page < chapter.backgrounds.length) {
			var backg = chapter.backgrounds[page];
		} else {
			var backg = chapter.backgrounds[chapter.backgrounds.length - 1];
		}
		if (backg != "") {
			opts.overlay = backg;
		}
	}

	mission.runScreen(opts, this._manualHandler, this);
	this.$fcbM = addFrameCallback(this._moveShip.bind(this));

}

this._moveShip = function (delta) {
	if (mission.displayModel) {
		if (mission.displayModel.position.y >= -1) {
			mission.displayModel.position = new Vector3D(0, -mission.displayModel.position.z / 2.5, mission.displayModel.position.z * 2.5);
		}
	}
}

this._setBookmark = function () {
	this.$bookmark[this.$currentBook][0] = this.$chapter;
	this.$bookmark[this.$currentBook][1] = this.$offset;
	this.$bookmark[this.$currentBook][2] = this.$mfdChapter;
	this.$bookmark[this.$currentBook][3] = this.$mfdOffset;
}

this._manualHandler = function (choice) {
	//		log(this.name,choice);
	delete this.$lastchoice;
	if (!choice) {
		return; // launched while reading?
	} else if (choice == "01_PREV") {
		if (this.$offset == 0) {
			if (this.$chapter == 0) {
				this.$mode = "contents";
			} else {
				this.$mode = "read";
				this.$chapter--;
				this.$offset = this._lastPageOffset(this.$chapter, this.$msStore);
				this._setBookmark();
			}
		} else {
			this.$mode = "read";
			this.$offset = this._previousOffset(this.$chapter, this.$offset, this.$msStore, this.$msRows, this.$msCols);
			this._setBookmark();
		}
		this.$lastchoice = choice;
	} else if (choice == "05_READ") {
		this.$mode = "read";
	} else if (choice == "09_NEXT") {
		this.$offset = this._nextOffset(this.$chapter, this.$offset, this.$msStore, this.$msRows, this.$msCols);
		if (this.$offset == -1) {
			this.$offset = 0;
			this.$chapter++;
			if (this.$chapter >= this.$contents.length) {
				this.$mode = "contents";
				this.$chapter = 0;
			} else {
				this._setBookmark();
				this.$mode = "read";
			}
		} else {
			this._setBookmark();
		}
	} else if (choice == "10_CONTENTS") {
		this.$mode = "contents";
		this.$offset = 0;
	} else if (choice == "11_CONTENTS_BACK") {
		this.$mode = "contents";
		this.$offset = 0;
		this.$chapter -= 1 + (this.$chapter % this.$chaptersperpage);
		this.$lastchoice = choice;
	} else if (choice.match(/^15_CONTENTS_SELECT_/)) {
		this.$chapter = parseInt(choice.slice(19), 10);
		this.$offset = 0;
		this._setBookmark();
		this.$mode = "read";
	} else if (choice == "19_CONTENTS_FORWARD") {
		this.$mode = "contents";
		this.$offset = 0;
		this.$chapter += this.$chaptersperpage - (this.$chapter % this.$chaptersperpage);
		this.$lastchoice = choice;
	} else if (choice == "50_BOOK") {
		this.$mode = "book";
		this.$offset = 0;
	} else if (choice == "51_BOOK_BACK") {
		this.$offset -= this.$chaptersperpage;
	} else if (choice == "59_BOOK_FORWARD") {
		this.$offset += this.$chaptersperpage;
	} else if (choice.match(/^55_BOOK_SELECT_/)) {
		this._setBook(choice.slice(18));
		this.$mode = "read";
	}

	// 01_PREV: reduce offset to prev page, or last page of prev chapter
	// 05_READ: switch to read mode at current chapter + offset
	// 09_NEXT: advance offset to next page, or next chapter if no more
	// 10_CONTENTS: switch to contents mode at current chapter, zero offset
	// 11_CONTENTS_BACK: up one page in chapter list
	// 15_CONTENTS_SELECT_[0-9]: switch to read mode in chapter at offset
	// 19_CONTENTS_FORWARD: down one page in chapter list
	// 99_EXIT: quit


	if (choice != "99_EXIT") {
		this._showManual();
	}
}

/** MFD functions **/

this._equipPressMode = function () {
	this.$mfdMode = (this.$mfdMode + 1) % 7;
	player.consoleMessage(expandDescription("[ships-library-mfd-mode][ships-library-mfd-mode" + this.$mfdMode + "]"));
}

this._equipPressActivate = function () {
	if (player.ship.multiFunctionDisplays == 0) {
		player.consoleMessage(expandDescription("[ships-library-mfd-unavailable]"));
		return;
	}
	if (this.$mfdMode == 0) // power
	{
		this.$mfdOn = !this.$mfdOn;
		if (this.$mfdOn) {
			// out of range value picks first free MFD, if there is one
			player.ship.setMultiFunctionDisplay(player.ship.multiFunctionDisplays, "ships-library")
			player.consoleMessage(expandDescription("[ships-library-mfd-on]"));
		}
	}
	else if (this.$mfdMode == 1) // next page
	{
		this.$mfdOffset = this._nextOffset(this.$mfdChapter, this.$mfdOffset, this.$mfdStore, this.$mfdRows, this.$mfdCols);
		if (this.$mfdOffset == -1) {
			this.$mfdOffset = 0;
			this.$mfdChapter++;
			if (this.$mfdChapter >= this.$contents.length) {
				this.$mfdChapter = 0;
			} else {
				this._setBookmark();
			}
		} else {
			this._setBookmark();
		}
	}
	else if (this.$mfdMode == 2) // prev page
	{
		if (this.$mfdOffset == 0) {
			if (this.$mfdChapter == 0) {
				this.$mfdChapter = this.$contents.length - 1;
			} else {
				this.$mfdChapter--;
				this.$mfdOffset = this._lastPageOffset(this.$mfdChapter, this.$mfdStore);
				this._setBookmark();
			}
		} else {
			this.$mfdOffset = this._previousOffset(this.$mfdChapter, this.$mfdOffset, this.$mfdStore, this.$mfdRows, this.$mfdCols);
			this._setBookmark();
		}
	}
	else if (this.$mfdMode == 3) // next chapter
	{
		this.$mfdOffset = 0;
		this.$mfdChapter++;
		if (this.$mfdChapter >= this.$contents.length) {
			this.$mfdChapter = 0;
		}
	}
	else if (this.$mfdMode == 4) // previous chapter
	{
		this.$mfdOffset = 0;
		this.$mfdChapter--;
		if (this.$mfdChapter < 0) {
			this.$mfdChapter = this.$contents.length - 1;
		}
	}
	else if (this.$mfdMode == 5) // next book
	{
		var booklist = Object.keys(this.$books);
		var curIndex = booklist.indexOf(this.$currentBook);
		var newIndex = curIndex + 1;
		if (curIndex == booklist.length - 1) newIndex = 0;
		this._setBook(booklist[newIndex]);
		player.consoleMessage("Book selected: " + this.$books[booklist[newIndex]].title, 6);
	}
	else if (this.$mfdMode == 6) // previous book
	{
		var booklist = Object.keys(this.$books);
		var curIndex = booklist.indexOf(this.$currentBook);
		var newIndex = curIndex - 1;
		if (curIndex == 0) newIndex = booklist.length - 1;
		this._setBook(booklist[newIndex]);
		player.consoleMessage("Book selected: " + this.$books[booklist[newIndex]].title, 6);
	}

	if (this.$mfdOn) {
		this._showMFD();
	}
	else {
		this._hideMFD();
	}
}

this._showMFD = function () {
	var text = this._textFromOffset(this.$mfdChapter, this.$mfdOffset, this.$mfdStore, this.$mfdRows, this.$mfdCols);
	text = this._mfdBreak(text);
	player.ship.setMultiFunctionText("ships-library", text);
}

this._hideMFD = function () {
	player.ship.setMultiFunctionText("ships-library");
}

this._mfdBreak = function (text) {
	if (text == String.fromCharCode(30)) {
		return expandDescription("[ingame-library-imageunavailable]");
	}
	var linewidth = this.$mfdCols;
	var lines = [];
	var paras = text.split("\n");

	var pl = paras.length;
	for (var i = 0; i < pl; i++) {
		var accum = "";
		var words = paras[i].split(" ");
		var widthuse = 0;
		var wl = words.length;
		for (var j = 0; j < wl; j++) {
			var word = (accum == "" ? "" : " ") + words[j];
			var wordwidth = defaultFont.measureString(word);
			if (widthuse + wordwidth < linewidth) {
				accum += word;
				widthuse += wordwidth;
			} else {
				lines.push(accum);
				accum = word.slice(1);
				widthuse = wordwidth - this.$spaceWidth;
			}
		}
		lines.push(accum);
	}
	return lines.join("\n");
}


/** Utility functions **/

this._blockFromOffset = function (chapternum, offset, lines, linewidth) {
	//		log(this.name,"Calculating block: "+key+","+offset);
	var pagebreak = String.fromCharCode(30);
	//	var lines = this.$rows;
	//	var linewidth = this.$cols;

	var fulltext = this._getText(chapternum);

	var text = fulltext.slice(offset, offset + (lines * linewidth * 4));
	var paras = text.split("\n");
	var accum = "";
	var lineuse = 0;
	var pl = paras.length;
	for (var i = 0; i < pl; i++) {
		var words = paras[i].split(" ");
		var widthuse = 0;
		var wl = words.length;
		for (var j = 0; j < wl; j++) {
			if (words[j] == pagebreak) {
				lineuse = lines;
				accum += pagebreak + "\n";
				break; // page break character
			}
			var word = (j == 0 ? "" : " ") + words[j];
			var wordwidth = defaultFont.measureString(word);
			if (widthuse + wordwidth < linewidth) {
				accum += word;
				widthuse += wordwidth;
			} else {
				widthuse = 0;
				lineuse++;
				if (lineuse >= lines) {
					accum += "\n";
					break;
				} else {
					accum += word;
					if (j == 0) {
						widthuse += wordwidth;
					}
					else {
						widthuse += wordwidth - this.$spaceWidth;
					}
				}
			}
		}
		if (lineuse >= lines) {
			break;
		}
		lineuse++;
		accum += "\n";
		if (lineuse >= lines) {
			break;
		}
	}

	if (lineuse < this.$minblock && lines == this.$msRows) {
		this.$short = true;
	} else {
		this.$short = false;
	}
	//		log(this.name,"Calculated block: "+key+","+offset);
	return accum;
}

// offset calculation can be fairly slow for large text blocks
// this calculates it in the background a page at a time
// while the player is docked and frame rate is less crucial
this._precalculateOffsets = function () {
	if (!player.ship || !player.ship.docked) {
		return;
	}
	for (var i = 0; i < this.$contents.length; i++) {
		if (this._precalculateOffset(i)) {
			return;
		}
	}
	removeFrameCallback(this.$fcb);
	delete this.$fcb;
}

this._precalculateOffset = function (chapternum) {
	var chapter = this.$contents[chapternum];
	if (chapter.offsets && chapter.miniOffsets) {
		return false;
	}
	if (!chapter._offsets) {
		chapter._offsets = [0];
	}
	if (!chapter._miniOffsets) {
		chapter._miniOffsets = [0];
	}

	var fulltext = this._getText(chapternum);
	var offset = chapter._offsets[chapter._offsets.length - 1];
	var block = this._blockFromOffset(chapternum, offset, this.$msRows, this.$msCols);
	offset += block.length;
	if (offset < fulltext.length) {
		// add next block
		chapter._offsets.push(offset);
	} else {
		// done
		if (this.$short && chapter._offsets.length > 1) {
			chapter._offsets.pop(); // merge last block into previous
		}
		chapter._offsets.push(-1);
		chapter.offsets = chapter._offsets;
		delete chapter._offsets;
	}

	offset = chapter._miniOffsets[chapter._miniOffsets.length - 1];
	block = this._blockFromOffset(chapternum, offset, this.$mfdRows, this.$mfdCols);
	offset += block.length;
	if (offset < fulltext.length) {
		// add next block
		chapter._miniOffsets.push(offset);
	} else {
		// done
		if (this.$short && chapter._miniOffsets.length > 1) {
			chapter._miniOffsets.pop(); // merge last block into previous
		}
		chapter._miniOffsets.push(-1);
		chapter.miniOffsets = chapter._miniOffsets;
		delete chapter._miniOffsets;
	}


	return true;
}


this._calculateOffsets = function (chapternum, rows, cols) {
	var offsets = new Array;
	var fulltext = this._getText(chapternum);

	var offset = 0;
	this.$short = false;
	while (offset < fulltext.length && !this.$short) {
		offsets.push(offset);
		var block = this._blockFromOffset(chapternum, offset, rows, cols);
		offset += block.length;
	}
	if (this.$short && offsets.length > 1) {
		offsets.pop(); // merge last block into previous
	}

	offsets.push(-1);

	return offsets;
}


this._textFromOffset = function (chapternum, fromoffset, otype, rows, cols) {
	var fulltext = this._getText(chapternum);

	var tooffset = this._nextOffset(chapternum, fromoffset, otype, rows, cols);
	if (tooffset == -1) {
		var slice = fulltext.slice(fromoffset);
	} else {
		var slice = fulltext.slice(fromoffset, tooffset);
	}
	return slice.trim();
}

this._getText = function (chapternum) {
	var chapter = this.$contents[chapternum];
	var params = new Object;
	params["ships-library-page-break"] = String.fromCharCode(30) + "\n";
	if (!chapter.params) {
		return expandMissionText(chapter.key + "-text", params);
	} else {
		for (var i = 0; i < chapter.params.length; i++) {
			params["ships_library_param_" + i] = chapter.params[i]();
		}
		return expandMissionText(chapter.key + "-text", params);
	}
}

this._pageOfOffset = function (chapter, offset, otype) {
	// only call this function when chapter.offsets must have been
	// calculated already

	for (var i = 0; i < chapter[otype].length; i++) {
		if (offset <= chapter[otype][i]) {
			return i;
		}
	}
	return chapter[otype].length;
}

this._nextOffset = function (chapternum, fromoffset, otype, rows, cols) {
	var chapter = this.$contents[chapternum];
	if (!chapter[otype]) {
		chapter[otype] = this._calculateOffsets(chapternum, rows, cols);
		delete chapter._offsets;
	}

	for (var i = 0; i < chapter[otype].length; i++) {
		if (fromoffset <= chapter[otype][i]) {
			if (i == chapter[otype].length - 1) {
				return -1;
			}
			return chapter[otype][i + 1];
		}
	}
	return -1;
}

this._previousOffset = function (chapternum, fromoffset, otype, rows, cols) {
	var offcount = 0;
	do {
		var next = this._nextOffset(chapternum, offcount, otype, rows, cols);
		if (next < fromoffset) {
			offcount = next;
		}
	} while (next < fromoffset);
	return offcount;
}

this._lastPageOffset = function (chapternum, otype, rows, cols) {
	var offcount = 0;
	do {
		var next = this._nextOffset(chapternum, offcount, otype, rows, cols);
		if (next != -1) {
			offcount = next;
		}
	} while (next != -1);
	return offcount;
}

this._getTip = function () {
	var numtips = 12;


	var tipn = (clock.days - 2084004) % numtips;
	if (tipn <= 9) {
		var tips = "00" + tipn.toString();
	} else if (tipn <= 99) {
		var tips = "0" + tipn.toString();
	} else {
		var tips = tipn.toString();
	}
	return expandMissionText("ingame-manual-tip-" + tips);
}