"use strict";
this.name = "GalCopBB_StationDisrupt";
this.author = "phkb";
this.copyright = "2017 phkb";
this.description = "Control of the F4 Interface and various functions for disrupting stations (mission 63,64,65)";
this.license = "CC BY-NC-SA 4.0";

this._debug = false;
this._disruptInstalled = []; // list of systems where the disruption software has been installed
this._typeToInstall = 0; // type of software being installed either 63 or 64
this._checkTimer = null; // timer started when player installs software to see if they are caught by GalCop
this._redockCatch = false; // flag to indicate player redocked within 24 hours of software installation
this._distCheckTimer = null; // timer to inform player when they've reached a suitable distance from main station
this._policeSelfDestructTimer = null; // timer to control the self-destruction of the police vessel
this._policeShip = null; // ship object of police vessel that will self-destruct
this._dataStreamTimer = null; // timer to control sending data stream to main station
this._dataStreamCounter = 0; // current position of the data stream
this._playerIsDocking = false; // flag to indicate when the player has requested docking clearance
this._mission6364MissionID = 0; // mission ID link for a 63/64 type mission
this._currDist = 0; // player's current distance from the main station
this._miss65EjectDist = 50000; // distance player must be from main station before ejecting
this._checkDistMessage = false; // flag to indicate the suitable distance message has been sent to the player
this._activeMissionID = 0; // mission ID link for the software that has just been installed at a station
this._cancelTimer = null; // timer to control when the window of opportunity for installing the software expires
this._abortTime = 0; // time when window of opportunity for software installation closes
this._simulator = false;

//	Must initiate software upload within 30 seconds of finishing data stream

//-------------------------------------------------------------------------------------------------------------
this.startUp = function () {
	if (missionVariables.GalCopBBMissions_StationDisruption) {
		this._disruptInstalled = JSON.parse(missionVariables.GalCopBBMissions_StationDisruption);
		delete missionVariables.GalCopBBMissions_StationDisruption;
	}
	var gcm = worldScripts.GalCopBB_Missions;
	// add these mission types into the main control
	var list = [63, 64, 65];
	gcm._availableMissionTypes = gcm._availableMissionTypes.concat(list);
	gcm._multiStageMissionTypes = gcm._multiStageMissionTypes.concat(list);
	this._debug = gcm._debug;
}

//-------------------------------------------------------------------------------------------------------------
this.startUpComplete = function () {
	this.$initInterface(player.ship.dockedStation);
	if (worldScripts.BountySystem_Core) {
		// add the specialised bounty item
		var bs = worldScripts.BountySystem_Core;
		bs._offenceTypes["galcop station interference"] = {
			description: expandDescription("[gcm_offence_station_interference]"),
			severity: 3
		};
	}
	this.$initInterface(player.ship.dockedStation);
}

//-------------------------------------------------------------------------------------------------------------
this.playerWillSaveGame = function () {
	if (this._disruptInstalled.length > 0) {
		missionVariables.GalCopBBMissions_StationDisruption = JSON.stringify(this._disruptInstalled);
	} else {
		delete missionVariables.GalCopBBMissions_StationDisruption;
	}
}

//-------------------------------------------------------------------------------------------------------------
this.systemWillPopulate = function () {
	// this system is disrupted...
	// work out the type of disruption and apply 
	if (this.$disruptCommodities(system.ID) === true) this.$adjustCommodityPrices();

	var gcm = worldScripts.GalCopBB_Missions;
	var list = gcm.$getListOfMissions(true, [63, 64]);
	for (var i = 0; i < list.length; i++) {
		if (list[i].destination === system.ID &&
			list[i].data.quantity === 0 &&
			list[i].data.destroyedQuantity === 0 &&
			list[i].expiry > clock.adjustedSeconds) {
			this._mission6364MissionID = list[i].ID;
			this.$createDataStreamOption();
		}
	}
}

//-------------------------------------------------------------------------------------------------------------
this.guiScreenChanged = function (to, from) {
	if (guiScreen === "GUI_SCREEN_EQUIP_SHIP" && player.ship.docked && player.ship.dockedStation.isMainStation && this.$disruptEquipment(system.ID) === true) {
		mission.runScreen({
			screenID: "oolite-gcm-disabled-summary",
			title: expandDescription("[gcm_services_unavailable]"),
			message: expandDescription("[gcm_disrupt_shipyard]"),
			overlay: {
				name: "gcm-bandaid.png",
				height: 546
			}
		});
	}
}

//-------------------------------------------------------------------------------------------------------------
this.shipDockedWithStation = function (station) {
	if (this._simulator === true) return;

	this._playerIsDocking = false;
	this._dataStreamCounter = 0;
	this.$stopTimers();
	this.$initInterface(station);

	// check if we're docking inside of 48hours of disruption
	if (station.isMainStation) {
		for (var i = 0; i < this._disruptInstalled.length; i++) {
			if (this._disruptInstalled[i].system === system.ID && (this._disruptInstalled[i].installTime + (86400 * 2)) > clock.adjustedSeconds && this._disruptInstalled[i].fined === false) {
				// oops! redocked within 48 hours
				this._activeMissionID = this._disruptInstalled[i].originalMissionID;
				this._redockCatch = true;
			}
		}
	}

	// check for a mission type 65 at stage 0
	var bb = worldScripts.BulletinBoardSystem;
	var gcm = worldScripts.GalCopBB_Missions;
	var list = gcm.$getListOfMissions(true, 65);
	for (var i = 0; i < list.length; i++) {
		if (list[i].destination === system.ID &&
			list[i].expiry > clock.adjustedSeconds &&
			list[i].data.stage === 0 &&
			list[i].data.destroyedQuantity === 0 &&
			station.allegiance !== "galcop") {

			var msg = expandDescription("[missionType65_arrivalReport_pickup]");
			// does the player already have an escape pod?
			if (player.ship.equipmentStatus("EQ_ESCAPE_POD") === "EQUIPMENT_OK") {
				player.ship.removeEquipment("EQ_ESCAPE_POD");
				msg = msg.replace("*existing*", ", removing your existing escape pod and ");
			} else {
				msg = msg.replace("*existing*", "");
			}
			player.ship.awardEquipment("EQ_GCM_ESCAPE_POD");
			list[i].data.stage += 1;
			list[i].data.quantity += 1;
			bb.$updateBBMissionPercentage(list[i].ID, list[i].data.quantity / list[i].data.targetQuantity);

			bb.$revertChartMarker(list[i].ID);

			gcm.$logMissionData(list[i].ID);
			player.consoleMessage(expandDescription("[goal_updated]"));

			player.addMessageToArrivalReport(msg);
			break;
		}

		if (list[i].destination === system.ID &&
			list[i].data.stage === 2 &&
			list[i].data.destroyedQuantity === 0) {

			// reset the expiry (because the escape pod rescue time is completely variable)
			list[i].expiry = clock.adjustedSeconds + (3600);
			gcm.$updateManifestEntry(list[i].ID);
		}
	}
}

//-------------------------------------------------------------------------------------------------------------
this.shipLaunchedFromStation = function (station) {
	var gcm = worldScripts.GalCopBB_Missions;
	this._simulator = false;
	if (gcm.$simulatorRunning() === true) {
		this._simulator = true;
		return;
	}

	var list = gcm.$getListOfMissions(true, [63, 64, 65]);
	for (var i = 0; i < list.length; i++) {
		if (list[i].data.missionType === 65 && list[i].source === system.ID && station.isMainStation) {

			if (list[i].data.stage === 1 && list[i].data.destroyedQuantity === 0) {
				this._distCheckTimer = new Timer(this, this.$distCheck, 5, 5);
				break;
			}
			if (list[i].data.stage === 2 && list[i].data.destroyedQuantity === 0) {
				// monkey-patch the station
				if (station.script.stationLaunchedShip && station.script.stationLaunchedShip != this.$gcm_stationLaunchedShip) {
					station.script.$gcm_hold_stationLaunchedShip = station.script.stationLaunchedShip;
				}
				station.script.stationLaunchedShip = this.$gcm_stationLaunchedShip;
				break;
			}
		}
		if ((list[i].data.missionType === 63 || list[i].data.missionType === 64) &&
			list[i].destination === system.ID &&
			list[i].data.stage === 0 &&
			list[i].data.destroyedQuantity === 0 &&
			list[i].expiry > clock.adjustedSeconds) {
			this._mission6364MissionID = list[i].ID;
			this.$createDataStreamOption();
		}
	}
}

//-------------------------------------------------------------------------------------------------------------
this.shipExitedWitchspace = function () {
	var gcm = worldScripts.GalCopBB_Missions;
	var list = gcm.$getListOfMissions(true, 65);
	for (var i = 0; i < list.length; i++) {
		if (list[i].source === system.ID) {
			if (list[i].data.stage === 1 && list[i].data.destroyedQuantity === 0) {
				this._distCheckTimer = new Timer(this, this.$distCheck, 5, 5);
				break;
			}
		}
	}
}

//-------------------------------------------------------------------------------------------------------------
this.shipWillEnterWitchspace = function () {
	this.$stopTimers();
	if (this._cancelTimer && this._cancelTimer.isRunning) {
		this._cancelTimer.stop();
		this._cancelTimer = null;
	}
}

//-------------------------------------------------------------------------------------------------------------
this.shipDied = function (whom, why) {
	this.$stopTimers();
	if (this._cancelTimer && this._cancelTimer.isRunning) {
		this._cancelTimer.stop();
		this._cancelTimer = null;
	}
}

//-------------------------------------------------------------------------------------------------------------
this.dayChanged = function (newday) {
	for (var i = this._disruptInstalled.length - 1; i >= 0; i--) {
		// has 1 month passed - if so, remove the disruption
		if ((this._disruptInstalled[i].installTime + (30 * 86400)) < clock.adjustedSeconds) this._disruptInstalled.splice(i, 1);
	}
}

//-------------------------------------------------------------------------------------------------------------
this.missionScreenOpportunity = function () {
	if (this._redockCatch === true) {
		this._redockCatch = false;
		this.$checkForCapture();
	}
}

//-------------------------------------------------------------------------------------------------------------
this.shipLaunchedEscapePod = function (escapepod) {
	var gcm = worldScripts.GalCopBB_Missions;
	var bb = worldScripts.BulletinBoardSystem;
	var list = gcm.$getListOfMissions(true, 65);
	for (var i = 0; i < list.length; i++) {
		if (list[i].source === system.ID &&
			list[i].data.stage === 1 &&
			list[i].data.destroyedQuantity === 0) {

			if (this._currDist > this._miss65EjectDist) {
				// reset the expiry (because the escape pod rescue time is completely variable)
				// we'll reset it again when we hit the shipWilldockwithstation routine, but for now just put it enough into the future to avoid failing it.
				list[i].expiry = clock.adjustedSeconds + (86400 * 20);
				list[i].data.stage += 1;
				list[i].data.quantity += 1;
				bb.$updateBBMissionPercentage(list[i].ID, list[i].data.quantity / list[i].data.targetQuantity);

				gcm.$logMissionData(list[i].ID);
				player.consoleMessage(expandDescription("[goal_updated]"));
			} else {
				list[i].destroyedQuantity = 1;
			}
			break;
		}
	}
}

//-------------------------------------------------------------------------------------------------------------
this.playerRequestedDockingClearance = function (message) {
	if (message === "DOCKING_CLEARANCE_GRANTED" || message === "DOCKING_CLEARANCE_EXTENDED") {
		this._playerIsDocking = true;
	} else {
		this._playerIsDocking = false;
	}
}

//-------------------------------------------------------------------------------------------------------------
this.playerDockingRefused = function () {
	this._playerIsDocking = false;
}

//-------------------------------------------------------------------------------------------------------------
// this works in v1.85/6 only
this.playerDockingClearanceGranted = function () {
	this._playerIsDocking = true;
}

//-------------------------------------------------------------------------------------------------------------
this.playerDockingClearanceExpired = function () {
	this._playerIsDocking = false;
}

//-------------------------------------------------------------------------------------------------------------
this.$stopTimers = function $stopTimers() {
	if (this._checkTimer && this._checkTimer.isRunning) {
		this._checkTimer.stop();
		this._checkTimer = null;
	}
	if (this._distCheckTimer && this._distCheckTimer.isRunning) {
		this._distCheckTimer.stop();
		this._distCheckTimer = null;
	}
	if (this._dataStreamTimer && this._dataStreamTimer.isRunning) {
		this._dataStreamTimer.stop();
		this._dataStreamTimer = null;
	}
}

//-------------------------------------------------------------------------------------------------------------
this.$gcm_stationLaunchedShip = function $gcm_stationLaunchedShip(whom) {
	if (this.ship.script.$gcm_hold_stationLaunchedShip) this.ship.script.$gcm_hold_stationLaunchedShip(whom);

	if (whom.isPolice) {
		// ok, got one!
		var gcmsd = worldScripts.GalCopBB_StationDisrupt;
		whom.awardEquipment("EQ_ESCAPE_POD");
		gcmsd._policeShip = whom;
		gcmsd.$startSelfDestructTimer();

		// un-monkey-patch station
		if (this.ship.script.$gcm_hold_stationLaunchedShip) {
			this.ship.script.stationLaunchedShip = this.ship.script.$gcm_hold_stationLaunchedShip;
			delete this.ship.script.$gcm_hold_stationLaunchedShip;
		} else {
			delete this.ship.script.stationLaunchedShip;
		}
	}
}

//-------------------------------------------------------------------------------------------------------------
this.$startSelfDestructTimer = function $startSelfDestructTimer() {
	if (this._policeShip) {
		// attach some failsafe scripts
		if (this._policeShip.script.shipDied && this._policeShip.script.shipDied != this.$gcm_police_shipDied) {
			this._policeShip.script.$gcm_hold_police_shipDied = this._policeShip.script.shipDied;
		}
		this._policeShip.script.shipDied = this.$gcm_police_shipDied;

		if (this._policeShip.script.shipDockedWithStation && this._policeShip.script.shipDockedWithStation != this.$gcm_police_shipDockedWithStation) {
			this._policeShip.script.$gcm_hold_police_shipDockedWithStation = this._policeShip.script.shipDockedWithStation;
		}
		this._policeShip.script.shipDockedWithStation = this.$gcm_police_shipDockedWithStation;
	}
	this._policeSelfDestructTimer = new Timer(this, this.$policeSelfDestruct, parseInt(Math.random() * 240 + 180), 0);
}

//-------------------------------------------------------------------------------------------------------------
this.$policeSelfDestruct = function $policeSelfDestruct() {
	if (this._policeShip.crew && this._policeShip.crew[0].name === "") {
		this._policeShip.crew[0].name = randomName() + " " + randomName();
	}
	this._policeShip.script.shipLaunchedEscapePod = this.$gcm_police_shipLaunchedEscapePod;
	this._policeShip.abandonShip();
	this._policeShip.explode();
}

//-------------------------------------------------------------------------------------------------------------
this.$gcm_police_shipDied = function $gcm_police_shipDied(whom, why) {
	if (this.ship.script.$gcm_hold_police_shipDied) this.ship.script.$gcm_hold_police_shipDied(whom, why);
	worldScripts.GalCopBB_StationDisrupt.$createCargo(this.ship);
}

//-------------------------------------------------------------------------------------------------------------
this.$gcm_police_shipLaunchedEscapePod = function $gcm_police_shipLaunchedEscapePod(pod, passengers) {
	pod.script._gcm_asked_already = true;
}

//-------------------------------------------------------------------------------------------------------------
this.$gcm_police_shipDockedWithStation = function $gcm_police_shipDockedWithStation(station) {
	if (this.ship.script.$gcm_hold_police_shipDockedWithStation) this.ship.script.$gcm_hold_police_shipDockedWithStation(station);
	//log(this.name, "police ship docked before self destruct");
	// if the police ship docks before the self destruct fires, reset the station monkey patching so we can try again
	// redo the monkey patch on the station
	if (station.script.stationLaunchedShip && station.script.stationLaunchedShip != worldScripts.GalCopBB_StationDisrupt.$gcm_stationLaunchedShip) {
		station.script.$gcm_hold_stationLaunchedShip = station.script.stationLaunchedShip;
	}
	station.script.stationLaunchedShip = worldScripts.GalCopBB_StationDisrupt.$gcm_stationLaunchedShip;
}

//-------------------------------------------------------------------------------------------------------------
this.$gcm_softwarecargo_shipWasScooped = function $gcm_softwarecargo_shipWasScooped(scooper) {
	if (scooper.isPlayer) {
		// complete mission
		player.ship.awardEquipment("EQ_GCM_SOFTWARE");
		var bb = worldScripts.BulletinBoardSystem;
		var gcm = worldScripts.GalCopBB_Missions;
		var list = gcm.$getListOfMissions(true, 65);
		for (var i = 0; i < list.length; i++) {
			if (list[i].source === system.ID &&
				list[i].data.stage === 2 &&
				list[i].data.destroyedQuantity === 0) {

				list[i].data.stage += 1;
				list[i].data.quantity += 1;
				bb.$updateBBMissionPercentage(list[i].ID, list[i].data.quantity / list[i].data.targetQuantity);

				gcm.$logMissionData(list[i].ID);
				player.consoleMessage(expandDescription("[goal_updated]"));
				break;
			}
		}
	}
}

//-------------------------------------------------------------------------------------------------------------
this.$createCargo = function $createCargo(ship) {
	var sw = ship.spawnOne("gcm_software_package");
	sw.script.shipWasScooped = this.$gcm_softwarecargo_shipWasScooped;
}

//-------------------------------------------------------------------------------------------------------------
this.$distCheck = function $distCheck() {
	if (player.ship && player.ship.isValid && player.ship.isInSpace) {
		this._currDist = player.ship.position.distanceTo(system.mainStation) / 1000;
	}
	if (this._currDist > this._miss65EjectDist && this._checkDistMessage === false) {
		player.consoleMessage(expandDescription("[gcm_eject_point]"));
		this._checkDistMessage = true;
		//this._distCheckTimer.stop();
		//delete this._distCheckTimer;
	}
}

//-------------------------------------------------------------------------------------------------------------
this.$initInterface = function $initInterface(station) {
	var inst = false;
	if (station.isMainStation) {
		var gcm = worldScripts.GalCopBB_Missions;
		var list = gcm.$getListOfMissions(true, [63, 64]);
		for (var i = 0; i < list.length; i++) {
			if (list[i].destination === system.ID &&
				((list[i].data.missionType === 63 && this.$disruptCommodities(system.ID) === false) || (list[i].data.missionType === 64 && this.$disruptEquipment(system.ID) === false)) &&
				list[i].data.targetQuantity > 0 && this._cancelTimer && this._cancelTimer.isRunning) {

				inst = true;
				this._typeToInstall = list[i].data.missionType;

				station.setInterface(this.name, {
					title: expandDescription("[gcm_install_software_title]"),
					category: expandDescription("[gcm_install_software_category]"),
					summary: (this._typeToInstall === 63 ? expandDescription("[gcm_disrupt_market]") : expandDescription("[gcm_disrupt_equipship]")),
					callback: this.$installSoftware.bind(this)
				});
			}
		}
	}
	if (inst === false) {
		station.setInterface(this.name, null);
	}
}

//-------------------------------------------------------------------------------------------------------------
this.$adjustCommodityPrices = function $adjustCommodityPrices() {
	for (var j = 0; j < this._disruptInstalled.length; j++) {
		if (this._disruptInstalled[j].system === system.ID && this._disruptInstalled[j].type === 1) {
			var price = this._disruptInstalled[j].price;
			var mkt = system.mainStation.market;
			for (var key in mkt) {
				if (mkt.hasOwnProperty(key)) {
					system.mainStation.setMarketPrice(key, price);
				}
			}
		}
	}
}

//-------------------------------------------------------------------------------------------------------------
this.$disruptCommodities = function $disruptCommodities(sysID) {
	var result = false;
	for (var i = 0; i < this._disruptInstalled.length; i++) {
		if (this._disruptInstalled[i].system === sysID && this._disruptInstalled[i].type === 1) result = true;
	}
	return result;
}

//-------------------------------------------------------------------------------------------------------------
this.$disruptEquipment = function $disruptEquipment(sysID) {
	var result = false;
	for (var i = 0; i < this._disruptInstalled.length; i++) {
		if (this._disruptInstalled[i].system === sysID && this._disruptInstalled[i].type === 2) result = true;
	}
	return result;
}

//-------------------------------------------------------------------------------------------------------------
this.$installSoftware = function $installSoftware() {
	// remove the interface screen
	player.ship.dockedStation.setInterface(this.name, null);
	this._cancelTimer.stop()

	// theoretically the player can't get here if they sat on the F4 interface screen until the time ran out, then tried an install
	// F4 interface would be invalid and they couldn't select it
	// flag the mission as complete
	var bb = worldScripts.BulletinBoardSystem;
	var gcm = worldScripts.GalCopBB_Missions;
	var list = gcm.$getListOfMissions(true, this._typeToInstall);
	for (var i = 0; i < list.length; i++) {
		if (list[i].destination === system.ID && list[i].data.stage === 1) {
			// add the system to the disrupt list
			this._disruptInstalled.push({
				system: system.ID,
				originalMissionID: list[i].ID,
				installTime: clock.adjustedSeconds,
				type: (this._typeToInstall === 63 ? 1 : 2),
				price: 10.0, // decicredits, means 1cr
				fined: false
			});

			if (this._typeToInstall === 63) this.$adjustCommodityPrices();

			// note the missionID so we can update the disrupt list if the player stays too long
			this._activeMissionID = list[i].ID;

			list[i].data.stage += 1;
			list[i].data.quantity += 1;
			bb.$updateBBMissionPercentage(list[i].ID, 1);

			gcm.$logMissionData(list[i].ID);
			player.consoleMessage(expandDescription("[goal_updated]"));
		}
	}

	// confirm the action to the player
	mission.runScreen({
		screenID: "oolite-gcm-disruptcomplete-summary",
		title: expandDescription("[gcm_disruption_software]"),
		message: expandDescription("[gcm_disrupt_successful]"),
		overlay: {
			name: "gcm-ok.png",
			height: 546
		},
		exitScreen: "GUI_SCREEN_INTERFACES",
	});

	this._checkTimer = new Timer(this, this.$doCapture, parseInt(Math.random() * 15 + 30), 0);
}

//-------------------------------------------------------------------------------------------------------------
this.$doCapture = function $doCapture() {
	this._redockCatch = true;
}

//-------------------------------------------------------------------------------------------------------------
this.$checkForCapture = function $checkForCapture() {
	// skip if the player has launched
	if (player.ship.isInSpace) return;
	// skip if this isn't the main station we're docked at
	if (player.ship.dockedStation.isMainStation == false) return;

	var found = false;
	if (this._activeMissionID != 0) {
		for (var i = 0; i < this._disruptInstalled.length; i++) {
			if (this._disruptInstalled[i].originalMissionID === this._activeMissionID && this._disruptInstalled[i].fined === false) {
				this._disruptInstalled[i].fined = true;
				found = true;
				break;
			}
		}
	}
	// the player has already be fined at this station for this offence
	if (found === false) return;

	var fine = 10000;
	if (player.credits < 10000) fine = Math.floor(player.credits);

	player.credit -= fine;
	player.ship.setBounty(player.bounty + 100, "galcop station interference");

	var curChoices = {};
	curChoices["99_EXIT"] = expandDescription("[gcm_press_space]");

	mission.runScreen({
		screenID: "oolite-gcm-disruptcaught-summary",
		title: expandDescription("[gcm_security_breach]"),
		overlay: {
			name: "gcm-warning.png",
			height: 546
		},
		message: expandDescription("[gcm_disrupt_caught]", {
			type: (this._typeToInstall === 63 ? expandDescription("[gcm_disrupt_market_short]") : expandDescription("[gcm_disrupt_equipship_short]")),
			fine: (fine > 0 ? expandDescription("[gcm_fined]", { amount: formatCredits(fine, false, true) }) : "")
		}),
		exitScreen: "GUI_SCREEN_STATUS"
	});
}

//-------------------------------------------------------------------------------------------------------------
this.$createDataStreamOption = function $createDataStreamOption() {
	var bcc = worldScripts.BroadcastCommsMFD;
	if (bcc.$checkMessageExists("gcm_disrupt_station") === false) {
		bcc.$createMessage({
			messageName: "gcm_disrupt_station",
			callbackFunction: this.$transmitDataStream.bind(this),
			displayText: expandDescription("[gcm_data_stream]"),
			messageText: "",
			ship: system.mainStation,
			transmissionType: "target",
			delayCallback: 1,
			deleteOnTransmit: true,
			hideOnConditionRed: true
		});
	}
}

//-------------------------------------------------------------------------------------------------------------
this.$transmitDataStream = function $transmitDataStream() {
	// check if we're under docking clearance
	// if not, reset the datastream option
	if (this._playerIsDocking === false) {
		player.consoleMessage(expandDescription("[gcm_data_stream_cannot_start]"))
		this._dataStreamCounter = 0;
		this.$createDataStreamOption();
		return;
	}
	var p = player.ship;
	// is the station still targetted?
	if (p.target !== system.mainStation) {
		player.consoleMessage(expandDescription("[gcm_data_stream_interrupted]"));
		this._dataStreamCounter = 0;
		this.$createDataStreamOption();
		return;
	}
	// are we out of range?
	if (p.position.distanceTo(system.mainStation) > p.scannerRange) {
		player.consoleMessage(expandDescription("[gcm_data_stream_out_of_range]"));
		this._dataStreamCounter = 0;
		this.$createDataStreamOption();
		return;
	}
	// check that the player is not in range of police ships
	var chk = p.checkScanner(true);
	for (var i = 0; i < chk.length; i++) {
		if (chk[i].isPolice && p.position.distanceTo(chk[i]) <= (p.scannerRange * 0.6)) {
			var item = worldScripts.BulletinBoardSystem.$getItem(this._mission6364MissionID);

			var fine = 5000;
			if (player.credits < 5000) fine = Math.floor(player.credits);

			player.credit -= fine;
			p.setBounty(player.bounty + 50, "galcop station interference");

			item.data.destroyedQuantity += 1;
			var msg = expandDescription("[gcm_datastream_detected_police]");
			chk[i].commsMessage(msg, p);
			return;
		}
	}
	this._dataStreamCounter += 1;
	if (this._dataStreamCounter === 1) {
		player.consoleMessage(expandDescription("[gcm_data_stream_started]"), 3);
	}
	player.consoleMessage(expandDescription("[gcm_data_stream_progress]", { complete: ((this._dataStreamCounter / 20) * 100).toFixed(0) }), 2);
	if (this._dataStreamCounter === 20) {
		player.consoleMessage(expandDescription("[gcm_data_stream_complete]"), 3);
		var bb = worldScripts.BulletinBoardSystem;
		var item = bb.$getItem(this._mission6364MissionID);
		item.data.stage += 1;
		item.data.quantity += 1;
		bb.$updateBBMissionPercentage(item.ID, item.data.quantity / item.data.targetQuantity);

		worldScripts.GalCopBB_Missions.$logMissionData(item.ID);
		player.consoleMessage(expandDescription("[goal_updated]"));
		// set the abort time for the software install
		this._abortTime = clock.adjustedSeconds + 30;
		this._cancelTimer = new Timer(this, this.$cancelUpload, 1, 1);
		return;
	}
	// restart the timer (total time to 100% should be 100 seconds - docking clearance lasts for 120)
	this._dataStreamTimer = new Timer(this, this.$transmitDataStream, 5, 0);
}

//-------------------------------------------------------------------------------------------------------------
this.$cancelUpload = function $cancelUpload() {
	if (this._abortTime > clock.adjustedSeconds) return;

	system.mainStation.setInterface(this.name, null);
	player.consoleMessage(expandDescription("[gcm_software_expired]"))
	var item = worldScripts.BulletinBoardSystem.$getItem(this._mission6364MissionID);
	item.data.quantity = 0;
	item.data.stage = 0;
	this._cancelTimer.stop();
}

//-------------------------------------------------------------------------------------------------------------
this.$acceptedMission = function $acceptedMission(missID) {
	var bb = worldScripts.BulletinBoardSystem;
	var item = bb.$getItem(missID);
	var gcm = worldScripts.GalCopBB_Missions;

	if (this._debug) log(this.name, "accepted mission id = " + missID);

	if (!item) {
		log(this.name, "!!ERROR: BB returned null value from $getItem on mission ID " + missID);
		return;
	}
	gcm.$updateLastMissionDate(item.source, item.data.missionType);
	gcm.$updateGeneralSettings(item);
}

//-------------------------------------------------------------------------------------------------------------
this.$completedMission = function $completedMission(missID) {
	var p = player.ship;
	var bb = worldScripts.BulletinBoardSystem;
	var item = bb.$getItem(missID);
	var gcm = worldScripts.GalCopBB_Missions;

	// update mission history
	gcm.$updateSuccessHistoryReputation(item);

	// *** type 65 - acquiring galcop station sec software
	if (item.data.missionType === 65) {
		if (p.equipmentStatus("EQ_GCM_SOFTWARE") === "EQUIPMENT_OK") {
			p.removeEquipment("EQ_GCM_SOFTWARE");
			var sbm = worldScripts.Smugglers_BlackMarket;
			if (sbm) sbm.$removeSaleItem("EQ_GCM_SOFTWARE:" + missID);
		}
	}
}

//-------------------------------------------------------------------------------------------------------------
this.$confirmCompleted = function $confirmCompleted(missID) {
	var p = player.ship;
	var result = "";
	var bb = worldScripts.BulletinBoardSystem;
	var item = bb.$getItem(missID);
	if (item) {
		// *** type 65 - acquire galcop station sec software
		if (item.data.missionType === 65) {
			var chk = p.equipmentStatus("EQ_GCM_SOFTWARE");
			if (chk !== "EQUIPMENT_OK") {
				result += (result === "" ? "" : "\n") + expandDescription("[gcm_software_not_found]");
			}
		}
	}
	return result;
}

//-------------------------------------------------------------------------------------------------------------
this.$terminateMission = function $terminateMission(missID) {
	var bb = worldScripts.BulletinBoardSystem;
	var item = bb.$getItem(missID);
	var sbm = worldScripts.Smugglers_BlackMarket;
	var gcm = worldScripts.GalCopBB_Missions;

	// adjust reputation only when the terminatePenalty flag is set to true 
	if (item.data.terminatePenalty === true) {
		// update mission history
		gcm.$updateFailedHistoryReputation(item);
	}

	// *** type 65 - acquiring galcop station security software
	if (item.data.missionType === 65) {
		// if the player terminates the mission after they have the software package, it can only mean the player is going to sell it
		if (item.data.quantity === 3) {
			var eqsts = player.ship.equipmentStatus("EQ_GCM_SOFTWARE");
			if (eqsts === "EQUIPMENT_OK") {
				player.ship.removeEquipment("EQ_GCM_SOFTWARE");
				if (sbm) sbm.$removeSaleItem("EQ_GCM_SOFTWARE:" + missID);
			}
		}
	}
}

//-------------------------------------------------------------------------------------------------------------
this.$failedMission = function $failedMission(missID) {
	var bb = worldScripts.BulletinBoardSystem;
	var sbm = worldScripts.Smugglers_BlackMarket;
	var gcm = worldScripts.GalCopBB_Missions;
	var eq = "";

	if (!sbm) {
		// if there's no blackmarket option, just remove the equipment
		this.$terminateMission(missID);
		return;
	}

	var item = bb.$getItem(missID);
	// update mission history
	gcm.$updateFailedHistoryReputation(item);

	if (item.data.missionType === 65) {
		// if the player terminates the mission after they have the software package, it can only mean the player is going to sell it
		if (item.data.quantity === 3) {
			var eqsts = player.ship.equipmentStatus("EQ_GCM_SOFTWARE");
			if (eqsts === "EQUIPMENT_OK") eq = "EQ_GCM_SOFTWARE";
		}
	}

	if (eq != "") gcm._equipmentFromFailedMissions.push({
		missionType: item.data.missionType,
		source: item.source,
		equip: eq,
		date: clock.adjustedSeconds
	});
}

//-------------------------------------------------------------------------------------------------------------
this.$updateManifestEntry = function $updateManifestEntry(missID) {
	var gcm = worldScripts.GalCopBB_Missions;
	gcm.$updateManifestEntry(missID);
}

//-------------------------------------------------------------------------------------------------------------
this.$missionAvailability = function $missionAvailability(missID, missType, origSysID) {
	return worldScripts.GalCopBB_Missions.$missionAvailability(missID, missType, origSysID);
}

//-------------------------------------------------------------------------------------------------------------
// 63 - disrupt commodities, 64 - disrupt ship maintenance
this.$missionType6364_Values = function $missionType6364_Values(workTime, routeTime, routeDistance, destSysInfo) {
	var result = {};
	result["quantity"] = 2;
	result["price"] = parseInt((parseInt(Math.random() * 500) + 500) / 10) * 10 +
		worldScripts.GalCopBB_CoreMissionValues.$calcPlayerBonus(500); // plus a possible bonus price, based on player score 
	result["expiry"] = clock.adjustedSeconds + routeTime + workTime; // transit time + 1 hour to complete
	result["penalty"] = parseInt(result.price / 4);
	return result;
}

//-------------------------------------------------------------------------------------------------------------
// 65 - acquire security software
this.$missionType65_Values = function $missionType65_Values(workTime, routeTime, routeDistance, destSysInfo) {
	var result = {};
	result["quantity"] = 3;
	result["price"] = parseInt((parseInt(Math.random() * 500) + 500) / 10) * 10 +
		worldScripts.GalCopBB_CoreMissionValues.$calcPlayerBonus(500) // plus a possible bonus price, based on player score 
		+
		worldScripts.GalCopBB_CoreMissionValues.$calcDistanceBonus(routeDistance, 20); // plus a distance bonus
	result["expiry"] = clock.adjustedSeconds + routeTime + workTime; // transit time + 1 hour to complete
	result["penalty"] = parseInt(result.price / 2);
	return result;
}