// /--------------------\
// |      ATS-SN/P      |
// | Plugin for openBVE |
// \--------------------/

#include "plugin.h"
#include "stdio.h"
#include "direct.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;

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;


// ---------
// - 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_BUREEKI = 30;
const int PANEL_LAMP_RIKKOU = 31;
const int PANEL_TRUE = 99;


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

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

bool playDing = false;


// -----------
// - 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 security system
	ATS_API void WINAPI Initialize (int brake) {
		if (!trainInitialized) {
			if (brake == ATS_INIT_OFF_EMG) {
				atsSnState = ATS_SN_DISABLED;
				atsPState = ATS_P_DISABLED;
				trainSystem = SYSTEM_NONE;
			} else {
				atsSnState = ATS_SN_NORMAL;
				atsPState = ATS_P_NORMAL;
				if (trainSystem == SYSTEM_NONE) {
					trainSystem = SYSTEM_ATS_SN;
				}
			}
			trainInitialized = true;
		}
		atsSnCountdown = 0;
		atsPDistance = 999999999.0;
	}

	// 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;
		// --- system ---
		if (trainSystem == SYSTEM_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;
					}
				}
			}
		}
		// door
		if (trainSystem != SYSTEM_NONE && trainDoorsOpen) {
			handles.Power = 0;
		}
		// --- constant monitoring
		float distance = 0.000277777777777778f * vehicleState.Speed * (float)(vehicleState.Time - lastTime);
		atsPDistance -= 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_FAILURE] = initialize ? 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;
	}

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

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

	// 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_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_D:
				// enable/disable ATS-SN
				if (atsSnState == ATS_SN_DISABLED) {
					trainSystem = SYSTEM_ATS_SN;
					atsSnState = ATS_SN_INITIALIZE;
					atsSnCountdown = 1000;
					playDing = true;
				} else if (atsSnState == ATS_SN_NORMAL || atsSnState == ATS_SN_CHIME || atsSnState == ATS_SN_ALARM) {
					trainSystem = SYSTEM_NONE;
					atsSnState = ATS_SN_DISABLED;
					atsPState = ATS_P_DISABLED;
				}
				break;
			case ATS_KEY_E:
				// enable/disable ATS-P
				if (atsPState == ATS_P_DISABLED) {
					if (trainSystem == SYSTEM_ATS_SN) {
						atsPState = ATS_P_NORMAL;
						atsPDistance = 999999999.0f;
					}
				} else {
					if (trainSystem == SYSTEM_ATS_P) {
						if (atsPState != ATS_P_STOP && atsPState != ATS_P_INITIALIZE) {
							trainSystem = SYSTEM_ATS_SN;
							atsSnState = ATS_SN_CHIME;
							atsPState = ATS_P_DISABLED;
							playDing = true;
						}
					} else {
						atsPState = ATS_P_DISABLED;
					}
				}
				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) {
	}

	// called when the doors start opening
	ATS_API void WINAPI DoorOpen () {
		trainDoorsOpen = true;
	}

	// called when the doors have closed
	ATS_API void WINAPI DoorClose () {
		trainDoorsOpen = false;
	}

	// called when the state of the upcoming signal changes
	ATS_API void WINAPI SetSignal (int signal) {
		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 == 8) {
			// --- ATS-P pattern ---
			atsPRouteLimit = beaconData.Optional;
		}
	}

}
