// /-------------------------\
// |  ATS / ATC / TASC / ATO |
// |   Plugin for openBVE    |
// \-------------------------/

#include "plugin.h"
#include <windows.h>
#include <math.h>



// ---------
// - train -
// ---------

int trainCars = 1;
int trainPowerNotch = 0;
int trainPowerNotches = 0;
int trainBrakeNotch = 0;
int trainReverserPos = 0;
int trainCancelNotch = 0;
int trainMediumNotch = 0;
int trainServiceNotch = 0;
int trainEmergencyNotch = 0;
bool trainDoorsOpen = false;
bool trainInitialized = false;

int lastTime = 0;
int lastSignal = 4;

const int SYSTEM_NONE = 0;
const int SYSTEM_ATS_SN = 1;
const int SYSTEM_ATS_P = 2;
const int SYSTEM_ATC = 3;

int trainSystem = SYSTEM_NONE;


// -------
// - ATS -
// -------

const int ATS_SN_DISABLED = -1;
const int ATS_SN_NORMAL = 0;
const int ATS_SN_CHIME = 1;
const int ATS_SN_ALARM = 2;
const int ATS_SN_EMERGENCY = 3;
const int ATS_SN_INITIALIZE = 4;

int atsSnState = ATS_SN_NORMAL;
int atsSnCountdown = 0;

const int ATS_P_DISABLED = -1;
const int ATS_P_NORMAL = 0;
const int ATS_P_PATTERN = 1;
const int ATS_P_BRAKE = 2;
const int ATS_P_STOP = 3;
const int ATS_P_INITIALIZE = 4;

int atsPState = ATS_P_NORMAL;
float atsPDistance = 999999999.0f;
float atsPRouteLimit = 999.0f;



// -------
// - ATC -
// -------

const int ATC_DISABLED = -1;
const int ATC_NORMAL = 0;
const int ATC_BRAKE = 1;

int atcState = ATC_NORMAL;
float atcRoutePreviousSpeed = -1.0f;
float atcRouteNextSpeed = -1.0f;
float atcRouteAdvanceSpeed = -1.0f;
float atcSignalPreviousSpeed = -1.0f;
float atcSignalNextSpeed = -1.0f;
float atcSignalAdvanceSpeed = -1.0f;
float atcSignalAdvanceTwoSpeed = -1.0f;
float atcPreviousSpeed = -1.0f;
float atcNextSpeed = -1.0f;
float atcAdvanceSpeed = -1.0f;
float atcCurrentSpeed = 0.0f;
float atcTotalSectionLength = 999999999.0f;
float atcRemainingSectionLength = 999999999.0f;
bool atcAdvanceWarning = false;
bool atcFastMode = false;



// --------
// - TASC -
// --------

const int TASC_NORMAL = 0;
const int TASC_PATTERN = 1;
const int TASC_BRAKE = 2;
const int TASC_IGNORE = 3;

bool tascEnabled = false;
int tascState = TASC_NORMAL;
float tascDistance = 999999999.0f;
float tascSpeed = 0.0f;



// -------
// - ATO -
// -------

const int ATO_DISABLED = -1;
const int ATO_CRUISE = 0;
const int ATO_ACCELERATE = 1;
const int ATO_BRAKE = 2;

int atoState = ATO_DISABLED;
int atoPowerNotch = 0;
int atoPowerNotchCountdown = 0;



// ------
// - EB -
// ------

int ebCountdown = 60000;



// ---------
// - panel -
// ---------

const int PANEL_LAMP_SN_POWER = 0;
const int PANEL_LAMP_SN = 1;
const int PANEL_LAMP_SN_EMG = 2;
const int PANEL_LAMP_P_POWER = 3;
const int PANEL_LAMP_P_PATTERN = 4;
const int PANEL_LAMP_P_SRV = 5;
const int PANEL_LAMP_P = 6;
const int PANEL_LAMP_FAILURE = 7;
const int PANEL_LAMP_ATC_POWER = 8;
const int PANEL_LAMP_ATC = 9;
const int PANEL_LAMP_ATC_SRV = 10;
const int PANEL_LAMP_ATC_EMG = 11;
const int PANEL_LAMP_TASC_POWER = 12;
const int PANEL_LAMP_TASC_PATTERN = 13;
const int PANEL_LAMP_TASC_SRV = 14;
const int PANEL_LAMP_ATO = 15;
const int PANEL_LAMP_ATC_SPEED = 16;
const int PANEL_LAMP_EB = 17;
const int PANEL_LAMP_ATC_P = 18;
const int PANEL_LAMP_ATC_ADVANCE = 19;
const int PANEL_DISPLAY_SYSTEM = 21;
const int PANEL_DISPLAY_TASC = 22;
const int PANEL_DISPLAY_ATO = 23;
const int PANEL_DISPLAY_ATC_MODE = 24;
const int PANEL_LAMP_BUREEKI = 30;
const int PANEL_LAMP_RIKKOU = 31;
const int PANEL_TRUE = 99;

const int PANEL_ATO_WHITE = 1;
const int PANEL_ATO_RED = 2;
const int PANEL_ATO_ORANGE = 3;
const int PANEL_ATO_BLUE = 4;



// ---------
// - sound -
// ---------

const int SOUND_CHIME = 0;
const int SOUND_ALARM = 1;
const int SOUND_DING = 2;
const int SOUND_EB = 3;

bool playDing = false;



// -------------
// - functions -
// -------------

// update the ATC current speed
void UpdateAtcCurrentSpeed() {
	if (atcRemainingSectionLength > 0.0f) {
		float u = max(atcPreviousSpeed, 0.0f);
		float v = max(atcNextSpeed, 0.0f);
		float deceleration = (u * u - v * v) / (7.2f * atcTotalSectionLength);
		atcCurrentSpeed = sqrt(7.2f * deceleration * atcRemainingSectionLength + atcNextSpeed * atcNextSpeed);
	} else {
		atcCurrentSpeed = atcNextSpeed;
	}
	if (atcCurrentSpeed > atcPreviousSpeed) {
		atcCurrentSpeed = atcPreviousSpeed;
	}
	if (atcCurrentSpeed < 0.0f) {
		atcCurrentSpeed = 0.0f;
	}
}

// update the ATC speeds
void UpdateAtcSpeeds() {
	bool oldP = atcNextSpeed < 0.0f && atcPreviousSpeed > 0.0f;
	atcPreviousSpeed = min(atcRoutePreviousSpeed, atcSignalPreviousSpeed);
	atcNextSpeed = min(atcRouteNextSpeed, atcSignalNextSpeed);
	atcAdvanceSpeed = min(atcRouteAdvanceSpeed, atcSignalAdvanceSpeed);
	if (atcPreviousSpeed >= atcCurrentSpeed) {
		if (atcFastMode) {
			if (atcNextSpeed < atcPreviousSpeed) {
				atcPreviousSpeed = min(atcPreviousSpeed, max(atcCurrentSpeed, atcNextSpeed));
			}
		} else {
			if (atcAdvanceSpeed < atcNextSpeed || atcNextSpeed < atcPreviousSpeed) {
				atcPreviousSpeed = min(atcPreviousSpeed, max(atcCurrentSpeed, min(atcNextSpeed, atcAdvanceSpeed)));
			}
		}
	}
	if (atcNextSpeed > atcPreviousSpeed) {
		atcNextSpeed = atcPreviousSpeed;
	}
	if (atcAdvanceSpeed > atcNextSpeed) {
		atcAdvanceSpeed = atcNextSpeed;
	}
	atcAdvanceWarning = atcAdvanceSpeed < atcNextSpeed || atcNextSpeed < atcPreviousSpeed || atcNextSpeed <= 0.0f;
	bool newP = atcNextSpeed < 0.0f && atcPreviousSpeed > 0.0f;
	float oldCurrentSpeed = atcCurrentSpeed;
	UpdateAtcCurrentSpeed();
	if (trainSystem == SYSTEM_ATC) {
		if (atcCurrentSpeed > oldCurrentSpeed || oldP != newP) {
			playDing = true;
		}
	}
}

// update the ATC route speed
void UpdateAtcRouteSpeed(float previousSpeed, float nextSpeed, float advanceSpeed) {
	atcRoutePreviousSpeed = previousSpeed;
	atcRouteNextSpeed = nextSpeed;
	atcRouteAdvanceSpeed = advanceSpeed;
	UpdateAtcSpeeds();
}

// update the ATC signal speed
void UpdateAtcSignalSpeed(float previousSpeed, float nextSpeed, float advanceSpeed, float advanceTwoSpeed) {
	atcSignalPreviousSpeed = previousSpeed;
	atcSignalNextSpeed = nextSpeed;
	atcSignalAdvanceSpeed = advanceSpeed;
	atcSignalAdvanceTwoSpeed = advanceTwoSpeed;
	UpdateAtcSpeeds();
}

// get the TASC speed
float GetTascSpeed() {
	const float a = 1.0f;
	const float b = 20.0f;
	const float d = 2.0f;
	const float offset = 0.1f;
	if (tascDistance <= 0.0f) {
		return 0.0f;
	} else if (tascDistance <= a) {
		const float f = 3.0f * sqrt(d / (10.0f * b - 5.0f * a)) / a;
		return offset + f * tascDistance * tascDistance;
	} else if (tascDistance <= b) {
		const float f = 3.0f * sqrt(d / (10.0f * b - 5.0f * a));
		return offset + f * (2.0f * tascDistance - a);
	} else {
		return offset + 3.0f * sqrt(d * (4.0f * tascDistance - a - 2.0f * b) / 5.0f);
	}
}



// -----------
// - exports -
// -----------

extern "C" {

	// called when the plugin is loaded
	ATS_API void WINAPI Load () { }

	// called when the plugin is unloaded
	ATS_API void WINAPI Dispose () { }

	// called to query the API version
	ATS_API int WINAPI GetPluginVersion () {
		return ATS_VERSION;
	}

	// called to inform the plugin about the specs of the train
	ATS_API void WINAPI SetVehicleSpec (ATS_VEHICLESPEC vehicleSpec) {
		trainCars = vehicleSpec.Cars;
		trainPowerNotches = vehicleSpec.PowerNotches;
		trainCancelNotch = vehicleSpec.AtsNotch;
		trainMediumNotch = vehicleSpec.B67Notch;
		trainServiceNotch = vehicleSpec.BrakeNotches;
		trainEmergencyNotch = vehicleSpec.BrakeNotches + 1;
	}

	// called to inform the plugin about the starting mode of the safety system
	ATS_API void WINAPI Initialize (int brake) {
		if (!trainInitialized) {
			if (brake == ATS_INIT_OFF_EMG) {
				atsSnState = ATS_SN_DISABLED;
				atsPState = ATS_P_DISABLED;
				atcState = ATC_DISABLED;
				tascEnabled = false;
				tascState = TASC_NORMAL;
				atoState = ATO_DISABLED;
				trainSystem = SYSTEM_NONE;
			} else {
				atsSnState = ATS_SN_NORMAL;
				atsPState = ATS_P_NORMAL;
				atcState = ATC_NORMAL;
				if (tascEnabled) {
					tascState = TASC_NORMAL;
				}
				if (atoState != ATO_DISABLED) {
					atoState = ATO_CRUISE;
				}
				if (trainSystem == SYSTEM_NONE) {
					trainSystem = SYSTEM_ATS_SN;
				}
			}
			trainInitialized = true;
		}
		atsSnCountdown = 0;
		atsPDistance = 999999999.0;
		tascDistance = 999999999.0f;
		ebCountdown = 60000;
	}

	// called every frame
	ATS_API ATS_HANDLES WINAPI Elapse (ATS_VEHICLESTATE vehicleState, int* panel, int* sound) {
		// --- panel ---
		for (int i = 0; i < 256; i++) {
			panel[i] = 0;
		}
		// --- sound ---
		for (int i = 0; i < 256; i++) {
			if (sound[i] == ATS_SOUND_PLAYLOOPING) {
				sound[i] = ATS_SOUND_STOP;
			} else {
				sound[i] = ATS_SOUND_CONTINUE;
			}
		}
		// --- train ---
		ATS_HANDLES handles;
		handles.Reverser = trainReverserPos;
		handles.Power = trainPowerNotch;
		handles.Brake = trainBrakeNotch;
		handles.ConstantSpeed = ATS_CONSTANTSPEED_CONTINUE;
		// --- time ---
		int timeElapsed = vehicleState.Time - lastTime;
		// --- system ---
		if (trainSystem == SYSTEM_NONE) {
			// --- none ---
			if (trainPowerNotch >= 2) {
				sound[SOUND_ALARM] = ATS_SOUND_PLAYLOOPING;
			}
		} else if (trainSystem == SYSTEM_ATS_SN) {
			// --- ATS-SN ---
			if (atsSnState == ATS_SN_ALARM) {
				atsSnCountdown -= vehicleState.Time - lastTime;
				if (atsSnCountdown <= 0) {
					atsSnState = ATS_SN_EMERGENCY;
				}
			} else if (atsSnState == ATS_SN_EMERGENCY) {
				handles.Brake = trainEmergencyNotch;
			} else if (atsSnState == ATS_SN_INITIALIZE) {
				atsSnCountdown -= vehicleState.Time - lastTime;
				if (atsSnCountdown <= 0) {
					atsSnState = ATS_SN_CHIME;
				} else {
					handles.Brake = trainEmergencyNotch;
				}
			}
			// --- sound ---
			if (atsSnState == ATS_SN_CHIME) {
				sound[SOUND_CHIME] = ATS_SOUND_PLAYLOOPING;
			} else if (atsSnState == ATS_SN_ALARM || atsSnState == ATS_SN_EMERGENCY || atsSnState == ATS_SN_INITIALIZE) {
				sound[SOUND_ALARM] = ATS_SOUND_PLAYLOOPING;
			}
		} else if (trainSystem == SYSTEM_ATS_P) {
			// --- ATS-P ---
			if (atsPState == ATS_P_INITIALIZE) {
				atsSnCountdown -= vehicleState.Time - lastTime;
				if (atsSnCountdown <= 0) {
					atsPState = ATS_P_NORMAL;
					atsPDistance = 999999999.0f;
					playDing = true;
				} else {
					handles.Brake = trainEmergencyNotch;
					sound[SOUND_ALARM] = ATS_SOUND_PLAYLOOPING;
				}
			} else {
				float temp;
				// brake
				float speedBrake = atsPDistance > 0.0 ? 4.09945795874961 * sqrt(atsPDistance) : 0.0f;
				if (speedBrake > atsPRouteLimit) {
					speedBrake = atsPRouteLimit;
				}
				// pattern
				temp = 309.76 + 16.5366666666667 * atsPDistance;
				float speedPattern = temp > 0.0f ? -17.6 + sqrt(temp) : 0.0f;
				if (speedPattern > atsPRouteLimit + 2) {
					speedPattern = atsPRouteLimit + 2;
				}
				// normal
				temp = -682.44 + 19.844 * atsPDistance;
				float speedNormal = temp > 0.0f ? -17.6 + sqrt(temp) : 0.0f;
				if (speedNormal > atsPRouteLimit + 10) {
					speedNormal = atsPRouteLimit + 10;
				}
				// apply
				if (vehicleState.Speed >= speedBrake) {
					if (atsPState == ATS_P_NORMAL || atsPState == ATS_P_PATTERN) {
						atsPState = ATS_P_BRAKE;
						playDing = true;
					}
				} else if (vehicleState.Speed >= speedPattern) {
					if (atsPState == ATS_P_NORMAL) {
						atsPState = ATS_P_PATTERN;
						playDing = true;
					}
				} else if (vehicleState.Speed >= speedNormal) {
					if (atsPState == ATS_P_BRAKE) {
						atsPState = ATS_P_PATTERN;
						playDing = true;
					}
				} else {
					if (atsPState == ATS_P_PATTERN || atsPState == ATS_P_BRAKE) {
						atsPState = ATS_P_NORMAL;
						playDing = true;
					}
				}
				if (atsPState == ATS_P_BRAKE || atsPState == ATS_P_STOP) {
					if (handles.Brake < trainServiceNotch) {
						handles.Brake = trainServiceNotch;
					}
				}
			}
		} else if (trainSystem == SYSTEM_ATC) {
			// --- ATC ---
			if (atcPreviousSpeed < 0.0f) {
				handles.Brake = trainEmergencyNotch;
				panel[PANEL_LAMP_ATC_EMG] = 1;
                atcCurrentSpeed = 0.0f;
			} else {
				UpdateAtcCurrentSpeed();
				if (atcCurrentSpeed == 0.0f) {
					if (handles.Brake < trainServiceNotch) {
						handles.Brake = trainServiceNotch;
					}
					panel[PANEL_LAMP_ATC_SRV] = 1;
					// panel[PANEL_LAMP_ATC_SPEED] = 0;
					atcCurrentSpeed = 0.0;
				} else {
					if (atcState == ATC_NORMAL) {
						if (vehicleState.Speed > atcCurrentSpeed) {
							atcState = ATC_BRAKE;
						}
					} else if (atcState == ATC_BRAKE) {
						if (vehicleState.Speed < atcCurrentSpeed - 1.0) {
							atcState = ATC_NORMAL;
						}
					}
					if (atcState == ATC_BRAKE) {
						int brakeNotch = -(int)floor(-4.0f * (vehicleState.Speed - atcCurrentSpeed)) + trainCancelNotch - 1;
						if (brakeNotch < trainCancelNotch) {
							brakeNotch = trainCancelNotch;
						} else if (brakeNotch > trainServiceNotch) {
							brakeNotch = trainServiceNotch;
						}
						if (handles.Brake < brakeNotch) {
							handles.Brake = brakeNotch;
						}
						panel[PANEL_LAMP_ATC_SRV] = 1;
					}
					panel[PANEL_LAMP_ATC_SPEED] = (int)floor(1000.0 * atcCurrentSpeed);
				}
			}
			panel[PANEL_LAMP_ATC] = 1;
			if (atcNextSpeed < 0.0f && atcPreviousSpeed > 0.0f) {
				panel[PANEL_LAMP_ATC_P] = 1;
			} else if (atcAdvanceWarning && atcNextSpeed >= 0.0f) {
				panel[PANEL_LAMP_ATC_ADVANCE] = 1;
			}
		}
		// --- TASC ---
		panel[PANEL_LAMP_TASC_POWER] = tascEnabled ? 1 : 0;
		if (tascState == TASC_PATTERN) {
			tascSpeed = GetTascSpeed();
			if (vehicleState.Speed >= 0.85f * tascSpeed - 2.5f) {
				tascState = TASC_BRAKE;
			}
			panel[PANEL_LAMP_TASC_PATTERN] = tascEnabled ? 1 : 0;
		} else if (tascState == TASC_BRAKE) {
			if (tascDistance <= -10.0f) {
				tascState = TASC_NORMAL;
				tascSpeed = 0.0f;
			} else if (tascDistance <= 0.0f) {
				if (tascEnabled) {
					int notch = (int)floor(-20.0f * tascDistance) + trainCancelNotch;
					if (notch > trainServiceNotch) {
						notch = trainServiceNotch;
					}
					if (handles.Brake < notch) {
						handles.Brake = notch;
					}
					panel[PANEL_LAMP_TASC_SRV] = 1;
				}
				tascSpeed = 0.0f;
			} else {
				tascSpeed = GetTascSpeed();
				if (vehicleState.Speed < 0.85f * tascSpeed - 2.5f) {
					tascState = TASC_PATTERN;
				} else if (tascEnabled) {
					int notch = (int)floor(vehicleState.Speed - tascSpeed);
					if (notch >= 1) {
						notch += trainCancelNotch - 1;
						if (notch >= trainServiceNotch) {
							notch = trainServiceNotch;
						}
						if (handles.Brake < notch) {
							handles.Brake = notch;
						}
					} else {
						if (handles.Brake == 0 && trainCancelNotch == 2) {
							handles.Brake = 1;
						}
					}
					panel[PANEL_LAMP_TASC_SRV] = 1;
				}
			}
			panel[PANEL_LAMP_TASC_PATTERN] = tascEnabled ? 1 : 0;
		} else if (tascState == TASC_IGNORE && tascDistance <= 0.0f) {
			tascState = TASC_NORMAL;
		}
		// --- ATO ---
		if (atoState != ATO_DISABLED) {
			if (trainSystem == SYSTEM_ATC) {
				if (atcPreviousSpeed < 0.0f) {
					// ATC unavailable
					atoPowerNotch = 0;
					atoPowerNotchCountdown = 0;
					panel[PANEL_LAMP_ATO] = PANEL_ATO_RED;
					handles.Brake = trainEmergencyNotch;
				} else {
					// ATC available
					float atoSpeed;
					if (atcAdvanceWarning) {
						if (atcFastMode) {
							atoSpeed = atcNextSpeed;
						} else {
							atoSpeed = atcAdvanceSpeed;
						}
						if (atoSpeed < 25.0f) {
							atoSpeed = 25.0f;
						}
						if (atoSpeed > atcCurrentSpeed) {
							atoSpeed = atcCurrentSpeed;
						}
					} else {
						atoSpeed = atcCurrentSpeed;
					}
					bool tascInterrupt;
					if (tascEnabled) {
						if (tascState == TASC_PATTERN) {
							if (tascDistance > 0.0) {
								if (atoSpeed > tascSpeed - 1.0) {
									atoSpeed = tascSpeed - 1.0;
								}
								tascInterrupt = false;
							} else {
								tascInterrupt = true;
							}
						} else if (tascState == TASC_BRAKE) {
							tascInterrupt = true;
						} else {
							tascInterrupt = false;
						}
					} else {
						tascInterrupt = false;
					}
					if (handles.Reverser == 1 && handles.Brake == 0 && !trainDoorsOpen && !tascInterrupt) {
						if (atoState == ATO_ACCELERATE) {
							// accelerate
							int powerNotch = (int)floor(0.5f * (atoSpeed - vehicleState.Speed)) + 1;
							if (powerNotch < 0) {
								powerNotch = 0;
							} else if (powerNotch > trainPowerNotches) {
								powerNotch = trainPowerNotches;
							}
							if (atoSpeed <= 15.0f && powerNotch > 1) {
								powerNotch = 1;
							} else if (atoSpeed <= 25.0f && powerNotch > 2) {
								powerNotch = 2;
							} else if (atoSpeed <= 40.0f && powerNotch > 3) {
								powerNotch = 3;
							} else if (atoSpeed <= 55.0f && powerNotch > 4) {
								powerNotch = 4;
							}
							if (atoPowerNotch < powerNotch) {
								atoPowerNotchCountdown += timeElapsed;
								if (atoPowerNotchCountdown >= 400) {
									atoPowerNotch++;
                                    atoPowerNotchCountdown = 0;
								}
							} else {
								atoPowerNotch = powerNotch;
								atoPowerNotchCountdown = 0;
							}
							if (handles.Power < atoPowerNotch) {
								handles.Power = atoPowerNotch;
							}
							if (vehicleState.Speed > atoSpeed - 3.0f) {
								atoState = ATO_CRUISE;
							}
							panel[PANEL_LAMP_ATO] = PANEL_ATO_WHITE;
						} else {
							// cruise
							atoPowerNotch = 0;
							atoPowerNotchCountdown = 0;
							if (vehicleState.Speed < atoSpeed - 7.0f) {
								atoState = ATO_ACCELERATE;
							}
							panel[PANEL_LAMP_ATO] = PANEL_ATO_BLUE;
						}
					} else {
						// blocked
						atoPowerNotch = 0;
						atoPowerNotchCountdown = 0;
						panel[PANEL_LAMP_ATO] = PANEL_ATO_ORANGE;
					}
				}
				panel[PANEL_DISPLAY_ATO] = 2;
			} else {
				atoPowerNotch = 0;
				atoPowerNotchCountdown = 0;
				handles.Brake = trainEmergencyNotch;
				panel[PANEL_LAMP_ATO] = PANEL_ATO_RED;
				panel[PANEL_DISPLAY_ATO] = 3;
			}
		} else {
			atoPowerNotch = 0;
			atoPowerNotchCountdown = 0;
			panel[PANEL_DISPLAY_ATO] = 1;
		}
		// EB
		if ((atsSnState != ATS_SN_DISABLED || atsPState != ATS_P_DISABLED) && (vehicleState.Speed < -1.0f || vehicleState.Speed > 1.0f)) {
			if (ebCountdown <= 0) {
				if (ebCountdown <= -5000) {
					if (atsSnState != ATS_SN_DISABLED) {
						if (atsSnState == ATS_SN_NORMAL || atsSnState == ATS_SN_CHIME || atsSnState == ATS_SN_ALARM) {
							atsSnState = ATS_SN_EMERGENCY;
							trainSystem = SYSTEM_ATS_SN;
							ebCountdown = 60000;
						}
					} else {
						if (atsPState == ATS_P_NORMAL || atsPState == ATS_P_PATTERN || atsPState == ATS_P_BRAKE) {
							atsPState = ATS_P_STOP;
							trainSystem = SYSTEM_ATS_P;
							ebCountdown = 60000;
							playDing = true;
						}
					}
				} else {
					panel[PANEL_LAMP_EB] = 1;
					sound[SOUND_EB] = ATS_SOUND_PLAYLOOPING;
					ebCountdown -= vehicleState.Time - lastTime;
				}
			} else {
				ebCountdown -= vehicleState.Time - lastTime;
			}
		} else {
			ebCountdown = 60000;
		}
		// door
		if (trainSystem != SYSTEM_NONE && trainDoorsOpen) {
			handles.Power = 0;
		}
		// --- constant monitoring
		float distance = 0.000277777777777778f * vehicleState.Speed * (float)timeElapsed;
		atsPDistance -= distance;
		tascDistance -= distance;
		atcRemainingSectionLength -= distance;
		// --- panel ---
		bool initialize = trainSystem == SYSTEM_ATS_SN && atsSnState == ATS_SN_INITIALIZE || trainSystem == SYSTEM_ATS_P && atsPState == ATS_P_INITIALIZE;
		panel[PANEL_LAMP_SN_POWER] = atsSnState != ATS_SN_DISABLED;
		panel[PANEL_LAMP_SN] = trainSystem == SYSTEM_ATS_SN && (atsSnState == ATS_SN_NORMAL || atsSnState == ATS_SN_CHIME) ? 1 : 0;
		panel[PANEL_LAMP_SN_EMG] = trainSystem == SYSTEM_ATS_SN && (atsSnState == ATS_SN_ALARM || atsSnState == ATS_SN_EMERGENCY && vehicleState.Time % 1200 < 600) ? 1 : 0;
		panel[PANEL_LAMP_P_POWER] = atsPState != ATS_P_DISABLED ? 1 : 0;
		panel[PANEL_LAMP_P_PATTERN] = trainSystem == SYSTEM_ATS_P && (atsPState != ATS_P_NORMAL || initialize) ? 1 : 0;
		panel[PANEL_LAMP_P_SRV] = trainSystem == SYSTEM_ATS_P && (atsPState == ATS_P_BRAKE || atsPState == ATS_P_STOP || initialize) ? 1 : 0;
		panel[PANEL_LAMP_P] = trainSystem == SYSTEM_ATS_P ? 1 : 0;
		panel[PANEL_LAMP_ATC_POWER] = atcState != ATC_DISABLED ? 1 : 0;
		panel[PANEL_LAMP_FAILURE] = initialize ? 1 : 0;
		panel[PANEL_DISPLAY_SYSTEM] = trainSystem + 1;
		panel[PANEL_DISPLAY_TASC] = tascEnabled ? 2 : 1;
		panel[PANEL_DISPLAY_ATC_MODE] = trainSystem == SYSTEM_ATC && atcFastMode ? 1 : 0;
		panel[PANEL_LAMP_BUREEKI] = handles.Brake >= trainCancelNotch && handles.Brake < trainEmergencyNotch || handles.Reverser != 0 && trainCancelNotch == 2 && handles.Brake == 1 ? 1 : 0;
		panel[PANEL_LAMP_RIKKOU] = handles.Reverser != 0 && (handles.Power != 0 && handles.Brake == 0 || trainCancelNotch == 2 && handles.Brake == 1) ? 1 : 0;
		panel[PANEL_TRUE] = 1;
		// --- finalize ---
		if (playDing) {
			sound[SOUND_DING] = ATS_SOUND_PLAY;
			playDing = false;
		}
		if (handles.Brake != 0) {
			handles.Power = 0;
		}
		lastTime = vehicleState.Time;
		return handles;
	}

	// called when the power notch is changed
	ATS_API void WINAPI SetPower (int notch) {
		trainPowerNotch = notch;
		ebCountdown = 60000;
	}

	// called when the brake notch is changed
	ATS_API void WINAPI SetBrake (int notch) {
		trainBrakeNotch = notch;
		ebCountdown = 60000;
	}

	// called when the reverser is changed
	ATS_API void WINAPI SetReverser (int pos) {
		trainReverserPos = pos;
		ebCountdown = 60000;
	}

	// called when a plugin key is pressed
	ATS_API void WINAPI KeyDown (int atsKeyCode) {
		switch (atsKeyCode) {
			case ATS_KEY_S:
				// acknowledge alarm
				if (trainSystem == SYSTEM_ATS_SN && atsSnState == ATS_SN_ALARM && trainPowerNotch == 0 && trainBrakeNotch >= trainCancelNotch) {
					atsSnState = ATS_SN_CHIME;
				}
				break;
			case ATS_KEY_A1:
				// cancel chime
				if (trainSystem == SYSTEM_ATS_SN && atsSnState == ATS_SN_CHIME) {
					atsSnState = ATS_SN_NORMAL;
				}
				break;
			case ATS_KEY_A2:
				// reset eb
				if (ebCountdown < 0) {
					ebCountdown = 60000;
				}
				break;
			case ATS_KEY_B1:
				// reset safety system
				if (trainSystem == SYSTEM_ATS_SN && atsSnState == ATS_SN_EMERGENCY && trainReverserPos == 0 && trainPowerNotch == 0 && trainBrakeNotch == trainEmergencyNotch) {
					atsSnState = ATS_SN_CHIME;
				} else if (trainSystem == SYSTEM_ATS_P && (atsPState == ATS_P_BRAKE || atsPState == ATS_P_STOP) && trainReverserPos == 0 && trainPowerNotch == 0 && trainBrakeNotch >= trainServiceNotch) {
					atsPState = ATS_P_NORMAL;
					atsPDistance = 999999999.0f;
					playDing = true;
				}
				break;
			case ATS_KEY_C1:
				// switch to ats
				if (trainSystem == SYSTEM_ATC) {
					if (atsSnState != ATS_SN_DISABLED) {
						trainSystem = SYSTEM_ATS_SN;
						atsSnState = ATS_SN_CHIME;
						atsSnCountdown = 1000;
						playDing = true;
					} else if (atsPState != ATS_P_DISABLED) {
						trainSystem = SYSTEM_ATS_P;
						atsPState = ATS_P_NORMAL;
						atsPDistance = 999999999.0f;
						atsSnCountdown = 1000;
						playDing = true;
					}
				}
				break;
			case ATS_KEY_C2:
				// switch to atc
				if (
				    trainSystem == SYSTEM_ATS_SN && atsSnState != ATS_SN_EMERGENCY && atsSnState != ATS_SN_INITIALIZE ||
				    trainSystem == SYSTEM_ATS_P && atsPState != ATS_P_STOP && atsPState != ATS_P_INITIALIZE
				) {
					if (atcState != ATC_DISABLED) {
						trainSystem = SYSTEM_ATC;
						playDing = true;
					}
				}
				break;
			case ATS_KEY_D:
				// enable/disable ATS-SN
				if (atsSnState == ATS_SN_DISABLED) {
					if (trainSystem == SYSTEM_NONE) {
						trainSystem = SYSTEM_ATS_SN;
						atsSnState = ATS_SN_INITIALIZE;
						atsSnCountdown = 1000;
						playDing = true;
					} else {
						atsSnState = ATS_SN_NORMAL;
					}
				} else {
					if (trainSystem == SYSTEM_ATS_SN) {
						if (atsSnState != ATS_SN_EMERGENCY && atsSnState != ATS_SN_INITIALIZE) {
							if (atsPState != ATS_P_DISABLED) {
								trainSystem = SYSTEM_ATS_P;
								atsPState = ATS_P_NORMAL;
								atsPDistance = 999999999.0f;
								playDing = true;
							} else if (atcState != ATC_DISABLED) {
								trainSystem = SYSTEM_ATC;
								playDing = true;
							} else {
								trainSystem = SYSTEM_NONE;
							}
							atsSnState = ATS_SN_DISABLED;
						}
					} else {
						atsSnState = ATS_SN_DISABLED;
					}
				}
				break;
			case ATS_KEY_E:
				// enable/disable ATS-P
				if (atsPState == ATS_P_DISABLED) {
					if (trainSystem == SYSTEM_NONE) {
						trainSystem = SYSTEM_ATS_P;
						atsPState = ATS_P_INITIALIZE;
						atsPDistance = 999999999.0f;
						atsSnCountdown = 1000;
						playDing = true;
					} else {
						atsPState = ATS_P_NORMAL;
						atsPDistance = 999999999.0f;
					}
				} else {
					if (trainSystem == SYSTEM_ATS_P) {
						if (atsPState != ATS_P_STOP && atsPState != ATS_P_INITIALIZE) {
							if (atsSnState != ATS_SN_DISABLED) {
								trainSystem = SYSTEM_ATS_SN;
								atsSnState = ATS_SN_CHIME;
								playDing = true;
							} else if (atcState != ATC_DISABLED) {
								trainSystem = SYSTEM_ATC;
								playDing = true;
							} else {
								trainSystem = SYSTEM_NONE;
							}
							atsPState = ATS_SN_DISABLED;
						}
					} else {
						atsPState = ATS_SN_DISABLED;
					}
				}
				break;
			case ATS_KEY_F:
				// enable/disable ATC
				if (atcState == ATC_DISABLED) {
					if (trainSystem == SYSTEM_NONE) {
						trainSystem = SYSTEM_ATC;
						playDing = true;
					}
					atcState = ATC_NORMAL;
				} else {
					if (trainSystem == SYSTEM_ATC) {
						if (atsSnState != ATS_SN_DISABLED) {
							trainSystem = SYSTEM_ATS_SN;
							atsSnState = ATS_SN_CHIME;
							playDing = true;
						} else if (atsPState != ATS_P_DISABLED) {
							trainSystem = SYSTEM_ATS_P;
							atsPState = ATS_P_NORMAL;
							atsPDistance = 999999999.0f;
							playDing = true;
						} else {
							trainSystem = SYSTEM_NONE;
						}
					}
					atcState = ATC_DISABLED;
				}
				break;
			case ATS_KEY_G:
				// enable/disable TASC
				if (tascEnabled) {
					tascEnabled = false;
				} else {
					tascEnabled = true;
				}
				break;
			case ATS_KEY_H:
				// enable/disable ATO
				if (atoState == ATO_DISABLED) {
					atoState = ATO_CRUISE;
				} else {
					atoState = ATO_DISABLED;
				}
				break;
			case ATS_KEY_I:
				// not used
				break;
			case ATS_KEY_J:
				// not used
				break;
			case ATS_KEY_K:
				// not used
				break;
			case ATS_KEY_L:
				// DEBUG
				atcFastMode = !atcFastMode;
				break;
		}
	}

	// called when a plugin key is released
	ATS_API void WINAPI KeyUp (int atsKeyCode) {
	}

	// called when a horn is played (or stopped)
	ATS_API void WINAPI HornBlow (int hornType) {
		ebCountdown = 60000;
	}

	// called when the doors start opening
	ATS_API void WINAPI DoorOpen () {
		if (trainSystem == SYSTEM_ATC && atcPreviousSpeed > 0.0f && atcNextSpeed < 0.0f) {
			if (atsSnState != ATS_SN_DISABLED) {
				trainSystem = SYSTEM_ATS_SN;
				atsSnState = ATS_SN_CHIME;
				playDing = true;
			} else if (atsPState != ATS_P_DISABLED) {
				trainSystem = SYSTEM_ATS_P;
				atsPState = ATS_P_NORMAL;
				atsPDistance = 999999999.0f;
				playDing = true;
			}
		} else if ((trainSystem == SYSTEM_ATS_SN || trainSystem == SYSTEM_ATS_P) && lastSignal > 100) {
			if (
				trainSystem == SYSTEM_ATS_SN && atsSnState != ATS_SN_EMERGENCY && atsSnState != ATS_SN_INITIALIZE ||
				trainSystem == SYSTEM_ATS_P && atsPState != ATS_P_STOP && atsPState != ATS_P_INITIALIZE
			) {
				if (atcState != ATC_DISABLED) {
					trainSystem = SYSTEM_ATC;
					atcState = ATC_NORMAL;
					playDing = true;
				}
			}
		}
		if (tascState == TASC_PATTERN || tascState == TASC_BRAKE) {
			tascDistance = -1.0f;
		}
		trainDoorsOpen = true;
	}

	// called when the doors have closed
	ATS_API void WINAPI DoorClose () {
		if (tascState == TASC_PATTERN || tascState == TASC_BRAKE) {
			tascState = TASC_IGNORE;
			tascDistance = 10.0f;
		}
		trainDoorsOpen = false;
	}

	// called when the state of the upcoming signal changes
	ATS_API void WINAPI SetSignal (int signal) {
		float previousSpeed;
		float nextSpeed;
		float advanceSpeed;
		float advanceTwoSpeed;
		switch (signal) {
			case 101:
				previousSpeed = -1.0f;
				nextSpeed = -1.0f;
				advanceSpeed = -1.0f;
				advanceTwoSpeed = -1.0f;
				break;
			case 102:
				previousSpeed = 0.0f;
				nextSpeed = -1.0f;
				advanceSpeed = -1.0f;
				advanceTwoSpeed = -1.0f;
				break;
			case 103:
				previousSpeed = 25.0f;
				nextSpeed = 0.0f;
				advanceSpeed = -1.0f;
				advanceTwoSpeed = -1.0f;
				break;
			case 104:
				previousSpeed = 40.0f;
				nextSpeed = 25.0f;
				advanceSpeed = 0.0f;
				advanceTwoSpeed = -1.0f;
				break;
			case 105:
				previousSpeed = 55.0f;
				nextSpeed = 40.0f;
				advanceSpeed = 25.0f;
				advanceTwoSpeed = 0.0f;
				break;
			case 106:
				previousSpeed = 65.0f;
				nextSpeed = 55.0f;
				advanceSpeed = 40.0f;
				advanceTwoSpeed = 25.0f;
				break;
			case 107:
				previousSpeed = 75.0f;
				nextSpeed = 65.0f;
				advanceSpeed = 55.0f;
				advanceTwoSpeed = 40.0f;
				break;
			case 108:
				previousSpeed = 83.0f;
				nextSpeed = 75.0f;
				advanceSpeed = 65.0f;
				advanceTwoSpeed = 55.0f;
				break;
			case 109:
				previousSpeed = 90.0f;
				nextSpeed = 83.0f;
				advanceSpeed = 75.0f;
				advanceTwoSpeed = 65.0f;
				break;
			case 110:
				previousSpeed = 90.0f;
				nextSpeed = 90.0f;
				advanceSpeed = 83.0f;
				advanceTwoSpeed = 75.0f;
				break;
			case 111:
				previousSpeed = 90.0f;
				nextSpeed = 90.0f;
				advanceSpeed = 90.0f;
				advanceTwoSpeed = 83.0f;
				break;
			case 112:
				previousSpeed = 90.0f;
				nextSpeed = 90.0f;
				advanceSpeed = 90.0f;
				advanceTwoSpeed = 90.0f;
				break;
			default:
				previousSpeed = -1.0f;
				nextSpeed = -1.0f;
				advanceSpeed = -1.0f;
				advanceTwoSpeed = -1.0f;
				break;
		}
		UpdateAtcSignalSpeed(previousSpeed, nextSpeed, advanceSpeed, advanceTwoSpeed);
		lastSignal = signal;
	}

	// called when a beacon is passed
	ATS_API void WINAPI SetBeaconData (ATS_BEACONDATA beaconData) {
		if (beaconData.Type == 0) {
			// --- S-type ---
			if (trainSystem == SYSTEM_ATS_P && (atsPState == ATS_P_NORMAL || atsPState == ATS_P_PATTERN) && beaconData.Optional == 0) {
				if (atsSnState == ATS_SN_DISABLED) {
					if (atsPState == ATS_P_NORMAL || atsPState == ATS_P_PATTERN) {
						playDing = true;
					}
					atsPState = ATS_P_STOP;
					atsPDistance = -999999999.0f;
				} else {
					trainSystem = SYSTEM_ATS_SN;
					atsSnState = ATS_SN_CHIME;
				}
			}
			if (trainSystem == SYSTEM_ATS_SN && (beaconData.Signal == 0 || beaconData.Signal >= 100)) {
				if (atsSnState == ATS_SN_NORMAL || atsSnState == ATS_SN_CHIME) {
					// --- activate alarm ---
					atsSnState = ATS_SN_ALARM;
					atsSnCountdown = 5000;
				}
			}
		} else if (beaconData.Type == 1) {
			// --- SN-type ---
			if (trainSystem == SYSTEM_ATS_P && (atsPState == ATS_P_NORMAL || atsPState == ATS_P_PATTERN) && beaconData.Optional == 0) {
				if (atsSnState == ATS_SN_DISABLED) {
					if (atsPState == ATS_P_NORMAL || atsPState == ATS_P_PATTERN) {
						playDing = true;
					}
					atsPState = ATS_P_STOP;
					atsPDistance = -999999999.0f;
				} else {
					trainSystem = SYSTEM_ATS_SN;
					atsSnState = ATS_SN_CHIME;
				}
			}
			if (trainSystem == SYSTEM_ATS_SN && (beaconData.Signal == 0 || beaconData.Signal >= 100)) {
				if (atsSnState != ATS_SN_INITIALIZE) {
					atsSnState = ATS_SN_EMERGENCY;
				}
			}
		} else if (beaconData.Type == 2) {
			// --- accidental departure ---
			if (trainSystem == SYSTEM_ATS_SN || trainSystem == SYSTEM_ATS_P) {
				if (beaconData.Signal == 0 || beaconData.Signal >= 100) {
					if (beaconData.Optional >= trainCars || beaconData.Optional == 0) {
						if (trainSystem != SYSTEM_ATS_SN || atsSnState != ATS_SN_INITIALIZE) {
							if (atsSnState == ATS_SN_DISABLED) {
								if (atsPState == ATS_P_NORMAL || atsPState == ATS_P_PATTERN) {
									playDing = true;
								}
								atsPState = ATS_P_STOP;
								atsPDistance = -999999999.0f;
							} else {
								trainSystem = SYSTEM_ATS_SN;
								atsSnState = ATS_SN_EMERGENCY;
							}
						}
					}
				}
			}
		} else if (beaconData.Type == 3) {
			// --- pattern renewal ---
			if (trainSystem == SYSTEM_ATS_SN && atsSnState != ATS_SN_EMERGENCY && atsSnState != ATS_SN_INITIALIZE && atsPState != ATS_P_DISABLED && beaconData.Optional == 0) {
				trainSystem = SYSTEM_ATS_P;
				atsPState = ATS_P_NORMAL;
				playDing = true;
			}
			if (trainSystem == SYSTEM_ATS_P) {
				if (beaconData.Signal == 0 || beaconData.Signal >= 100) {
					atsPDistance = beaconData.Distance;
				} else {
					atsPDistance = 999999999.0f;
				}
			}
		} else if (beaconData.Type == 4) {
			// --- immediate stop ---
			if (trainSystem == SYSTEM_ATS_SN && atsSnState != ATS_SN_EMERGENCY && atsSnState != ATS_SN_INITIALIZE && atsPState != ATS_P_DISABLED && beaconData.Optional == 0) {
				trainSystem = SYSTEM_ATS_P;
				atsPState = ATS_P_NORMAL;
				playDing = true;
			}
			if (trainSystem == SYSTEM_ATS_P) {
				if (beaconData.Signal == 0 || beaconData.Signal >= 100) {
					if (atsPState == ATS_P_NORMAL || atsPState == ATS_P_PATTERN) {
						playDing = true;
					}
					atsPState = ATS_P_STOP;
					atsPDistance = -999999999.0f;
				} else {
					atsPDistance = 999999999.0f;
				}
			}
		} else if (beaconData.Type == 6) {
			// --- TASC pattern ---
			int minimumCars = beaconData.Optional / 1000000;
			int maximumCars = (beaconData.Optional % 1000000) / 10000;
			if (trainCars >= minimumCars && trainCars <= maximumCars || maximumCars == 0) {
				if (tascState == TASC_NORMAL) {
					tascState = TASC_PATTERN;
				}
				tascDistance = (float)(beaconData.Optional % 10000);
			}
		} else if (beaconData.Type == 7) {
			// --- ATC advance ---
			// dddaabbcc
			atcTotalSectionLength = (beaconData.Optional / 1000000) % 1000;
			if (atcTotalSectionLength == 0.0f) {
                atcTotalSectionLength = 999999999.0f;
			}
			atcRemainingSectionLength = atcTotalSectionLength;
			float routePreviousSpeed = (beaconData.Optional / 10000) % 100;
			float routeNextSpeed = (beaconData.Optional / 100) % 100;
			if (routeNextSpeed == 0.0f) {
                routeNextSpeed = -1.0f;
			}
			float routeAdvanceSpeed = beaconData.Optional % 100;
			if (routeAdvanceSpeed == 0.0f) {
				routeAdvanceSpeed = -1.0f;
			}
			atcSignalPreviousSpeed = atcSignalNextSpeed;
			atcSignalNextSpeed = atcSignalAdvanceSpeed;
			atcSignalAdvanceSpeed = atcSignalAdvanceTwoSpeed;
			UpdateAtcRouteSpeed(routePreviousSpeed, routeNextSpeed, routeAdvanceSpeed);
		} else if (beaconData.Type == 8) {
			// --- ATS-P pattern ---
			atsPRouteLimit = beaconData.Optional;
		}
	}
	
}
