// main controller for the installation
// TODO: playback of the recordings, automation, switch from open-loop to closed-loop with the openCV tracker
(
var imgPositions, imgIndexToAudioIndex, curPos, tarPos,
netAddress, localAddress, serialPort, serialListener,
moveTo, jogControl, jogHorizontal, jogVertical,
imgSelect, imgCalibrate, lastSelect,
trackerPos, trackerOffsetBaseDist, trackerOffset, trackLock;

// init global vars
imgPositions = 9.collect({nil});
imgIndexToAudioIndex = [0, 1, nil, 2, nil, 3, nil, 4, 5];
curPos = Point.new(0, 0);
tarPos = Point.new(0, 0);
netAddress = NetAddr.new("127.0.0.1", 8080);
localAddress = NetAddr.new("127.0.0.1", 57120);
lastSelect = -1;
trackerOffsetBaseDist = 16000;
trackerOffset = [
	[trackerOffsetBaseDist * 1, trackerOffsetBaseDist * 1],
	[trackerOffsetBaseDist * 0, trackerOffsetBaseDist * 1],
	[trackerOffsetBaseDist * -1, trackerOffsetBaseDist * 1],

	[trackerOffsetBaseDist * 1, trackerOffsetBaseDist * 0],
	[trackerOffsetBaseDist * 0, trackerOffsetBaseDist * 0],
	[trackerOffsetBaseDist * -1, trackerOffsetBaseDist * 0],

	[trackerOffsetBaseDist * 1, trackerOffsetBaseDist * -1],
	[trackerOffsetBaseDist * 0, trackerOffsetBaseDist * -1],
	[trackerOffsetBaseDist * -1, trackerOffsetBaseDist * -1]
];
trackLock = true;

~serialPort = SerialPort("/dev/ttyACM0", baudrate: 115200, crtscts: true);

// recieve motor feedback
~serialListener = Routine({
	var byte, str, res, valArray,
	stepper, limitSwitchNeg, limitSwitchPos, safeMode, limitPos;

	safeMode = false;

	loop{
		byte = ~serialPort.read;
		if(byte==13, {
			if(str[1].asString == "[", {
				valArray = str.asString.interpret.postln;
				curPos = Point.new(valArray[0], valArray[1]);
				limitSwitchNeg = valArray[2];
				limitSwitchPos = valArray[3];
				if(safeMode && (limitSwitchNeg == limitSwitchPos), {
					safeMode = false;
					fork {
						netAddress.sendMsg("/STATE/SET", "{message: \"all clear\"}");
						2.wait;
						netAddress.sendMsg("/STATE/SET", "{message: \"\"}");
					}
				});
			}, {
				if(str[1..3].asString == "!!!", {
					netAddress.sendMsg("/STATE/SET", "{message: \"!!! limit switch still on after 1000 steps, this should NEVER happen\"}");
				}, {
					safeMode = true;
					netAddress.sendMsg("/STATE/SET", "{message: \"!! limit hit, move the other direction\"}");
				});
			});
			str = "";
		}, {str = str++byte.asAscii});
	};
}).play(AppClock);

// send new coordinates to the arduino / motors
moveTo = {arg point;
	if((point.x.abs < 8000) && (curPos.y.abs < 8000), {
		"move to".postln;
		point.postln;
		~serialPort.putAll(point.x.asInteger.asString ++ " " ++ point.y.asInteger.asString);
		~serialPort.put(10);
	});
};

jogControl = {arg axis;
	var jog, count = 0, jogRate= 0, jogDirection = 1;
	jog = Task({
		loop{
			count = (count + 0.01).clip(0, 1);
			count.postln;
			jogRate = pow(count, 2) * 500;
			if(axis == '/jog_horizontal', {
				tarPos.x = curPos.x + (jogRate * jogDirection * -1);
			}, {
				tarPos.y = curPos.y + (jogRate * jogDirection);
			});
			curPos.postln;
			moveTo.value(tarPos);
			0.1.wait
		};
	});
	OSCFunc({arg msg;
		msg.postln;
		if(msg[1] == 0, {count = 0; jogRate = 0; jog.pause()}, {jogDirection = msg[1]; jog.play(AppClock)});
	}, axis, netAddress);
};

jogHorizontal = jogControl.value('/jog_horizontal');
jogVertical = jogControl.value('/jog_vertical');

imgSelect = {
	OSCFunc({arg msg;
		var imgIndex, audioIndex;
		msg.postln;
		if((msg[1] > 0), {
			trackLock = false;
			imgIndex = msg[1] - 1;
			imgIndex.postln;
			if(imgPositions[imgIndex] != nil, {tarPos = imgPositions[imgIndex].deepCopy; moveTo.value(tarPos)});
			9.do({arg i; if(imgIndex != i, {
				netAddress.sendMsg("/STATE/SET", "{img_" ++ (i + 1).asString ++ "_select: " ++ (i + 1).neg ++ "}")})});
			lastSelect = imgIndex;
			audioIndex = imgIndexToAudioIndex[imgIndex];
			if(audioIndex != nil, {localAddress.sendMsg('/playTiling', audioIndex)});
			Routine({60.wait; trackLock = true}).play;
		}, {
			lastSelect.postln;
			if(msg[1] == ((lastSelect + 1) * -1), {"here".postln; lastSelect = -1})
		});
	}, '/img_select', netAddress)
}.value;

imgCalibrate = {
	var calibrateHold, imgIndex, setPos;
	calibrateHold = Routine({
		var imageCalibrationIndex = 0;
		20.do({0.1.wait});
		[-1, 0, 1].do({arg row;
			[-1, 0, 1].do({arg col;
				imgPositions[imageCalibrationIndex] = setPos.deepCopy + Point.new(6550 * col, 6550 * row);
				imgPositions[imageCalibrationIndex].postln;
				imageCalibrationIndex = imageCalibrationIndex + 1;
			});
		});

		imgPositions.postln;
		netAddress.sendMsg("/STATE/SET", "{message: \"image calibrated\"}");
	});

	OSCFunc({ arg msg;
		imgIndex = msg[1] - 1;
		if(imgIndex >= 0, {
			setPos = curPos.deepCopy;
			calibrateHold.play(AppClock);
		}, {
			calibrateHold.stop; calibrateHold.reset; netAddress.sendMsg("/STATE/SET", "{message: \"\"}");
		});
	}, '/img_calibrate', netAddress);
}.value;

trackerPos = OSCFunc({arg msg;
	if((lastSelect.postln > -1) && trackLock.postln.not, {
		var distX, distY, isFocusing;
		distX = curPos.x - imgPositions[lastSelect].x;
		distY = curPos.y - imgPositions[lastSelect].y;
		isFocusing = false;
		if((imgPositions[lastSelect] != nil) && (distX.abs < 300) && ((msg[1] - trackerOffset[lastSelect][0]).abs > 3000), {
			tarPos.x = curPos.x + (10 * (msg[1] - trackerOffset[lastSelect][0]).sign);// * -1);
			isFocusing = true;
		});
		if((imgPositions[lastSelect] != nil) && (distY.abs < 300) && ((msg[2] - trackerOffset[lastSelect][1]).abs > 3000), {
			tarPos.y = curPos.y + (10 * (msg[2] - trackerOffset[lastSelect][1]).sign);// * -1);
			isFocusing = true;
		});
		if(isFocusing, {moveTo.value(tarPos)});
	})
}, '/trackerpos');
)

//in case of emergency
~serialPort.close
~serialPort = SerialPort.new("/dev/ttyACM0", baudrate: 115200, crtscts: true);
~serialListener.reset
~serialListener.play(AppClock);


