#pragma once

#pragma region include
// C/C++ utility libraries
#include <memory>
#include <vector>
#include <ctime>
#include <fstream>
#include <iomanip>
#include <sstream>
#include <chrono>
#include <curl/curl.h>
#include <filesystem>

#include "ofURLFileLoader.h"

// Definitions
#include "Definitions.h"  // a set of definitions for utility / convenience

// openFrameworks addons 
#include "ofMain.h"  // main openFrameworks libraries
#include "ofxLayerMask.h"  // used for applying stencil masks
#include "ofxHistoryPlot.h"// used for plotting (DATA scene)
#include "ofxIniSettings.h"  // used for manipulating .ini files, customized for openFrameworks to an extent
#pragma endregion

#pragma region global functions

// function to convert from GPS time to 
std::string convertGPSTimeToString(unsigned long gpsSeconds) {
	// GPS epoch start date: January 6, 1980
	struct tm gpsEpoch = { 0 };
	gpsEpoch.tm_year = 1980 - 1900; // years since 1900
	gpsEpoch.tm_mon = 0; // January
	gpsEpoch.tm_mday = 6; // 6th day
	gpsEpoch.tm_hour = 0;
	gpsEpoch.tm_min = 0;
	gpsEpoch.tm_sec = 0;

	// Calculate time_t for the GPS epoch
	time_t gpsEpochTime = mktime(&gpsEpoch);

	// Add GPS seconds to the GPS epoch time
	time_t utcTime = gpsEpochTime + gpsSeconds - 18; // Adjust for GPS-UTC offset (currently 18 seconds)

	// Convert to tm structure for local time representation
	struct tm* timeInfo = gmtime(&utcTime);

	// Format as a string: year-month-date HH-MM-SS
	char buffer[20];
	strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", timeInfo);

	return std::string(buffer);
}

#pragma endregion

#pragma region global variables
// global variables to be accessible by all classes 
const time_t GPS_EPOCH_OFFSET = 315964800; // Seconds from Unix epoch (Jan 1, 1970) to GPS epoch (Jan 6, 1980)
const int GPS_UTC_OFFSET = 18; // GPS-UTC offset in seconds (as of now)
bool ASSE_VS_VANE_FLAG = false; // used by the DATA scene and ofApp 
#pragma endregion

# pragma region global singletons 
// mapZoom singleton - a class that manages the mapZoom variable instance to be shared across other classes
class mapZoom {
public:
	// Static method to access the single instance
	static mapZoom& getInstance() {
		static mapZoom instance; // Guaranteed to be destroyed, instantiated on first use.
		return instance;
	}

	// Getter and Setter for the integer
	int getValue() const {
		return value;
	}

	int getValueMax() const {
		return maxValue;
	}

	int getValueMin() const {
		return minValue;
	}

	void setValueMin(int newValue) {
		minValue = newValue;
	}

	void setValueMax(int newValue) {
		maxValue = newValue;
	}

	void setValue(int newValue) {
		value = ofClamp(newValue, minValue, maxValue);
	}


private:
	int minValue, maxValue,value; // The integer value

	// Private constructor
	mapZoom() : value(0) {} // Initialize the integer to 0

	// Delete copy constructor and assignment operator to ensure singleton behavior
	mapZoom(const mapZoom&) = delete;
	mapZoom& operator=(const mapZoom&) = delete;
};

// tileCache singleton - a class that manages the tileCache map structure instance to be shared across other classes
class TileCache {
public:
	static TileCache& getInstance() {
		static TileCache instance;
		return instance;
	}

	std::unordered_map<std::string, ofImage>& getCache() {
		return cache;
	}

private:
	TileCache() {}
	std::unordered_map<std::string, ofImage> cache;

	// Delete copy constructor and assignment operator to ensure singleton behavior
	TileCache(const TileCache&) = delete;
	TileCache& operator=(const TileCache&) = delete;
};
#pragma endregion

#pragma region global enum
// enum is defined in the global scope to declare it once and make it available to all classes inside this file
enum MeasurementSystem {METRIC, IMPERIAL};
enum Scene {PFD, MAP, DATA};
#pragma endregion

#pragma region Input Data structures

// inputData is a generic struct that holds the input name, the value (read from the OBC outputs), min and max values as well as the method for updating the value (ofNoise, using Perlin noise algorithm)
struct inputData {

	// Member variables
	std::string name;
	float value;
	float min;
	float max;

	std::string gpsWeek;

	// Constructor
	inputData(const std::string& n, float val, float min, float max) : name(n), value(val), min(min), max(max) {}

	//Member function: used to display the values of each input data (for debugging)
	void display() const {
		std::cout << "Name: " << name << std::endl;
		std::cout << "Value: " << value << std::endl;
		std::cout << "Min: " << min << std::endl;
		std::cout << "Max: " << max << std::endl;
	}

	// Memeber function: fake noise (used for testing/simulating inputs updated with a certain pattern)
	 float updateFakeNoise(float elapsedTime, float duration)  
	{
		 float noiseValue = ofNoise(fmod(elapsedTime*0.1,duration*0.1));
		 
		 value = ofMap(noiseValue, 0, 1, min, max);


		 return this->value;
	}

	 float updateFakeNoiseRemapped(float elapsedTime, float duration, float mapMin, float mapMax)
	 {
		 float noiseValue = ofNoise(fmod(elapsedTime * 0.1, duration * 0.1));

		 value = ofMap(noiseValue, 0, 1, mapMin, mapMax);

		 return this->value;
	 }
};

struct inputGPSTime {

	std::string name;
	unsigned long value;
	unsigned long min;
	unsigned long max;

	std::string gpsWeek;

	// Constructor
	inputGPSTime(const std::string& n, unsigned long val, unsigned long min, unsigned long max) : name(n), value(val), min(min), max(max) {}

	//Member function: used to display the values of each input data (for debugging)
	void display() const {
		std::cout << "Name: " << name << std::endl;
		std::cout << "Value: " << value << std::endl;
		std::cout << "Min: " << min << std::endl;
		std::cout << "Max: " << max << std::endl;
	}

	// Memeber function: fake noise (used for testing/simulating inputs updated with a certain pattern)
	unsigned long updateFakeNoise(float elapsedTime, float duration)
	{
		float noiseValue = ofNoise(fmod(elapsedTime * 0.1, duration * 0.1));

		value = ofMap(noiseValue, 0, 1, min, max);

		return this->value;
	}



};
// INPUTS is the container of all the inputs, defined as inputData, that are used for the avionic display in some way. 
struct INPUTS
{

#pragma region members declaration
	//declaration of members of this structure
	//inputData ADC_OUT_GPS_TIME; <-- TBD 
	inputGPSTime ADC_OUT_GPS_TIME;
	inputData ADC_OUT_OAT;
	inputData ADC_OUT_STATICPRESS;
	inputData ADC_OUT_PRESSALT;
	inputData ADC_OUT_DYNPRESS;
	inputData ADC_OUT_INDPRESSALT;
	inputData ADC_OUT_IAS;
	inputData ADC_OUT_CAS;
	inputData ADC_OUT_TAS;
	inputData ADC_OUT_AOA;
	inputData ADC_OUT_AOS;
	inputData ADC_OUT_AOA_VANE;
	inputData ADC_OUT_AOS_VANE;
	inputData ADC_OUT_ROLL;
	inputData ADC_OUT_PITCH;
	inputData ADC_OUT_YAW;
	inputData ADC_OUT_TASDOT;
	inputData ADC_OUT_VERTICAL_SPEED;
	inputData ADC_OUT_MAGHEADING;
	inputData ADC_OUT_MAGPITCH;
	inputData ADC_OUT_MAGROLL;
	inputData ADC_OUT_ROLLRATE;
	inputData ADC_OUT_PITCHRATE;
	inputData ADC_OUT_YAWRATE;
	inputData ADC_OUT_NX;
	inputData ADC_OUT_NY;
	inputData ADC_OUT_NZ;
	inputData ADC_OUT_LAT;
	inputData ADC_OUT_LON;
	inputData ADC_OUT_HGPS;
	inputData ADC_OUT_VN;
	inputData ADC_OUT_VE;
	inputData ADC_OUT_VU;
	inputData ADC_OUT_STATICPRESS_LOC;
	inputData ADC_OUT_DYNPRESS_LOC;

#pragma endregion

	// Initializer (Name, value, min, max). Values are initialized all at 0 and are updated from the first cycle onward via readings from OBC outputs.
	INPUTS()
		: ADC_OUT_OAT("Outside Air Temperature", 0, -70, 70), // Celsius
		ADC_OUT_STATICPRESS("Outside Static Pressure", 0, 22600, 108000), //Pa
		ADC_OUT_PRESSALT("Pressure Altitude", 0, -300, 14000), // Pa
		ADC_OUT_INDPRESSALT("Indicated Altitude", 0, -300, 14000), // m
		ADC_OUT_DYNPRESS("Dynamic Pressure", 0, 0, 6400),
		ADC_OUT_IAS("Indicated Air Speed", 0, 0, 237), //m/s
		ADC_OUT_CAS("Cadicated Air Speed", 0, 0, 155),
		ADC_OUT_TAS("True Airspeed", 0, 0, 237),  // m/s
		ADC_OUT_AOA("Angle of Attack from ASSE", 0, -20, 24),  // deg
		ADC_OUT_AOS("Angle of Sideslip from ASSE", 0, -20, 24),  // deg
		ADC_OUT_AOA_VANE("Angle of Attack from vane", 0, -90, 90),  // deg
		ADC_OUT_AOS_VANE("Angle of Sideslip from vane", 0, -90, 90),  // deg
		ADC_OUT_ROLL("Roll angle", 0, -180, 180),  // deg
		ADC_OUT_PITCH("Pitch angle", 0, -90, 90),  // deg
		ADC_OUT_YAW("Yaw angle", 0, -180, 180),  // deg
		ADC_OUT_TASDOT("TAS Time Derivative", 0, -50, 50),  // m/s^2
		ADC_OUT_VERTICAL_SPEED("Vertical Speed", 0, -50, 50),  // m/s
		ADC_OUT_MAGHEADING("Magnetic Heading", 0, 0, 360),  // deg
		ADC_OUT_MAGPITCH("Magnetic Pitch", 0, 0, 360),  // deg
		ADC_OUT_MAGROLL("Magnetic Roll", 0, 0, 360),  // deg
		ADC_OUT_ROLLRATE("Angular Velocity X", 0, -400, 400),  // deg/s
		ADC_OUT_PITCHRATE("Angular Velocity Y", 0, -400, 400),  // deg/s
		ADC_OUT_YAWRATE("Angular Velocity Z", 0, -400, 400),  // deg/s
		ADC_OUT_NX("Body accelleration X", 0, -98.1, 98.1), // m/s^2
		ADC_OUT_NY("Body accelleration Y", 0, -98.1, 98.1), // m/s^2
		ADC_OUT_NZ("Body accelleration Z", 0, -98.1, 98.1), // m/s^2
		ADC_OUT_LAT("Latitude", 0, -90, 90),  // deg
		ADC_OUT_LON("Longitude", 0, -180, 180),  // deg
		ADC_OUT_HGPS("GPS Height", 0, -1000, 22000),  // m
		ADC_OUT_VN("GPS Velocity North", 0, -300, 300),  // m/s
		ADC_OUT_VE("GPS Velocity East", 0, -300, 300),  // m/s
		ADC_OUT_VU("GPS Velocity Up", 0, -300, 300),  // m/s
		ADC_OUT_STATICPRESS_LOC("Raw Static Pressure", 0, 22600, 108000),
		ADC_OUT_DYNPRESS_LOC("Raw Dynamic Pressure", 0, 0, 6000),
		ADC_OUT_GPS_TIME("GPS Time", 0, 0, 1609024062) 

	

	// The update method calls all the member update methods. Note that some randomize parameters for certain members are scaled down to soften the update speed
{}void update(float elapsedTime, float duration = 5)
	{
		ADC_OUT_OAT.updateFakeNoise(elapsedTime, duration);
		ADC_OUT_STATICPRESS.updateFakeNoise(elapsedTime * 0.1, duration * 0.1);
		ADC_OUT_PRESSALT.updateFakeNoise(elapsedTime, duration);
		ADC_OUT_INDPRESSALT.updateFakeNoise(elapsedTime*0.1, duration*0.1);
		ADC_OUT_DYNPRESS.updateFakeNoise(elapsedTime * 0.1, duration * 0.1);
		ADC_OUT_CAS.updateFakeNoise(elapsedTime, duration);
		ADC_OUT_IAS.updateFakeNoise(elapsedTime, duration);
		ADC_OUT_TAS.updateFakeNoise(elapsedTime, duration);
		ADC_OUT_AOA.updateFakeNoise(elapsedTime, duration);
		ADC_OUT_AOS.updateFakeNoise(elapsedTime, duration);
		ADC_OUT_AOA_VANE.updateFakeNoise(elapsedTime, duration);
		ADC_OUT_AOS_VANE.updateFakeNoise(elapsedTime, duration);
		ADC_OUT_ROLL.updateFakeNoise(elapsedTime, duration);
		ADC_OUT_PITCH.updateFakeNoise(elapsedTime, duration);
		ADC_OUT_YAW.updateFakeNoise(elapsedTime, duration);
		ADC_OUT_TASDOT.updateFakeNoise(elapsedTime, duration);
		ADC_OUT_VERTICAL_SPEED.updateFakeNoise(elapsedTime*0.1, duration*0.1);
		ADC_OUT_MAGHEADING.updateFakeNoise(elapsedTime, duration);
		ADC_OUT_MAGPITCH.updateFakeNoise(elapsedTime, duration);
		ADC_OUT_MAGROLL.updateFakeNoise(elapsedTime, duration);
		ADC_OUT_ROLLRATE.updateFakeNoise(elapsedTime, duration);
		ADC_OUT_PITCHRATE.updateFakeNoise(elapsedTime, duration);
		ADC_OUT_YAWRATE.updateFakeNoise(elapsedTime, duration);
		ADC_OUT_NX.updateFakeNoise(elapsedTime, duration);
		ADC_OUT_NY.updateFakeNoiseRemapped(elapsedTime, duration,-1, 1);
		ADC_OUT_NZ.updateFakeNoise(elapsedTime, duration);
		ADC_OUT_LAT.updateFakeNoise(elapsedTime*1, duration*1);
		ADC_OUT_LON.updateFakeNoise(elapsedTime*1, duration*1);
		ADC_OUT_HGPS.updateFakeNoise(elapsedTime*0.05, duration*0.05);
		ADC_OUT_VN.updateFakeNoise(elapsedTime, duration);
		ADC_OUT_VE.updateFakeNoise(elapsedTime, duration);
		ADC_OUT_VU.updateFakeNoise(elapsedTime, duration);
		ADC_OUT_STATICPRESS_LOC.updateFakeNoise(elapsedTime, duration);
		ADC_OUT_DYNPRESS_LOC.updateFakeNoise(elapsedTime, duration);
		ADC_OUT_GPS_TIME.updateFakeNoise(elapsedTime, duration);
	}

};

#pragma endregion

// These classes are used inside the PFD scene. They are defined separately as groups of indicators for encapsulating and avoiding complexity. 
#pragma region PFD_Classes

// Attitude Indicator class
class AttitudeIndicator 
{

public:

	#pragma region variables declaration
		// variables / parameters that will be used as reference for all graphical objects inside this class
		float HorizonlineLenght;
		float rectWidth; // sky and ground boxes
		float rectHeight; //sky and ground boxes

		float centerX;
		float centerY;

		float rollIndicatorRadius;
		float defaultRollIndicatorRadius; //this will be used to store the default radius in order to scale other objects depending on the roll radius read from the INI file

		float slipIndicatorWidth;
		float slipIndicatorHeight;
		float slipBallRadius;

		std::string ladderText = "";

		// variables that will be calculated based on the INPUT_DATA and used to animate the graphical object of this class
		float ladderTranslation;
		float rotation;
		float slipMaxDisp;
		float slipDisplacement;

		// mask objects that will be used to define and implement masks for certain elements inside this class
		ofxLayerMask maskPitchLadder;
		ofxLayerMask maskRollIndicator;

		// font objects to be loaded by this class
		ofTrueTypeFont fontSmall;
		ofTrueTypeFont fontMedium;
		ofTrueTypeFont fontLarge;

		// INI loader and parser object, used to read ini configurations from the file placed in ./bin/data
		ofxIniSettings settings;

		// variables that will be initialized based on the INI parsing, represting % of screen width or height or hex values for colors. 
		float _CENTER_X;
		float _CENTER_Y;
		float _ROLL_INDICATOR_RADIUS;

		ofColor _SKY_COLOR;
		ofColor _GROUND_COLOR;
		ofColor _WATERLINE_COLOR;
		ofColor _LADDER_TEXT_COLOR;
	#pragma endregion
	
	AttitudeIndicator()
	{
		// Initialization of default parameters and varibles
		centerX = ofGetWidth() *0.5f;
		centerY = ofGetHeight() *0.333f;
		rollIndicatorRadius = ofGetHeight() * 0.25;
		defaultRollIndicatorRadius = rollIndicatorRadius; //default roll indicator radius, used for scaling in case it's changed via .ini
		HorizonlineLenght = ofGetWidth() * 2;
		rectWidth = ofGetWidth()*2;
		rectHeight = ofGetWidth() * 2;

		// Inizialization of variables to be used in animating the graphical objects
		ladderTranslation = 0;
		rotation = 0;
		slipDisplacement = 0;

		// Slip Indicator variables initialized as a % of the rollIndicatorRadius
		slipIndicatorWidth = rollIndicatorRadius * 0.7f;
		slipIndicatorHeight = rollIndicatorRadius * 0.15f;
		slipBallRadius = slipIndicatorHeight * 0.48;
		slipMaxDisp = (slipIndicatorWidth * 0.5f) - slipBallRadius;

		// .ini variables initialized with default values (percentages that will be applied to the screen width and height)
		_CENTER_X = 0.5;
		_CENTER_Y = 0.333f;
		_ROLL_INDICATOR_RADIUS = 0.25;

		// Definition of relevant colors 
		_LADDER_TEXT_COLOR = ofColor::white;
		_SKY_COLOR = ofColor::blue;
		_GROUND_COLOR = ofColor::brown;
		_WATERLINE_COLOR = ofColor::yellow;
	}

	void loadIni()
	{
		settings.load(ofToDataPath("settings.ini"));

		string section = "ATTITUDE INDICATOR";	

		_CENTER_X = settings.getFloat(section, "CENTER_X");
		_CENTER_Y = settings.getFloat(section, "CENTER_Y");
		_ROLL_INDICATOR_RADIUS = settings.getFloat(section, "ROLL_INDICATOR_RADIUS");
	
	}

	void setupFont()
	{
		float textScale = rollIndicatorRadius / defaultRollIndicatorRadius;

		fontSmall.load(ofToDataPath("fonts/Nord-Regular.ttf"), 12* textScale, true, true);
		
		fontMedium.load(ofToDataPath("fonts/Nord-Regular.ttf"), 14 * textScale, true, true);
		
		fontLarge.load(ofToDataPath("fonts/Nord-Regular.ttf"), 20 * textScale, true, true);
	}
	
	void setup()

	{
		maskPitchLadder.setup(ofGetWidth(), ofGetHeight());
		maskPitchLadder.newLayer();

		maskRollIndicator.setup(ofGetWidth(), ofGetHeight());
		maskRollIndicator.newLayer();

		setupFont();

	}

	void update(const INPUTS& INPUT_DATA) 
	{

		//updates with the Ini file reading
		centerX = _CENTER_X * SCREEN_WIDTH;
		centerY = _CENTER_Y * SCREEN_HEIGHT;
		rollIndicatorRadius = _ROLL_INDICATOR_RADIUS * SCREEN_HEIGHT;

		//Inpud Data updates
		ladderTranslation = ofClamp(INPUT_DATA.ADC_OUT_PITCH.value, INPUT_DATA.ADC_OUT_PITCH.min, INPUT_DATA.ADC_OUT_PITCH.max) * PITCH_DEG_TO_PIXEL;
		slipDisplacement = ofClamp(INPUT_DATA.ADC_OUT_NY.value, INPUT_DATA.ADC_OUT_NY.min, INPUT_DATA.ADC_OUT_NY.max) * slipMaxDisp;
		rotation = ofClamp(INPUT_DATA.ADC_OUT_ROLL.value, INPUT_DATA.ADC_OUT_ROLL.min, INPUT_DATA.ADC_OUT_ROLL.max);

	}

	void drawWaterLine() {

		//draw plane symbol at the center of the current coordinate system;
		ofSetColor(_WATERLINE_COLOR);

		ofDrawTriangle(0, 0, rollIndicatorRadius * 0.5, rollIndicatorRadius * 0.15, rollIndicatorRadius * 0.3, rollIndicatorRadius * 0.15);
		ofDrawTriangle(0, 0, -rollIndicatorRadius * 0.5, rollIndicatorRadius * 0.15, -rollIndicatorRadius * 0.3, rollIndicatorRadius * 0.15);

		//draw outline
		ofSetColor(ofColor::black);
		ofNoFill();
		ofSetLineWidth(1);
		ofDrawTriangle(0, 0, rollIndicatorRadius * 0.5, rollIndicatorRadius * 0.15, rollIndicatorRadius * 0.3, rollIndicatorRadius * 0.15);
		ofDrawTriangle(0, 0, -rollIndicatorRadius * 0.5, rollIndicatorRadius * 0.15, -rollIndicatorRadius * 0.3, rollIndicatorRadius * 0.15);
		ofFill();

		//draw lateral lines
		ofSetColor(ofColor::black);
		ofSetLineWidth(7);

		ofDrawLine(-rollIndicatorRadius * 0.95 -1, 0, -rollIndicatorRadius * 0.5+1, 0);
		ofDrawLine(rollIndicatorRadius * 0.95+1, 0, rollIndicatorRadius * 0.5-1, 0);


		ofSetColor(_WATERLINE_COLOR);
		ofSetLineWidth(5);
		ofDrawLine(-rollIndicatorRadius*0.95, 0, -rollIndicatorRadius * 0.5, 0);
		ofDrawLine(rollIndicatorRadius*0.95, 0, rollIndicatorRadius * 0.5, 0);
		
	}

	void drawPitchScale()

	{
		float lineWidth10s = rollIndicatorRadius * 0.505f;

		ofSetColor(_LADDER_TEXT_COLOR);
		ofSetLineWidth(2);

		// for loops iterating throught the range -90 - 90 to draw the whole scale

		// 10s, with text box, large lines;
		for (int i = -9; i < 10; i++) 
		{

			if(i==0){}

			else {

				ofDrawLine(-lineWidth10s * 0.5, -i * 10 * PITCH_DEG_TO_PIXEL, lineWidth10s * 0.5, -i * 10 * PITCH_DEG_TO_PIXEL);

				ladderText = std::to_string(abs(i * 10));
				fontSmall.drawString(ladderText, -lineWidth10s * 0.5 -fontSmall.stringWidth(ladderText)*1.2, -i * 10 * PITCH_DEG_TO_PIXEL + fontSmall.stringHeight(ladderText) * 0.5);//rollIndicatorRadius * 0.0035f);
				fontSmall.drawString(ladderText, lineWidth10s * 0.5 + fontSmall.stringWidth(ladderText) * 0.2 , -i * 10 * PITCH_DEG_TO_PIXEL + fontSmall.stringHeight(ladderText) * 0.5);//rollIndicatorRadius * 0.0035f);
			}
		}

		// 5s, without text box, medium lines;
		for (int i = -17; i < 18; i+=2) 
		{
			ofDrawLine(-lineWidth10s*0.25f, -i * 5 * PITCH_DEG_TO_PIXEL, +lineWidth10s * 0.25f, -i * 5 * PITCH_DEG_TO_PIXEL);
		}

		// 2.5s, without text box, short lines;
		for (int i = -35; i < 36; i = i + 2)
		{
			ofDrawLine(-lineWidth10s * 0.125f, -i * 2.5 * PITCH_DEG_TO_PIXEL, lineWidth10s * 0.125f, -i * 2.5 * PITCH_DEG_TO_PIXEL);
		}

	}

	void drawPitchScaleMask()
	{
		float lineY;

		ofPushMatrix();

		float circleX = 0;
		float circleY = 0;
		float circleRadius = rollIndicatorRadius * 0.908f; 
	
		lineY = circleRadius*0.5;

		float dx = sqrt(circleRadius * circleRadius - (lineY - circleY) * (lineY - circleY));

		float angle1 = atan2(lineY - circleY, -dx);
		float angle2 = atan2(lineY - circleY, dx);

	
		float startAngle, endAngle;
		if (angle1 < angle2) {
			startAngle = angle1;
			endAngle = angle2;
		}
		else {
			startAngle = angle2;
			endAngle = angle1;
		}

		startAngle = ofRadToDeg(startAngle);
		endAngle = ofRadToDeg(endAngle);

		ofPath arc;
		arc.setFillColor(ofColor::blue);
		arc.setStrokeColor(ofColor::white);
		arc.setStrokeWidth(2);
		arc.setArcResolution(100);
		arc.arc(0,0, circleRadius, circleRadius, endAngle, startAngle);

		vector<ofPolyline> polylines = arc.getOutline();
		if (polylines.size() > 0) {
			// Start defining the shape
			ofSetColor(255, 0, 0); // Fill color (red)
			ofBeginShape();

			// Loop through the vertices and add them to the shape
			for (auto& polyline : polylines) {
				for (auto& vertex : polyline) {
					ofVertex(vertex);
				}
			}

			// End defining the shape
			ofEndShape(true); // Close the shape
		}

		ofPopMatrix();
			
	}

	void drawMaskedPitchScale()
	{

		maskPitchLadder.beginLayer();
		ofClear(0, 0, 0, 0);
		
		ofPushMatrix();
		ofTranslate(centerX, centerY);
		ofRotateZDeg(rotation);
		ofTranslate(0, -ladderTranslation);

		drawPitchScale();

		ofPopMatrix();

		maskPitchLadder.endLayer();

		maskPitchLadder.beginMask();
		ofClear(0, 0, 0, 0);

		ofPushMatrix();
		ofTranslate(centerX, centerY);

		drawPitchScaleMask();

		ofPopMatrix();

		maskPitchLadder.endMask();

		maskPitchLadder.draw(0, 0);

	}

	void drawRollIndicator(float startangle = 210, float endangle = 330)
	{
		//Draw the roll indicator centered at the origin of the current transormation matrix

		float angle1 = startangle;
		float angle2 = endangle;

		if (rotation >= 60) {

			angle1 = startangle - (rotation - 60);
		}

		else if (rotation < -60) {

			angle2 = endangle + abs(rotation) - 60;
		}

		else {};

		//Draw the arc
		ofPath arc;
		arc.setFillColor(ofColor(0, 0));
		arc.setStrokeColor(ofColor::white);
		arc.setStrokeWidth(2);
		arc.setArcResolution(100);
		arc.arc(0, 0, rollIndicatorRadius, rollIndicatorRadius, angle1, angle2);
		arc.draw();

		auto drawArcLines = [&] (float lenght, float thickness, float angle) {
			// draw the lines (edge of arc, right to left)

			float anglePosition = ofDegToRad(angle + startangle);
			float arcX = rollIndicatorRadius * cos(anglePosition);
			float arcY = rollIndicatorRadius * sin(anglePosition);
			float perpAngle = anglePosition;

			float lineX2 = arcX + lenght * cos(perpAngle);
			float lineY2 = arcY + lenght * sin(perpAngle);
		
			ofSetColor(ofColor::white);
			ofSetLineWidth(thickness);
			ofDrawLine(arcX, arcY, lineX2, lineY2);

		};
		
		drawArcLines(rollIndicatorRadius*0.175, 2, 0);
		drawArcLines(rollIndicatorRadius*0.175, 2, 120);
		drawArcLines(rollIndicatorRadius * 0.1f, 2, 60);
		drawArcLines(rollIndicatorRadius*0.175, 2, 30);
		drawArcLines(rollIndicatorRadius*0.175, 2, 90);

		drawArcLines(rollIndicatorRadius*0.1f, 2, 40);
		drawArcLines(rollIndicatorRadius*0.1f, 2, 50);
		drawArcLines(rollIndicatorRadius*0.1f, 2, 70);
		drawArcLines(rollIndicatorRadius*0.1f, 2, 80);
		drawArcLines(rollIndicatorRadius*0.1f, 2, 15);
		drawArcLines(rollIndicatorRadius*0.1f, 2, 105);

		//draw Roll upper triangle indicator (moves with the arc)

		ofVec2f fixedV1(0, -rollIndicatorRadius);
		ofVec2f fixedV2(-rollIndicatorRadius * 0.05f, -rollIndicatorRadius - rollIndicatorRadius * 0.1f);
		ofVec2f fixedV3(rollIndicatorRadius * 0.05f, -rollIndicatorRadius - rollIndicatorRadius * 0.1f);

		ofSetColor(ofColor::white);
		ofDrawTriangle(fixedV1, fixedV2, fixedV3);

		//draw Roll lower triangle indicator (stays fixed)
		ofPushMatrix();
		ofRotateZDeg(-rotation);

		ofVec2f V1(0, -rollIndicatorRadius);
		ofVec2f V2(-rollIndicatorRadius * 0.05f, -rollIndicatorRadius + rollIndicatorRadius * 0.1f);
		ofVec2f V3(rollIndicatorRadius * 0.05f, -rollIndicatorRadius + rollIndicatorRadius * 0.1f);

		ofSetColor(ofColor::white);
		ofDrawTriangle(V1, V2, V3);

		ofPopMatrix();
	}

	void drawMaskedRollIndicator()
	{
		maskRollIndicator.beginLayer();

		ofClear(0, 0, 0, 0);

		ofPushMatrix();
		ofTranslate(centerX, centerY);
		ofRotateZDeg(rotation);

		drawRollIndicator();

		ofPopMatrix();

		maskRollIndicator.endLayer();

		maskRollIndicator.beginMask();

		ofClear(0, 0, 0, 0);
		ofPushMatrix();
		ofTranslate(centerX, centerY);

		ofDrawRectangle(-rollIndicatorRadius * 1.1, -rollIndicatorRadius * 1.1, rollIndicatorRadius * 2 * 1.2, rollIndicatorRadius * 2);

		ofPopMatrix();

		maskRollIndicator.endMask();

		maskRollIndicator.draw(0, 0);


	}

	void drawSlipIndicator()
	{		

		ofPushMatrix();
		ofTranslate(0, rollIndicatorRadius * 0.52);

		ofSetColor(ofColor::black, 40);
		ofSetLineWidth(2);

		ofDrawRectRounded(-slipIndicatorWidth * 0.5f, 0, slipIndicatorWidth, slipIndicatorHeight, 10);

		ofNoFill();
		ofSetColor(ofColor::white);
		ofDrawRectRounded(-slipIndicatorWidth * 0.5f, 0, slipIndicatorWidth, slipIndicatorHeight, 10);
		ofFill();

		ofPopMatrix();

		ofPushMatrix();
		ofTranslate(0,rollIndicatorRadius*0.52 + slipIndicatorHeight * 0.5f);

		ofTranslate(slipDisplacement, 0);
		ofSetColor(ofColor::white);
		ofNoFill();
		ofDrawCircle(0, 0, slipBallRadius);
		ofPopMatrix();

		ofFill();

		ofPushMatrix();
		ofTranslate(0, rollIndicatorRadius * 0.52);

		ofDrawLine(-slipMaxDisp*0.5, 0, -slipMaxDisp * 0.5, slipIndicatorHeight);
		ofDrawLine(slipMaxDisp * 0.5, 0, slipMaxDisp * 0.5, slipIndicatorHeight);
		ofDrawLine(0, 0, 0, slipIndicatorHeight *0.3);
		ofDrawLine(0, slipIndicatorHeight *0.7, 0, slipIndicatorHeight);

		ofPopMatrix();

	}

	void drawAll() {
		
		ofPushMatrix();

		//set reference system to the center of the AI (1/3 down from the top of the display)
		ofTranslate(centerX, centerY);
		
		ofPushMatrix();
	
		//apply rotation and tranlsation depending on inputs
		ofRotateZDeg(rotation);
		ofTranslate(0, - ladderTranslation);
		
		//draw sky and ground box
		ofSetColor(_SKY_COLOR);
		ofDrawRectangle(-rectWidth / 2, -rectHeight, rectWidth, rectHeight);
		ofSetColor(_GROUND_COLOR);
		ofDrawRectangle(- rectWidth/2, 0, rectWidth, rectHeight);
		
		//draw horizon line (white)
		ofSetColor(255);
		ofSetLineWidth(2);
		ofDrawLine(-HorizonlineLenght / 2, 0, HorizonlineLenght / 2, 0);

		ofPopMatrix(); //restores the matrix before the translation and rotation
		
		drawSlipIndicator();

		ofPopMatrix(); //restores the original matrix (0,0) screen coordinates

		drawMaskedPitchScale();
		drawMaskedRollIndicator();

		ofPushMatrix();

		ofTranslate(centerX, centerY);
		drawWaterLine();

		ofPopMatrix();
	}

};

// Compass class
class Compass {

public:

#pragma region variables declarations
	float centerX;
	float centerY;

	float radius;
	float rotation;
	std::string CompassText;
	std::string magHeading;
	ofPath planeIcon;

	ofTrueTypeFont fontSmall;
	ofTrueTypeFont fontMedium;
	ofTrueTypeFont fontLarge;

	float _CENTER_X;
	float _CENTER_Y;

	float _COMPASS_RADIUS;
	
	ofColor _COMPASS_TEXT_COLOR;
	ofColor _PLANE_ICON_COLOR;
	ofColor _HDG_INDICATOR_TEXT_COLOR;
	ofColor _HDG_INDICATOR_BG_COLOR;

	float defaultCompassRadius;

	ofxIniSettings settings;
#pragma endregion

	Compass() {

		// Initialization of default parameters and varibles
		centerX = ofGetWidth() *0.5f;
		centerY = ofGetHeight() * 0.8;
		radius = ofGetHeight() * 0.185;
		CompassText = "";

		// Inizialization of variables to be used in animating the graphical objects
		rotation = 0;

		defaultCompassRadius = radius; //used for scaling dynamically

		// .ini variables initialized with default values (percentages that will be applied to the screen width / height)
		_CENTER_X = 0.5f;
		_CENTER_Y = 0.785f;
		_COMPASS_RADIUS = 0.198f;

		// defintion of relevant colors
		_COMPASS_TEXT_COLOR = ofColor::white;
		_PLANE_ICON_COLOR = ofColor::white;
		_HDG_INDICATOR_TEXT_COLOR = ofColor::white;
		_HDG_INDICATOR_BG_COLOR = ofColor::black;
	}

	void setupFont()
	{
		float textScale = radius / defaultCompassRadius;

		fontSmall.load(ofToDataPath("fonts/Orbitron-Regular.ttf"), 12*textScale, true, true);
		fontMedium.load(ofToDataPath("fonts/Orbitron-Regular.ttf"), 14*textScale, true, true);
		fontLarge.load(ofToDataPath("fonts/Orbitron-Regular.ttf"), 20*textScale, true, true);

	}

	void loadIni()
	{
		settings.load(ofToDataPath("settings.ini"));

		string section = "NAV";

		_CENTER_X = settings.getFloat(section, "CENTER_X");
		_CENTER_Y = settings.getFloat(section, "CENTER_Y");
		_COMPASS_RADIUS = settings.getFloat(section, "COMPASS_RADIUS");
	}

	void setup()
	{
		setupFont();

		float dimension = ofGetHeight()*0.05;

		planeIcon.setColor(_PLANE_ICON_COLOR);


		ofPoint point0 (0, -dimension / 2.0f);
		ofPoint point1 (dimension / 10.0f, -dimension / 2.0f + dimension / 10.0f);
		ofPoint point2 (dimension / 10.0f, -dimension / 2.0f + dimension / 10.0f + dimension / 10.0f + 1);
		ofPoint point3 (dimension / 10.0f + dimension / 3.0f + dimension / 15.0f, -dimension / 2.0f + dimension / 10.0f + dimension / 10.0f + 1 + dimension / 10.0f);
		ofPoint point4 (dimension / 10.0f + dimension / 3.0f + dimension / 15.0f, -dimension / 2.0f + dimension / 10.0f + dimension / 10.0f + 1 + dimension / 10.0f + dimension/15.0f);
		ofPoint point5 (dimension / 10.0f, -dimension / 2.0f + dimension / 10.0f + dimension / 10.0f + 1 + dimension / 10.0f + dimension / 15.0f);
		ofPoint point6 (dimension / 10.0f, -dimension / 2.0f + dimension / 10.0f + dimension / 10.0f + 1 + dimension / 10.0f + dimension / 15.0f + dimension / 7.5f);
		ofPoint point7 (dimension / 10.0f + dimension / 7.5f, -dimension / 2.0f + dimension / 10.0f + dimension / 10.0f + 1 + dimension / 10.0f + dimension / 15.0f + dimension / 7.5f + dimension / 15.0f);
		ofPoint point8 (dimension / 10.0f + dimension / 7.5f, -dimension / 2.0f + dimension / 10.0f + dimension / 10.0f + 1 + dimension / 10.0f + dimension / 15.0f + dimension / 7.5f + dimension / 15.0f + dimension/15.0f);

		ofPoint point9(-point8.x, point8.y);
		ofPoint point10(-point7.x, point7.y);
		ofPoint point11(-point6.x, point6.y);
		ofPoint point12(-point5.x, point5.y);
		ofPoint point13(-point4.x, point4.y);
		ofPoint point14(-point3.x, point3.y);
		ofPoint point15(-point2.x, point2.y);
		ofPoint point16(-point1.x, point1.y);


		planeIcon.moveTo(point0);
		planeIcon.lineTo(point1);
		planeIcon.lineTo(point2);
		planeIcon.lineTo(point3);
		planeIcon.lineTo(point4);
		planeIcon.lineTo(point5);
		planeIcon.lineTo(point6);
		planeIcon.lineTo(point7);
		planeIcon.lineTo(point8);
		planeIcon.lineTo(point9);
		planeIcon.lineTo(point10);
		planeIcon.lineTo(point11);
		planeIcon.lineTo(point12);
		planeIcon.lineTo(point13);
		planeIcon.lineTo(point14);
		planeIcon.lineTo(point15);
		planeIcon.lineTo(point16);
		
		planeIcon.close();

	}

	void update(const INPUTS& INPUT_DATA)

	{
		// updates based on ini readings
		centerX = _CENTER_X * SCREEN_WIDTH;
		centerY = _CENTER_Y * SCREEN_HEIGHT;
		radius = _COMPASS_RADIUS * SCREEN_HEIGHT;

		//updates inputs
		rotation = ofClamp(INPUT_DATA.ADC_OUT_MAGHEADING.value, 0, 360);

	}

	void drawCompass() {

		ofPushMatrix(); // First Push
		ofRotateZDeg(-rotation);

		//draw compass circle  ********************************************************
		ofSetColor(ofColor::slateGrey,80);
		ofSetLineWidth(2);

		ofDrawCircle(0, 0, radius);
		//*****************************************************************************

		//draw external lines (90, 270)
		ofSetLineWidth(3);
		ofSetColor(ofColor::white);
		
		ofDrawLine(-radius - radius * 0.126f, 0, -radius - 1, 0); // 270
		ofDrawLine(radius + 1, 0, radius + radius * 0.126f, 0); // 90
		//*****************************************************************************

		//draw external lines (45, 135, 225, 315) *************************************
		ofSetLineWidth(2);
		ofSetColor(ofColor::white);

		ofPushMatrix();
		ofRotateZDeg(45);
		for (int i = 0; i < 4; i++)
		{
			ofRotateZDeg(-i * 90);
			ofDrawLine(0, radius + 1, 0, radius + 20);
			ofRotateZDeg(i * 90);
		}
		ofPopMatrix();
		//*****************************************************************************

		//draw internal lines: tens, number every 30 degrees, letters for N,E,S,W *****
		ofSetLineWidth(2);
		ofSetColor(ofColor::white);

		ofPushMatrix(); 

		for (int i = 0; i < 36; i++)
		{
			ofRotateZDeg(i*10);

			
			ofDrawLine(0, -radius + 1, 0, -radius + radius * 0.126f);

			ofSetColor(_COMPASS_TEXT_COLOR);

			
				

			if (i % 3 == 0)
			{
			
				

				switch (i)
				{
					

				case 0 : 

					CompassText = "N";

					ofPushMatrix();
					ofTranslate(0, -radius + radius * 0.126f + 1 + fontLarge.stringHeight(CompassText) * 0.5);
					ofRotateZDeg(rotation);

					

					fontLarge.drawString(CompassText, -fontLarge.stringWidth(CompassText) / 2,  fontLarge.stringHeight(CompassText)*0.5);
					ofPopMatrix();

					
					break;

				case 9 :
					CompassText = "E";

					ofPushMatrix();
					ofTranslate(0, -radius + radius * 0.126f + 1 + fontLarge.stringHeight(CompassText) * 0.5);
					ofRotateZDeg(rotation-i*10);
					
					fontLarge.drawString(CompassText, -fontLarge.stringWidth(CompassText) / 2, fontLarge.stringHeight(CompassText) * 0.5);
					ofPopMatrix();
					break;

				case 18 :

					CompassText = "S";
					ofPushMatrix();
					ofTranslate(0, -radius + radius * 0.126f + 1 + fontLarge.stringHeight(CompassText) * 0.5);
					ofRotateZDeg(rotation);
					
					fontLarge.drawString(CompassText, -fontLarge.stringWidth(CompassText) / 2, fontLarge.stringHeight(CompassText) * 0.5);
					ofPopMatrix();
					break;

				case 27 :

					CompassText = "W";
					ofPushMatrix();
					ofTranslate(0, -radius + radius * 0.126f + 1 + fontLarge.stringHeight(CompassText) * 0.5);
					ofRotateZDeg(rotation-i*10);
					
					fontLarge.drawString(CompassText, -fontLarge.stringWidth(CompassText) / 2, fontLarge.stringWidth(CompassText) * 0.5);
					ofPopMatrix();
					break;

				default:

					CompassText = std::to_string(i);
					ofPushMatrix();
					//ofTranslate(0, -radius + radius * 0.126f + 1 + fontMedium.stringHeight(CompassText)*0.5f);
					//ofRotateZDeg(rotation-i*10);
					
					fontMedium.drawString(CompassText, -fontMedium.stringWidth(CompassText) / 2, -radius + radius * 0.126f + 1 + fontMedium.stringHeight(CompassText));
					ofPopMatrix();
					break;

				}
				
				
			}
			
			
			
			ofRotateZDeg(-i * 10);


		}

		ofPopMatrix();
		//*****************************************************************************

		//draw internal lines: 5s *****************************************************

		ofSetLineWidth(2);
		ofSetColor(ofColor::white);

		ofPushMatrix();
		ofRotateZDeg(5);
		for (int i = 0; i < 36; i++)
		{
			ofRotateZDeg(i * 10);
			ofDrawLine(0, -radius + 1, 0, -radius + radius * 0.068f);
			ofRotateZDeg(-i*10);
		}
		ofPopMatrix();
		ofPopMatrix();
		//*****************************************************************************

		//draw Heading indicator (triangle, fixed at the top of the compass ***********

		ofSetColor(ofColor::white);
		ofDrawTriangle(0, -radius + radius * 0.088f, -radius * 0.068f, -radius - radius * 0.068f, radius * 0.068f, -radius - radius * 0.068f);

		//draw the Heading box ********************************************************

		ofSetColor(_HDG_INDICATOR_BG_COLOR);
	
		ofDrawRectangle(-radius*0.6f/2.0f, -radius - radius * 0.07f - radius*0.23f, radius * 0.6f, radius * 0.23f);

		ofNoFill();
		ofSetColor(ofColor::white);
		ofDrawRectangle(-radius * 0.6f / 2.0f, -radius - radius * 0.07f - radius * 0.23f, radius * 0.6f, radius * 0.23f);
		ofFill();

		ofSetColor(_HDG_INDICATOR_TEXT_COLOR);
		magHeading = std::to_string(static_cast<int>(rotation)); 
		fontLarge.drawString(magHeading , -fontLarge.stringWidth(magHeading ) * 0.5f, -radius - radius * 0.07f - radius * 0.23f*0.5 + fontLarge.stringHeight(magHeading)*0.5);
		
		//******************************************************************************

	} 

	void drawPlaneIcon()
	{
		// Draws the plane icon at the center of the current reference system *********

		planeIcon.draw();

	}
	
	void drawAll()

	{
		ofPushMatrix();
		//set reference system to the center of the Compass group
		ofTranslate(centerX, centerY);

		drawCompass();
		ofPushMatrix();
		
		ofTranslate(0, 10);
		ofScale((radius/defaultCompassRadius)*1.2f, (radius / defaultCompassRadius) * 1.7, 1);
		drawPlaneIcon();

		ofPopMatrix();
		ofPopMatrix();
	}

};

// Airspeed Indicator class
class AirspeedIndicator
{
public:

#pragma region variables delcaration
	float centerX;
	float centerY;

	float height;
	float width;

	float aoaBoxHeight;
	float aoaBoxWidth;

	float minAirSpeed;
	float maxAirSpeed;

	float scaleTranslation;

	float indicatedAirSpeed;
	float trueAirSpeed;
	float groundSpeed;
	float tasDot;

	float lineWidth;

	ofTrueTypeFont fontSmall;
	ofTrueTypeFont fontMedium;
	ofTrueTypeFont fontLarge;

	float knotsToPixels;
	float metersToPixels;

	float degToPixels;

	float aoaTranslation;

	MeasurementSystem measurementSystem;

	string airSpeedText = "";
	string aoaText = "";

	ofxLayerMask maskScale;

	float maxNearest10 = std::round(maxAirSpeed);
	float numberOfLines = maxNearest10 * 0.1;
	float maxKnots = 0;

	ofxIniSettings settings;

	float _CENTER_X;
	float _CENTER_Y;

	float _WIDTH;
	float _HEIGHT;

	int _MEASUREMENT_SYSTEM;

	float defaultWidth;
	float defaultHeight;
	
	ofColor _INDICATOR_BG_COLOR;
	ofColor _INDICATOR_TEXT_COLOR;
	ofColor _TAPE_TEXT_COLOR;

#pragma endregion

	AirspeedIndicator()

	{
		measurementSystem = IMPERIAL;
		_MEASUREMENT_SYSTEM = 1;

		centerX = ofGetWidth() * 0.25f;
		centerY = ofGetHeight() *0.333f;

		height = ofGetHeight() * 0.5f; //Roll indicator diameter
		width = ofGetWidth() * 0.085f;

		aoaBoxHeight = height * 0.8;
		aoaBoxWidth = width * 0.35;

		defaultWidth = width;
		defaultHeight = height;

		_CENTER_X = 0.25;
		_CENTER_Y = 0.333f;

		_WIDTH = 0.085f;
		_HEIGHT = 0.5f;

		_INDICATOR_BG_COLOR = ofColor::black;
		_INDICATOR_TEXT_COLOR = ofColor::white;
		_TAPE_TEXT_COLOR = ofColor::white;

		minAirSpeed = 0;
		maxAirSpeed = 237;

		lineWidth = width * 0.25;

		knotsToPixels = height / 60 * 0.95; //ideally it should be /60 to display 60 knots range at all times, but this way it doesn't "cut the numbers" top and bottom
		metersToPixels = height / 30 * 0.95;

		degToPixels = aoaBoxHeight / 44; // range of -20 to 24 degrees for AoA

		scaleTranslation = 0;

		indicatedAirSpeed = 0;
		trueAirSpeed = 0;
		groundSpeed = 0;

	}

	void setupFont()
	{
		float textScale = width / defaultWidth;

		fontSmall.load(ofToDataPath("fonts/Orbitron-Regular.ttf"), 12 * textScale, true, true);
		fontMedium.load(ofToDataPath("fonts/Orbitron-Regular.ttf"), 14 * textScale, true, true);
		fontLarge.load(ofToDataPath("fonts/Orbitron-Regular.ttf"), 18 * textScale, true, true);

	}

	void loadIni()
	{
		settings.load(ofToDataPath("settings.ini"));

		string section = "AIRSPEED INDICATOR";

		_MEASUREMENT_SYSTEM = settings.getInt(section, "MEASUREMENT_SYSTEM");
		measurementSystem = _MEASUREMENT_SYSTEM == 1 ? IMPERIAL : METRIC;

		_CENTER_X = settings.getFloat(section, "CENTER_X");
		_CENTER_Y = settings.getFloat(section, "CENTER_Y");
		_WIDTH = settings.getFloat(section, "WIDTH");
		_HEIGHT = settings.getFloat(section, "HEIGHT");

	}

	void setup()

	{
		setupFont();

		maskScale.setup(ofGetWidth(), ofGetHeight());
		maskScale.newLayer();
	}

	void update(const INPUTS& INPUT_DATA)
	{

		centerX = _CENTER_X * SCREEN_WIDTH;
		centerY = _CENTER_Y * SCREEN_HEIGHT;
		width = _WIDTH * SCREEN_WIDTH;
		height = _HEIGHT * SCREEN_HEIGHT;

		aoaBoxWidth = width * 0.35;
		aoaBoxHeight = height * 0.8;

		knotsToPixels = height / 60 * 0.95; //ideally it should be /60 to display 60 knots range at all times, but this way it doesn't "cut the numbers" top and bottom
		metersToPixels = height / 30 * 0.95;
		degToPixels = aoaBoxHeight / 44; // -20 , 24 range for AoA

		aoaText = ofToString(int(ofClamp(INPUT_DATA.ADC_OUT_AOA_VANE.value, INPUT_DATA.ADC_OUT_AOA_VANE.min, INPUT_DATA.ADC_OUT_AOA_VANE.max)));
		aoaTranslation = ofClamp(INPUT_DATA.ADC_OUT_AOA_VANE.value, -20, 24)* degToPixels;

		switch (measurementSystem)

		{

		case METRIC:

			scaleTranslation = ofClamp(INPUT_DATA.ADC_OUT_IAS.value, INPUT_DATA.ADC_OUT_IAS.min, INPUT_DATA.ADC_OUT_IAS.max) * metersToPixels;
			indicatedAirSpeed = ofClamp(INPUT_DATA.ADC_OUT_IAS.value, INPUT_DATA.ADC_OUT_IAS.min, INPUT_DATA.ADC_OUT_IAS.max);

			trueAirSpeed = ofClamp(INPUT_DATA.ADC_OUT_TAS.value, INPUT_DATA.ADC_OUT_TAS.min, INPUT_DATA.ADC_OUT_TAS.max);
			tasDot = ofClamp(INPUT_DATA.ADC_OUT_TASDOT.value, INPUT_DATA.ADC_OUT_TASDOT.min, INPUT_DATA.ADC_OUT_TASDOT.max);

			groundSpeed = sqrt(pow(ofClamp(INPUT_DATA.ADC_OUT_VN.value, INPUT_DATA.ADC_OUT_VN.min, INPUT_DATA.ADC_OUT_VN.max), 2) + pow(ofClamp(INPUT_DATA.ADC_OUT_VE.value, INPUT_DATA.ADC_OUT_VE.min, INPUT_DATA.ADC_OUT_VE.max), 2));

			break;


		case IMPERIAL:

			scaleTranslation = ofClamp(INPUT_DATA.ADC_OUT_IAS.value, INPUT_DATA.ADC_OUT_IAS.min, INPUT_DATA.ADC_OUT_IAS.max) * knotsToPixels * M_S_TO_KNOTS;
			indicatedAirSpeed = ofClamp(INPUT_DATA.ADC_OUT_IAS.value, INPUT_DATA.ADC_OUT_IAS.min, INPUT_DATA.ADC_OUT_IAS.max) * M_S_TO_KNOTS;

			trueAirSpeed = ofClamp(INPUT_DATA.ADC_OUT_TAS.value, INPUT_DATA.ADC_OUT_TAS.min, INPUT_DATA.ADC_OUT_TAS.max) * M_S_TO_KNOTS;
			tasDot = ofClamp(INPUT_DATA.ADC_OUT_TASDOT.value, INPUT_DATA.ADC_OUT_TASDOT.min, INPUT_DATA.ADC_OUT_TASDOT.max) * M_S_TO_KNOTS;

			groundSpeed = sqrt(pow(ofClamp(INPUT_DATA.ADC_OUT_VN.value, INPUT_DATA.ADC_OUT_VN.min, INPUT_DATA.ADC_OUT_VN.max), 2) + pow(ofClamp(INPUT_DATA.ADC_OUT_VE.value, INPUT_DATA.ADC_OUT_VE.min, INPUT_DATA.ADC_OUT_VE.max), 2)) * M_S_TO_KNOTS;

			break;
		}

	}

	void drawBox()
	{

		ofSetColor(ofColor::black, 100);
		ofDrawRectRounded(-width * 0.5f, -height * 0.5f , width, height,2);

		ofNoFill();
		ofSetColor(ofColor::white);
		ofSetLineWidth(2);
		ofDrawRectRounded(-width * 0.5f, -height * 0.5f, width, height,2);
		ofFill();


		//TAS box
		float tasBoxHeight = height * 0.08;
		ofSetColor(ofColor::black);
		ofDrawRectRounded(-width * 0.5f, - height * 0.5f - tasBoxHeight, width, tasBoxHeight,2);

		ofSetColor(ofColor::white);
		ofNoFill();
		ofDrawRectRounded(-width * 0.5f, - height * 0.5f - tasBoxHeight, width, tasBoxHeight,2);
		ofFill();

		string tasString = ofToString(static_cast<int>(trueAirSpeed));
		string tasLabel = "TAS";

		ofSetColor(ofColor::cyan);
		fontSmall.drawString(tasLabel, -width * 0.48, -height * 0.5 + fontSmall.stringHeight(tasLabel) * 0.5 -tasBoxHeight*0.5);

		ofSetColor(ofColor::white);
		switch (measurementSystem) {

		case (METRIC):

			fontSmall.drawString(tasString + " m/s", width * 0.48 - fontSmall.stringWidth(tasString + " m/s"), - height * 0.5 + fontSmall.stringHeight(tasString + " m/s") * 0.5 - tasBoxHeight*0.5);

			break;

		case (IMPERIAL):

			fontSmall.drawString(tasString + " KT", width * 0.48 - fontSmall.stringWidth(tasString + " KT") , - height * 0.5 + fontSmall.stringHeight(tasString + " KT")*0.5 - tasBoxHeight * 0.5);

				break;
		}

		// ground Speed box
		float gsBoxHeight = height * 0.08;
		ofSetColor(ofColor::black);
		ofDrawRectRounded(-width * 0.5f, height * 0.5f, width, gsBoxHeight, 2);

		ofSetColor(ofColor::white);
		ofNoFill();
		ofDrawRectRounded(-width * 0.5f, height * 0.5f, width, gsBoxHeight, 2);
		ofFill();

		string gsString = ofToString(static_cast<int>(groundSpeed));
		string gsLabel = "GS";

		ofSetColor(ofColor::cyan);
		fontSmall.drawString(gsLabel, -width * 0.48, height * 0.5 + fontSmall.stringHeight(gsLabel) * 0.5 + gsBoxHeight * 0.5);

		ofSetColor(ofColor::white);
		switch (measurementSystem) {

		case (METRIC):

			fontSmall.drawString(gsString + " m/s", width * 0.48 - fontSmall.stringWidth(gsString + " m/s"), height * 0.5 + gsBoxHeight * 0.5 + fontSmall.stringHeight(gsString + " m/s") * 0.5);

			break;

		case (IMPERIAL):

			fontSmall.drawString(gsString + " KT", width * 0.48 - fontSmall.stringWidth(gsString + " KT"), height * 0.5 + gsBoxHeight * 0.5 + fontSmall.stringHeight(gsString + " KT") * 0.5);

			break;
		}

	}

	void drawIndicatorMask()

	{
		string indicatorString = ofToString(static_cast<int>(indicatedAirSpeed));

		ofSetColor(_INDICATOR_BG_COLOR);


		ofDrawRectRounded(-width * 0.5 + 1, -width * 0.25, width - width * 0.125, width * 0.5, 5);
		ofDrawTriangle(width * 0.5 - width * 0.125, width * 0.1, width * 0.5 - width * 0.125, -width * 0.1, width * 0.5, 0);

		ofSetColor(_INDICATOR_TEXT_COLOR);
		fontLarge.drawString(indicatorString, -fontLarge.stringWidth(indicatorString) * 0.5, fontLarge.stringHeight(indicatorString) * 0.5);
	}

	void drawScale() 
	
	{
		//Depending on the desired setting, the scale will be expressed in knots or meters. At any given time, the scale will show a range of 60 knots or 30 m/s
		switch (measurementSystem)
		{
			case(METRIC):

				ofSetLineWidth(2);
				ofSetColor(_TAPE_TEXT_COLOR);

				//draw 10s lines, starting from 0, at center of the current reference system;
			
				 maxNearest10 = std::round(maxAirSpeed);
				 numberOfLines = maxNearest10 * 0.1;

				ofPushMatrix();

				for (int i = 0; i < numberOfLines; i++)

				{
					ofTranslate(0, -i * 10 * metersToPixels, 0);

					ofDrawLine(-lineWidth, 0, 0, 0);

					ofSetColor(_TAPE_TEXT_COLOR);
					airSpeedText = std::to_string(i * 10);
					fontMedium.drawString(airSpeedText, -fontMedium.stringWidth(airSpeedText) - lineWidth * 1.1, fontMedium.stringHeight(airSpeedText) * 0.5);

					ofTranslate(0, i * 10 * metersToPixels, 0);

				}

				ofPopMatrix();

				ofPushMatrix();

				ofTranslate(0, -5 * metersToPixels);

				for (int i = 1; i < numberOfLines; i++)

				{

					airSpeedText = std::to_string(i * 10);

					ofDrawLine(-lineWidth * 0.5, 0, 0, 0);
					ofTranslate(0, -10 * metersToPixels, 0);

				}

				ofPopMatrix();

				break;


			case (IMPERIAL):

				ofSetLineWidth(2);
				ofSetColor(_TAPE_TEXT_COLOR);

				//draw 10s lines, starting from 0, at center of the current reference system;
				 maxKnots = maxAirSpeed * M_S_TO_KNOTS;
				 maxNearest10 = std::round(maxKnots);
				 numberOfLines = maxNearest10 * 0.1;
				
				ofPushMatrix();

				for (int i = 0; i < numberOfLines; i++)

				{
					ofTranslate(0, -i * 10 * knotsToPixels, 0);
					
					airSpeedText = std::to_string(i * 10);

					ofDrawLine(-lineWidth, 0, 0, 0);

					ofSetColor(_TAPE_TEXT_COLOR);
					fontMedium.drawString(airSpeedText, -fontMedium.stringWidth(airSpeedText) - lineWidth*1.1, fontMedium.stringHeight(airSpeedText)*0.5);

					ofTranslate(0,i * 10 * knotsToPixels, 0);

				}

				ofPopMatrix();

				ofPushMatrix();

				ofTranslate(0, - 5 * knotsToPixels);

				for (int i = 1; i < numberOfLines; i++)

				{
					
					airSpeedText = std::to_string(i * 10);

					ofDrawLine(-lineWidth * 0.5, 0, 0, 0);
					ofTranslate(0, - 10 * knotsToPixels, 0);

				}

				ofPopMatrix();


				break;
		
		}

	}

	void drawMaskedScale()
	{
		maskScale.beginLayer();
		ofClear(0, 0, 0, 0);
		ofPushMatrix();
		ofTranslate(centerX + width*0.5, centerY);

		ofPushMatrix();
		ofTranslate(0, scaleTranslation);

		drawScale();

		ofPopMatrix();

		ofTranslate(-lineWidth * 0.5, 0);
		ofSetColor(ofColor::purple);
		switch (measurementSystem)
		{

		case METRIC:

			ofDrawRectangle(0, 0, lineWidth * 0.5, -tasDot * 10 * metersToPixels);

			ofNoFill();
			ofSetColor(ofColor::white);
			ofDrawRectangle(0, 0, lineWidth * 0.5, -tasDot * 10 * metersToPixels);
			ofFill();

			break;

		case IMPERIAL:


			ofDrawRectangle(0, 0, lineWidth * 0.5, -tasDot * 10 * knotsToPixels);

			ofNoFill();
			ofSetColor(ofColor::white);
			ofDrawRectangle(0, 0, lineWidth * 0.5, -tasDot * 10 * knotsToPixels);
			ofFill();

			break;

		}

		ofPopMatrix();

		maskScale.endLayer();

		maskScale.beginMask();

		ofClear(0, 0, 0, 0);
		ofPushMatrix();
		ofTranslate(centerX, centerY);
		
		ofDrawRectangle(-width * 0.5f, -height * 0.5f, width, height);
		ofPopMatrix();

		maskScale.endMask();

		maskScale.draw(-centerX - width*0.5, -centerY);
	}

	void drawAoAIndicator()
	{
		ofPushMatrix();
		ofTranslate(-(width * 0.5 + aoaBoxWidth + 1), -aoaBoxHeight * 0.5);

		ofNoFill();
		ofSetLineWidth(1);
		ofSetColor(ofColor::white);

		ofDrawRectangle(0, 0, aoaBoxWidth, aoaBoxHeight+1);
		ofFill();

		ofSetColor(ofColor::red);
		ofDrawRectangle(1, 1, aoaBoxWidth -0.5, 4 * degToPixels - 0.5);

		ofTranslate(0, 4 * degToPixels);

		ofSetColor(ofColor::yellow);
		ofDrawRectangle(1, 1, aoaBoxWidth - 0.5, 15 * degToPixels - 0.5);

		ofTranslate(0, 15 * degToPixels);

		ofSetColor(ofColor::forestGreen);
		ofDrawRectangle(1, 1, aoaBoxWidth - 0.5, 20 * degToPixels - 0.5);

		ofTranslate(0, 20 * degToPixels);

		ofSetColor(ofColor::darkBlue);
		ofDrawRectangle(1, 1, aoaBoxWidth - 0.5, 5 * degToPixels - 0.5);

		ofPopMatrix();

		ofPushMatrix();

		ofTranslate(-(width * 0.5 + aoaBoxWidth*1.5), 2 * degToPixels);
		ofTranslate(0, -aoaTranslation);

		ofSetColor(ofColor::black);

		ofTranslate(aoaBoxWidth*0.5, 0);
		ofDrawTriangle(aoaBoxWidth * 0.5, 0, 0, aoaBoxHeight * 0.05, 0, -aoaBoxHeight * 0.05);

		ofDrawRectangle(-aoaBoxWidth, -aoaBoxHeight * 0.05, aoaBoxWidth, aoaBoxHeight * 0.1);

		ofSetColor(ofColor::white);
		ofTranslate(-aoaBoxWidth * 0.5, 0);
		fontMedium.drawString(aoaText, -fontMedium.stringWidth(aoaText) * 0.5, fontMedium.stringHeight(aoaText) * 0.5);


		ofPopMatrix();

		

	}

	void drawAll()
	{
		ofPushMatrix();

		ofTranslate(centerX, centerY);

		drawBox();

		drawAoAIndicator();


		ofPushMatrix();
		ofTranslate(lineWidth*2,0);
		drawMaskedScale();
		ofPopMatrix();

		
		drawIndicatorMask();

		ofPopMatrix();

	}
};

// Altitude Indicator class
class AltitudeIndicator
{

public:

#pragma region variable declration

	float centerX = 0;
	float centerY = 0;

	float height = 0;
	float width = 0;

	float _CENTER_X;
	float _CENTER_Y;

	float _HEIGHT;
	float _WIDTH;

	ofColor _ALT_IND_BG_COLOR;
	ofColor _ALT_IND_TEXT_COLOR;
	ofColor _ALT_10_S_TAPE_COLOR;


	int _MEASUREMENT_SYSTEM;

	float defaultHeight;
	float defaultWidth;

	float minAlt;
	float maxAlt;

	float minVertSpeed;
	float maxVertSpeet;

	float minVertShow;
	float maxVertShow;

	float verticalSpeed;
	float indicatedAltitude;
	float scaleTranslation;
	float vertTranslation;

	float lineWidth;

	ofTrueTypeFont fontSmall;
	ofTrueTypeFont fontMedium;
	ofTrueTypeFont fontLarge;
	ofTrueTypeFont fontMediumIndicator;

	float feetToPixels;
	float feetMintoPixels;

	float metersToPixels;
	float m_sToPixels;

	MeasurementSystem measurementSystem;
	ofxIniSettings settings;

	float pressRef = 0;

	string altitudeText = "";
	string altitudeTapeText = "";
	string vertText = "";
	string pressureRefText = "";
	int gpsHeight;

	ofxLayerMask maskScale;

	ofPath vertBox1;
	ofPath vertBox2;
	ofPath vertIndicator;

	float maxAltFeet;
	float maxNearest100F;
	float maxNearest100M;
	float numberOfLinesF;
	float numberOfLinesM;
#pragma endregion

	AltitudeIndicator()
	{
		measurementSystem = IMPERIAL;
		_MEASUREMENT_SYSTEM = 1;

		centerX = ofGetWidth() * 0.75;
		centerY = ofGetHeight() * 0.333f;

		height = ofGetHeight() * 0.5f; //Roll scale diameter
		width = ofGetWidth() * 0.08f; //double width of Arispeed box

		_CENTER_X = 0.75;
		_CENTER_Y = 0.333;

		_HEIGHT = 0.5;
		_WIDTH = 0.08;

		_ALT_IND_BG_COLOR = ofColor::black;
		_ALT_IND_TEXT_COLOR = ofColor::white;
		_ALT_10_S_TAPE_COLOR = ofColor::purple;
		

		minAlt = -300; //meters
		maxAlt = 14000; //meters

		minVertSpeed = -50; //meters/s
		maxVertSpeet = 50; //meters/s

		minVertShow = 0;
		maxVertShow = 0;

		scaleTranslation = 0;
		vertTranslation = 0;

		lineWidth = width * 0.2;
	
		feetToPixels = height / 600 * 0.95; //show a range of 600 feet at all times in the scale
		feetMintoPixels = height * 0.9 / 4000 * 0.92;
		indicatedAltitude = 0;
	
		metersToPixels = height / 600 * 0.95; //show a range of 600 feet at all times in the scale
		m_sToPixels = height*0.9/20 * 0.92;

		defaultWidth = width;
		defaultHeight = height;

	}

	void loadIni()
	{
		settings.load(ofToDataPath("settings.ini"));

		string section = "ALTITUDE INDICATOR";

		_MEASUREMENT_SYSTEM = settings.getInt(section, "MEASUREMENT_SYSTEM");

		measurementSystem = _MEASUREMENT_SYSTEM == 1 ? IMPERIAL : METRIC;

		_CENTER_X = settings.getFloat(section, "CENTER_X");
		_CENTER_Y = settings.getFloat(section, "CENTER_Y");
		_WIDTH = settings.getFloat(section, "WIDTH");
		_HEIGHT = settings.getFloat(section, "HEIGHT");

	}

	void setupFont()

	{
		float textScale = width / defaultWidth;
		fontSmall.load(ofToDataPath("fonts/Orbitron-Regular.ttf"), 12* textScale, true, true);
		fontMedium.load(ofToDataPath("fonts/Orbitron-Regular.ttf"), 14* textScale, true, true);
		fontLarge.load(ofToDataPath("fonts/Orbitron-Regular.ttf"), 18* textScale, true, true);

	}

	void setup()
	{
		setupFont();

		maskScale.setup(ofGetWidth(), ofGetHeight());
		maskScale.newLayer();

		ofColor fillColor(0, 0, 0, 40);
		
		vertBox1.setFillColor(fillColor);
		vertBox1.setStrokeColor(ofColor::white);
		vertBox1.setStrokeWidth(2);
		vertBox1.moveTo(0, 0);
		vertBox1.lineTo(0, (height * 0.5) * 0.9);
		vertBox1.lineTo(width * 0.4, (height * 0.5) * 0.9);
		vertBox1.lineTo(width * 0.4, height * 0.5 * 0.15);
		vertBox1.close();

		vertBox2.setFillColor(fillColor);
		vertBox2.setStrokeColor(ofColor::white);
		vertBox2.setStrokeWidth(2);
		vertBox2.moveTo(0, 0);
		vertBox2.lineTo(0, -(height * 0.5) * 0.9);
		vertBox2.lineTo(width * 0.4, -(height * 0.5) * 0.9);
		vertBox2.lineTo(width * 0.4, -height * 0.5 * 0.15);
		vertBox2.close();

		float wedgeTan = width * 0.4 / (height * 0.5 * 0.15);

		vertIndicator.setFillColor(ofColor::black);
		vertIndicator.setStrokeColor(ofColor::white);
		vertIndicator.setStrokeWidth(1);
		vertIndicator.moveTo(0, 0);
		vertIndicator.lineTo(width*0.2, width*0.2*wedgeTan*0.5);
		vertIndicator.lineTo(width * 0.4 + width*0.5, width * 0.2 * wedgeTan*0.5);
		vertIndicator.lineTo(width * 0.4 + width * 0.5, -width * 0.2 * wedgeTan*0.5);
		vertIndicator.lineTo(width * 0.2, - width * 0.2 * wedgeTan*0.5);
		vertIndicator.close();
		
	}

	void update(const INPUTS& INPUT_DATA)
	{

		centerX = _CENTER_X * SCREEN_WIDTH;
		centerY = _CENTER_Y * SCREEN_HEIGHT;

		height = _HEIGHT * SCREEN_HEIGHT;
		width = _WIDTH * SCREEN_WIDTH;

		switch (measurementSystem) {

		case METRIC:
			scaleTranslation = ofClamp(INPUT_DATA.ADC_OUT_INDPRESSALT.value, INPUT_DATA.ADC_OUT_INDPRESSALT.min, INPUT_DATA.ADC_OUT_INDPRESSALT.max) * metersToPixels;;
			indicatedAltitude = ofClamp(INPUT_DATA.ADC_OUT_INDPRESSALT.value, INPUT_DATA.ADC_OUT_INDPRESSALT.min, INPUT_DATA.ADC_OUT_INDPRESSALT.max);

			pressRef = (ofClamp(INPUT_DATA.ADC_OUT_STATICPRESS.value, INPUT_DATA.ADC_OUT_STATICPRESS.min, INPUT_DATA.ADC_OUT_STATICPRESS.max));

			verticalSpeed = ofClamp(INPUT_DATA.ADC_OUT_VERTICAL_SPEED.value, INPUT_DATA.ADC_OUT_VERTICAL_SPEED.min, INPUT_DATA.ADC_OUT_VERTICAL_SPEED.max);
			vertTranslation = ofClamp(-verticalSpeed * m_sToPixels, -m_sToPixels * 10, m_sToPixels * 10);
			vertText = ofToString(static_cast<int>(verticalSpeed));
			gpsHeight = ofClamp(INPUT_DATA.ADC_OUT_HGPS.value, INPUT_DATA.ADC_OUT_HGPS.min, INPUT_DATA.ADC_OUT_HGPS.max);

			break;
			
		case IMPERIAL:
			scaleTranslation = ofClamp(INPUT_DATA.ADC_OUT_INDPRESSALT.value, INPUT_DATA.ADC_OUT_INDPRESSALT.min, INPUT_DATA.ADC_OUT_INDPRESSALT.max) * feetToPixels * M_TO_FEET;
			indicatedAltitude = ofClamp(INPUT_DATA.ADC_OUT_INDPRESSALT.value, INPUT_DATA.ADC_OUT_INDPRESSALT.min, INPUT_DATA.ADC_OUT_INDPRESSALT.max) * M_TO_FEET;

			pressRef = (ofClamp(INPUT_DATA.ADC_OUT_STATICPRESS.value, INPUT_DATA.ADC_OUT_STATICPRESS.min, INPUT_DATA.ADC_OUT_STATICPRESS.max) );
			pressRef = pressRef * PA_TO_HG_INCH;

			pressureRefText = ofToString(pressRef);

			verticalSpeed = ofClamp(INPUT_DATA.ADC_OUT_VERTICAL_SPEED.value, INPUT_DATA.ADC_OUT_VERTICAL_SPEED.min, INPUT_DATA.ADC_OUT_VERTICAL_SPEED.max) * M_S_TO_FEET_MIN;
			vertTranslation = ofClamp( - verticalSpeed * feetMintoPixels, - feetMintoPixels * 2000, feetMintoPixels * 2000);
			vertText = ofToString(static_cast<int>(verticalSpeed));
			gpsHeight = ofClamp(INPUT_DATA.ADC_OUT_HGPS.value, INPUT_DATA.ADC_OUT_HGPS.min, INPUT_DATA.ADC_OUT_HGPS.max) * M_TO_FEET;

			break;
		}

	}

	void drawBox()

	{
		ofSetColor(ofColor::black, 100);
		ofDrawRectRounded(-width * 0.5f, -height * 0.5f, width, height,2);

		ofNoFill();
		ofSetColor(ofColor::white);
		ofSetLineWidth(2);
		ofDrawRectRounded(-width * 0.5f, -height * 0.5f, width, height,2);
		ofFill();

		// inch box

		float vertBoxHeight = height * 0.08;
		ofSetColor(ofColor::black);
		ofDrawRectRounded(-width * 0.5f, height * 0.5f, width, vertBoxHeight, 2);

		ofSetColor(ofColor::white);
		ofNoFill();
		ofDrawRectRounded(-width * 0.5f, height * 0.5f, width, vertBoxHeight, 2);
		ofFill();

		pressureRefText = ofToString(static_cast<int>(pressRef));
		string vertString = pressureRefText;


		ofSetColor(ofColor::white);
		
		switch (measurementSystem) {

		case (METRIC):

			ofSetColor(ofColor::cyan);
			fontSmall.drawString(vertString + "  Pa", -fontSmall.stringWidth(vertString + "  Pa") * 0.5, height * 0.5 + vertBoxHeight * 0.5 + fontSmall.stringHeight(vertString + "  Pa") * 0.5);
			ofSetColor(ofColor::white);

			break;

		case (IMPERIAL):

			ofSetColor(ofColor::cyan);
			fontSmall.drawString(vertString + "  IN",  - fontSmall.stringWidth(vertString + "  IN")*0.5, height * 0.5 + vertBoxHeight * 0.5 + fontSmall.stringHeight(vertString + "  IN") * 0.5);
			ofSetColor(ofColor::white);

			break;
		}

		float gpsBoxHeight = height * 0.08;
		ofSetColor(ofColor::black);
		ofDrawRectRounded(-width * 0.5f, -height * 0.5f - gpsBoxHeight, width, gpsBoxHeight, 2);

		ofSetColor(ofColor::white);
		ofNoFill();
		ofDrawRectRounded(-width * 0.5f, -height * 0.5f - gpsBoxHeight, width, gpsBoxHeight, 2);
		ofFill();

		string gpsString = ofToString(gpsHeight);
		string gpsLabel = "HGPS";

		ofSetColor(ofColor::cyan);
		fontSmall.drawString(gpsLabel, -width * 0.48, -height * 0.5 + fontSmall.stringHeight(gpsLabel) * 0.5 - gpsBoxHeight * 0.5);

		ofSetColor(ofColor::white);
		switch (measurementSystem) {

		case (METRIC):

			fontSmall.drawString(gpsString + " M", width * 0.48 - fontSmall.stringWidth(gpsString + " M"), -height * 0.5 + fontSmall.stringHeight(gpsString + " M") * 0.5 - gpsBoxHeight * 0.5);

			break;

		case (IMPERIAL):

			fontSmall.drawString(gpsString + " ft", width * 0.48 - fontSmall.stringWidth(gpsString + " ft"), -height * 0.5 + fontSmall.stringHeight(gpsString + " ft") * 0.5 - gpsBoxHeight * 0.5);

			break;
		}
	}

	void drawScale()

	{
		ofPushMatrix();
		ofTranslate(-width * 0.5, 0);

		switch (measurementSystem) {

		case METRIC :

			ofSetLineWidth(2);
			ofSetColor(ofColor::white);

			maxNearest100M = std::round(maxAlt);
			numberOfLinesM = (maxNearest100M + 300) * 0.01;

			for (int i = 0; i < numberOfLinesM; i++)

			{
				ofTranslate(0, -i * 100 * metersToPixels, 0);

				altitudeTapeText = std::to_string(i * 100 - 300);

				ofDrawLine(0, 0, lineWidth, 0);
				fontMedium.drawString(altitudeTapeText, lineWidth * 1.1, fontMedium.stringHeight(altitudeTapeText) * 0.5);

				ofTranslate(0, i * 100 * metersToPixels, 0);

			}

			for (int i = 0; i < numberOfLinesM * 5; i++)

			{
				ofTranslate(0, -20 * i * metersToPixels, 0);
				ofSetLineWidth(2);
				ofSetColor(ofColor::white);
				ofDrawLine(0, 0, lineWidth * 0.5, 0);
				ofTranslate(0, 20 * i * metersToPixels, 0);
			}

			break;

		case IMPERIAL :


			ofSetLineWidth(2);
			ofSetColor(ofColor::white);

			maxAltFeet = (maxAlt + 300) * M_TO_FEET;
			maxNearest100F = std::round(maxAltFeet);
			numberOfLinesF = (maxNearest100F) * 0.01;

			//draw 10s lines, starting from 0, at center of the current reference system;

			for (int i = 0; i < numberOfLinesF; i++)

			{
				ofTranslate(0, -i * 100 * feetToPixels, 0);

				altitudeTapeText = std::to_string(i * 100 - 300);

				ofDrawLine(0, 0, lineWidth, 0);
				fontMedium.drawString(altitudeTapeText, lineWidth * 1.1, fontMedium.stringHeight(altitudeTapeText) * 0.5);

				ofTranslate(0, i * 100 * feetToPixels, 0);

			}

			for (int i = 0; i < numberOfLinesF * 5; i++)

			{
				ofTranslate(0, -20 * i * feetToPixels, 0);
				ofSetLineWidth(2);
				ofSetColor(ofColor::white);
				ofDrawLine(0, 0, lineWidth * 0.5, 0);
				ofTranslate(0, 20 * i * feetToPixels, 0);
			}

			break;
		}

		ofPopMatrix();
	}

	void drawIndicator()

	{
		string indicatorString = ofToString(static_cast<int>(indicatedAltitude));

		ofSetColor(_ALT_IND_BG_COLOR);

		ofDrawRectRounded(-(width - width * 0.25) * 0.5, -width * 0.25, width - width * 0.125-1, width * 0.5, 5);
		ofDrawTriangle(-width * 0.5 + width * 0.25, width * 0.2, - width * 0.5 + width * 0.25, -width * 0.2, - width * 0.5, 0);

		ofSetColor(_ALT_IND_TEXT_COLOR);

		fontMedium.drawString(indicatorString, -fontMedium.stringWidth(indicatorString) * 0.5, fontMedium.stringHeight(indicatorString) * 0.5);
	}

	void drawMaskedScale()
	{
		maskScale.beginLayer();
		ofClear(0, 0, 0, 0);
		ofPushMatrix();
		ofTranslate(centerX, centerY);

		ofPushMatrix();
		ofTranslate(0, scaleTranslation);

		drawScale();
		
		ofPopMatrix();

		ofTranslate(-lineWidth * 2.5, 0);
		ofSetColor(_ALT_10_S_TAPE_COLOR);

		switch (measurementSystem)
		{

		case METRIC:

			ofDrawRectangle(0, 0, lineWidth * 0.5, - verticalSpeed * 10 * metersToPixels); 

			ofNoFill();
			ofSetColor(ofColor::white);
			ofDrawRectangle(0, 0, lineWidth * 0.5, - verticalSpeed * 10 * metersToPixels);
			ofFill();

			break;

		case IMPERIAL:

			float conversion = 10 / 60.0f * feetToPixels; ;

			ofDrawRectangle(0, 0, lineWidth * 0.5, - verticalSpeed * conversion); 
			
			ofNoFill();
			ofSetColor(ofColor::white);
			ofDrawRectangle(0, 0, lineWidth * 0.5, -verticalSpeed * conversion);
			ofFill();

			break;

		}
		
		ofPopMatrix();

		maskScale.endLayer();

		maskScale.beginMask();

		ofClear(0, 0, 0, 0);
		ofPushMatrix();
		ofTranslate(centerX, centerY);

		ofDrawRectangle(-width * 0.5f, -height * 0.5f, width, height);
		ofPopMatrix();

		maskScale.endMask();

		maskScale.draw(-centerX , -centerY);
	}

	void drawVertBox()

	{

		switch (measurementSystem)
		{
		case (METRIC) :
			
			ofPushMatrix();
			ofTranslate(width * 0.5, 0);

			//draw lower "box"
			vertBox1.draw();

			//draw scale, lower

			ofSetColor(ofColor::white);
			ofSetLineWidth(2);

			ofDrawLine(0,m_sToPixels * 5, width * 0.4 / 3, m_sToPixels * 5);
			fontMedium.drawString("5", width * 0.4 * 2 / 3 - fontMedium.stringWidth("5") * 0.5, m_sToPixels * 5 + fontMedium.stringHeight("5") * 0.5);

			ofDrawLine(0, m_sToPixels * 2.5, width * 0.4 / 4, m_sToPixels * 2.5);

			ofDrawLine(0, m_sToPixels * 10, width * 0.4 / 3, m_sToPixels * 10);
			fontMedium.drawString("10", width * 0.4 * 2 / 3 - fontMedium.stringWidth("10") * 0.5, m_sToPixels * 10 + fontMedium.stringHeight("10") * 0.5);

			ofDrawLine(0, m_sToPixels * 7.5, width * 0.4 / 4, m_sToPixels * 7.5);

			//draw upper "box"
			vertBox2.draw();

			//draw scale, upper

			ofSetColor(ofColor::white);
			ofSetLineWidth(2);

			ofDrawLine(0, -m_sToPixels * 5, width * 0.4 / 3, -m_sToPixels * 5);
			fontMedium.drawString("5", width * 0.4 * 2 / 3 - fontMedium.stringWidth("5") * 0.5, -m_sToPixels * 5 + fontMedium.stringHeight("5") * 0.5);

			ofDrawLine(0, -m_sToPixels * 2.5, width * 0.4 / 4, -m_sToPixels * 2.5);

			ofDrawLine(0, -m_sToPixels * 10, width * 0.4 / 3, -m_sToPixels * 10);
			fontMedium.drawString("10", width * 0.4 * 2 / 3 - fontMedium.stringWidth("10") * 0.5, -m_sToPixels * 10 + fontMedium.stringHeight("10") * 0.5);

			ofDrawLine(0, -m_sToPixels * 7.5, width * 0.4 / 4, -m_sToPixels * 7.5);

			ofPushMatrix();
			ofTranslate(0, vertTranslation);
			vertIndicator.draw();

			fontMedium.drawString(vertText, width * 0.5 - fontMedium.stringWidth(vertText) * 0.5, +fontMedium.stringHeight(vertText) * 0.5);
			ofPopMatrix();

			ofPopMatrix();

			break;

		case (IMPERIAL) :


			ofPushMatrix();
			ofTranslate(width * 0.5, 0);

			//draw lower "box"
			vertBox1.draw();

			//draw scale, lower

			ofSetColor(ofColor::white);
			ofSetLineWidth(2);

			ofDrawLine(0, feetMintoPixels * 1000, width * 0.4 / 3, feetMintoPixels * 1000);
			fontMedium.drawString("1", width * 0.4 * 2 / 3 - fontMedium.stringWidth("1") * 0.5, feetMintoPixels * 1000 + fontMedium.stringHeight("1") * 0.5);

			ofDrawLine(0, feetMintoPixels * 500, width * 0.4 / 4, feetMintoPixels * 500);

			ofDrawLine(0, feetMintoPixels * 2000, width * 0.4 / 3, feetMintoPixels * 2000);
			fontMedium.drawString("2", width * 0.4 * 2 / 3 - fontMedium.stringWidth("2") * 0.5, feetMintoPixels * 2000 + fontMedium.stringHeight("2") * 0.5);

			ofDrawLine(0, feetMintoPixels * 1500, width * 0.4 / 4, feetMintoPixels * 1500);

			//draw upper "box"
			vertBox2.draw();

			//draw scale, upper

			ofSetColor(ofColor::white);
			ofSetLineWidth(2);

			ofDrawLine(0, -feetMintoPixels * 1000, width * 0.4 / 3, -feetMintoPixels * 1000);
			fontMedium.drawString("1", width * 0.4 * 2 / 3 - fontMedium.stringWidth("1") * 0.5, -feetMintoPixels * 1000 + fontMedium.stringHeight("1") * 0.5);

			ofDrawLine(0, -feetMintoPixels * 500, width * 0.4 / 4, -feetMintoPixels * 500);

			ofDrawLine(0, -feetMintoPixels * 2000, width * 0.4 / 3, -feetMintoPixels * 2000);
			fontMedium.drawString("2", width * 0.4 * 2 / 3 - fontMedium.stringWidth("2") * 0.5, -feetMintoPixels * 2000 + fontMedium.stringHeight("2") * 0.5);

			ofDrawLine(0, -feetMintoPixels * 1500, width * 0.4 / 4, -feetMintoPixels * 1500);

			ofPushMatrix();
			ofTranslate(0, vertTranslation);
			vertIndicator.draw();

			fontMedium.drawString(vertText, width * 0.5 - fontMedium.stringWidth(vertText) * 0.5, +fontMedium.stringHeight(vertText) * 0.5);
			ofPopMatrix();

			ofPopMatrix();

			break;

		}

	}

	void drawAll()

	{
		ofPushMatrix();


		ofTranslate(centerX, centerY);

		drawBox();
		drawMaskedScale();
		drawIndicator();
		drawVertBox();

		ofPopMatrix();

	}

};

// OtherReadings class
class OtherReadings

{
public:

#pragma region variable declaration	
	float outsideTemp;
	float staticPressure;
	float groundSpeed;
	float magPitch;
	float magRoll;
	float latitude;
	float longitude;

	float gpsHeight;
	float gpsVert;

	float boxWidth;
	float boxHeight;

	float centerX;
	float centerY;

	ofxHistoryPlot* plot1;
	ofxHistoryPlot* plot2; 

	float aoaVane;
	float currentFrameRate;

	ofTrueTypeFont fontSmall;
	ofTrueTypeFont fontMedium;
	ofTrueTypeFont fontLarge;

	std::string outTempText;
	std::string outPressText;
	std::string magPitchText;
	std::string magRollText;
	std::string latText;
	std::string lonText;
	std::string gpsHeightText;
	std::string timeText;

	std::vector <string> labels;
	std::vector <string> values;
#pragma endregion

	OtherReadings()
	{

		boxHeight = ofGetHeight() * 0.25;
		boxWidth = ofGetHeight() * 0.43;

		centerX = ofGetWidth() * 0.75 - ofGetWidth() * 0.08f*0.5f + boxWidth*0.5;
		centerY = ofGetHeight() *0.8f ;

		labels.push_back("OUT TEMP.");
		labels.push_back("GPS HEIG.");
		labels.push_back("LON");
		labels.push_back("MAG PITCH");
		labels.push_back("TIME");
		labels.push_back("ST. PRESS.");
		labels.push_back("LAT");
		labels.push_back("MAG ROLL");

		outTempText = "";
		outPressText = "";
		magPitchText = "";
		magRollText = "";
		latText = "";
		lonText = "";
		gpsHeightText = "";
		timeText = "";
		aoaVane = 0;
		
	}

	void setup()
	{

		setupFont();

	}

	void setupFont()

	{
		fontSmall.load(ofToDataPath("fonts/Nord-Regular.ttf"), 9, true, true);
		fontMedium.load(ofToDataPath("fonts/Nord-Regular.ttf"), 12, true, true);
		fontLarge.load(ofToDataPath("fonts/Nord-Regular.ttf"), 18, true, true);

	}

	void update(const INPUTS& INPUT_DATA)
	{
		outTempText = ofToString(static_cast<int>(ofClamp(INPUT_DATA.ADC_OUT_OAT.value, INPUT_DATA.ADC_OUT_OAT.min, INPUT_DATA.ADC_OUT_OAT.max))) + " C";
		outPressText = ofToString(static_cast<int>(ofClamp(INPUT_DATA.ADC_OUT_STATICPRESS.value, INPUT_DATA.ADC_OUT_STATICPRESS.min, INPUT_DATA.ADC_OUT_STATICPRESS.max))) + " Pa";
		magPitchText = ofToString(static_cast<int>(ofClamp(INPUT_DATA.ADC_OUT_MAGPITCH.value, INPUT_DATA.ADC_OUT_MAGPITCH.min, INPUT_DATA.ADC_OUT_MAGPITCH.max))) + " DEG";
		magRollText = ofToString(static_cast<int>(ofClamp(INPUT_DATA.ADC_OUT_MAGROLL.value, INPUT_DATA.ADC_OUT_MAGROLL.min, INPUT_DATA.ADC_OUT_MAGROLL.max))) + " DEG";
		latText = ofToString(std::round(ofClamp(INPUT_DATA.ADC_OUT_LAT.value, INPUT_DATA.ADC_OUT_LAT.min, INPUT_DATA.ADC_OUT_LAT.max) * 100000) / 100000);
		lonText = ofToString(std::round(ofClamp(INPUT_DATA.ADC_OUT_LON.value, INPUT_DATA.ADC_OUT_LON.min, INPUT_DATA.ADC_OUT_LON.max) * 100000) / 100000);
		gpsHeightText = ofToString(static_cast<int>(ofClamp(INPUT_DATA.ADC_OUT_HGPS.value, INPUT_DATA.ADC_OUT_HGPS.min, INPUT_DATA.ADC_OUT_HGPS.max))) + " m";

		
		unsigned long gpsTimeSeconds = ofClamp(INPUT_DATA.ADC_OUT_GPS_TIME.value, INPUT_DATA.ADC_OUT_GPS_TIME.min, INPUT_DATA.ADC_OUT_GPS_TIME.max);
		timeText = convertGPSTimeToString(gpsTimeSeconds);

		aoaVane = ofClamp(INPUT_DATA.ADC_OUT_AOA.value, INPUT_DATA.ADC_OUT_AOA.min, INPUT_DATA.ADC_OUT_AOA.max);
		
		currentFrameRate = ofGetFrameRate();

		values.clear();

		values.push_back(outTempText);
		values.push_back(gpsHeightText);
		values.push_back(lonText);
		values.push_back(magPitchText);
		values.push_back(timeText);
		values.push_back(outPressText);
		values.push_back(latText);
		values.push_back(magRollText);

	}

	void drawAll()

	{
		ofPushMatrix();
		ofTranslate(centerX, centerY);

		ofSetColor(ofColor::darkSlateGrey);
		ofDrawRectRounded(-boxWidth * 0.5f, -boxHeight * 0.5f, boxWidth, boxHeight, 2);
		
		ofNoFill();
		ofSetColor(ofColor::white);
		ofSetLineWidth(2);
		ofDrawRectRounded(-boxWidth * 0.5f, -boxHeight * 0.5f, boxWidth, boxHeight, 2);
		ofFill();

		ofSetLineWidth(1);
		ofDrawLine(0, -boxHeight * 0.5, 0, boxHeight*0.5);

		ofPushMatrix();
		ofTranslate(0, -boxHeight * 0.5);
		for (int i = 1; i < 5; i++)
		{
			ofTranslate(0, boxHeight / 4.0f);
			//ofDrawLine(-boxWidth * 0.5, 0, boxWidth * 0.5, 0);

			ofSetColor(ofColor::lawnGreen);
			fontMedium.drawString(labels[i-1], -boxWidth * 0.49, -boxHeight / 8.0f + fontMedium.stringHeight(labels[i-1]) * 0.5);
			fontMedium.drawString(labels[i - 1+4], boxWidth * 0.01, -boxHeight / 8.0f + fontMedium.stringHeight(labels[i - 1+4]) * 0.5);

			ofSetColor(ofColor::white);
			fontMedium.drawString(values[i - 1], - fontMedium.stringWidth(values[i - 1]) - boxWidth*0.01, -boxHeight / 8.0f + fontMedium.stringHeight(values[i - 1]) * 0.5);
			fontMedium.drawString(values[i - 1 + 4], boxWidth*0.5 -fontMedium.stringWidth(values[i - 1+4]) - boxWidth * 0.01, -boxHeight / 8.0f + fontMedium.stringHeight(values[i - 1 + 4]) * 0.5);

		}
		ofPopMatrix();
		ofPopMatrix();

	}

};

class Map {

public:
#pragma region variables declaration
	ofxIniSettings settings;

	bool isMapScene;
	float latitude;
	float longitude;
	string token; // = "pk.eyJ1Ijoic2FpZmUtZGlzcGxheSIsImEiOiJjbHhua204amowM252MmtzOWdvcm93MWlsIn0.QWvcp4fk8H9yJ0IqIavl6A";
	int x, y;
	float mapDisplX;
	float mapDisplY;

	float heading;

	string url;

	float mapZoom;
	float imageWidth;
	float imageHeight;

	float width = ofGetWidth(); 
	float height = ofGetHeight();

	ofxLayerMask mask;
	ofxLayerMask maskRect;

	float maskWidth;
	float maskHeight;
	float maskRadius;

	ofPath planeIcon;
	ofImage image;
	
	glm::vec2 centerTile;
#pragma endregion

	void setup(bool isMapScene)
	{
		settings.load(ofToDataPath("settings.ini"));

		token = settings.getString("MAP", "MAP_BOX TOKEN");

		float dimension = ofGetHeight() * 0.05;

		planeIcon.setColor(ofColor::white); 

		ofPoint point0(0, -dimension / 2.0f);
		ofPoint point1(dimension / 10.0f, -dimension / 2.0f + dimension / 10.0f);
		ofPoint point2(dimension / 10.0f, -dimension / 2.0f + dimension / 10.0f + dimension / 10.0f + 1);
		ofPoint point3(dimension / 10.0f + dimension / 3.0f + dimension / 15.0f, -dimension / 2.0f + dimension / 10.0f + dimension / 10.0f + 1 + dimension / 10.0f);
		ofPoint point4(dimension / 10.0f + dimension / 3.0f + dimension / 15.0f, -dimension / 2.0f + dimension / 10.0f + dimension / 10.0f + 1 + dimension / 10.0f + dimension / 15.0f);
		ofPoint point5(dimension / 10.0f, -dimension / 2.0f + dimension / 10.0f + dimension / 10.0f + 1 + dimension / 10.0f + dimension / 15.0f);
		ofPoint point6(dimension / 10.0f, -dimension / 2.0f + dimension / 10.0f + dimension / 10.0f + 1 + dimension / 10.0f + dimension / 15.0f + dimension / 7.5f);
		ofPoint point7(dimension / 10.0f + dimension / 7.5f, -dimension / 2.0f + dimension / 10.0f + dimension / 10.0f + 1 + dimension / 10.0f + dimension / 15.0f + dimension / 7.5f + dimension / 15.0f);
		ofPoint point8(dimension / 10.0f + dimension / 7.5f, -dimension / 2.0f + dimension / 10.0f + dimension / 10.0f + 1 + dimension / 10.0f + dimension / 15.0f + dimension / 7.5f + dimension / 15.0f + dimension / 15.0f);

		ofPoint point9(-point8.x, point8.y);
		ofPoint point10(-point7.x, point7.y);
		ofPoint point11(-point6.x, point6.y);
		ofPoint point12(-point5.x, point5.y);
		ofPoint point13(-point4.x, point4.y);
		ofPoint point14(-point3.x, point3.y);
		ofPoint point15(-point2.x, point2.y);
		ofPoint point16(-point1.x, point1.y);

		planeIcon.moveTo(point0);
		planeIcon.lineTo(point1);
		planeIcon.lineTo(point2);
		planeIcon.lineTo(point3);
		planeIcon.lineTo(point4);
		planeIcon.lineTo(point5);
		planeIcon.lineTo(point6);
		planeIcon.lineTo(point7);
		planeIcon.lineTo(point8);
		planeIcon.lineTo(point9);
		planeIcon.lineTo(point10);
		planeIcon.lineTo(point11);
		planeIcon.lineTo(point12);
		planeIcon.lineTo(point13);
		planeIcon.lineTo(point14);
		planeIcon.lineTo(point15);
		planeIcon.lineTo(point16);

		planeIcon.close();

		mask.setup(width*3, width*3);
		mask.newLayer();

		maskRect.setup(width*2, width*2);
		maskRect.newLayer();

		imageWidth = width*0.25;
		imageHeight = width*0.25;
		
		maskRadius = imageWidth;
		maskHeight = imageHeight*2;
		maskWidth = imageWidth * 2;
	
	}

	void update(INPUTS INPUT_DATA) 
	{

		heading = ofClamp(INPUT_DATA.ADC_OUT_MAGHEADING.value, INPUT_DATA.ADC_OUT_MAGHEADING.min, INPUT_DATA.ADC_OUT_MAGHEADING.max);
		latitude = ofClamp(INPUT_DATA.ADC_OUT_LAT.value, INPUT_DATA.ADC_OUT_LAT.min, INPUT_DATA.ADC_OUT_LAT.max);
		longitude = ofClamp(INPUT_DATA.ADC_OUT_LON.value, INPUT_DATA.ADC_OUT_LON.min, INPUT_DATA.ADC_OUT_LON.max);

		centerTile = latLonToTile(latitude, longitude, mapZoom::getInstance().getValue());

	}

	void drawMaskedMap()
	{
		mask.beginLayer();
		ofClear(0, 0, 0, 0);
		
		ofPushMatrix();

		ofTranslate(width*1.5, width*1.5);
		ofTranslate(-width * 0.5, -width * 0.5);

		draw();

		ofPopMatrix();

		mask.endLayer();

		mask.beginMask();

		ofClear(0, 0, 0, 0);
		
		ofPushMatrix();
		ofTranslate(width * 1.5, width*1.5);
		ofDrawCircle(0, 0, maskRadius);
		ofPopMatrix();

		mask.endMask();

		mask.draw(-width *1.5 ,- width*1.5);

		ofPushMatrix();
		ofTranslate(0,0);
		ofNoFill();
		ofSetColor(ofColor::white);
		ofSetLineWidth(3);
		ofDrawCircle(0, 0, maskRadius);
		ofFill();
		ofPopMatrix();

		ofPushMatrix();
		ofTranslate(0,0);

		ofRotateZDeg(heading);
		ofScale(1.8, 2.1, 1);

		planeIcon.draw();

		ofPopMatrix();
	}

	void drawMaskedMapRect()
	{
		maskRect.beginLayer();

		ofClear(0, 0, 0, 0);

		ofPushMatrix();

		ofTranslate(width, width);

		ofRotate(-heading);

		drawMaskedMap();

		ofPopMatrix();

		maskRect.endLayer();

		maskRect.beginMask();

		ofClear(0, 0, 0, 0);

		ofPushMatrix();

		ofTranslate(width, width);

		ofDrawRectangle(-maskWidth * 0.5, -maskHeight * 0.5, maskWidth, maskHeight);

		ofPopMatrix();

		maskRect.endMask();

		maskRect.draw(-width, -width);
	}

	void draw()
	{
		ofPushMatrix();

		ofTranslate(-mapDisplX * imageWidth, -mapDisplY * imageHeight);

		for (int x = -2; x <= 1; ++x) {
			for (int y = -2; y <= 1; ++y) {
				int tileX = floor(centerTile.x) + x;
				int tileY = floor(centerTile.y) + y;
				if (!isTileLoaded(tileX, tileY, mapZoom::getInstance().getValue())) {

					loadTile(tileX, tileY, mapZoom::getInstance().getValue());
					ofImage& tile = TileCache::getInstance().getCache()[getTileKey(tileX, tileY, mapZoom::getInstance().getValue())];
					if (tile.isAllocated()) {
						tile.draw((x + 2) * imageWidth, (y + 2) * imageHeight, imageWidth, imageHeight);
					}
				}
				else {

					ofImage& tile = TileCache::getInstance().getCache()[getTileKey(tileX, tileY, mapZoom::getInstance().getValue())];
					if (tile.isAllocated()) {
						tile.draw((x + 2) * imageWidth, (y + 2) * imageHeight, imageWidth, imageHeight);
					}
				}
			}
		}
		ofPopMatrix();

	}

	void drawAll() {

		ofClear(0, 0, 0, 0);

		ofPushMatrix();

		ofTranslate(width * 0.5, height * 0.5);

		ofRotateZDeg(-heading);

		ofTranslate(-width * 0.5, -height * 0.5);

		draw();

		ofPopMatrix();

		ofPushMatrix();
		ofTranslate(width * 0.5, height * 0.5);

		ofScale(1.6, 1.9, 1);

		planeIcon.draw();

		ofPopMatrix();
	}

	std::string getTileKey(int x, int y, int zoom) 
	{
		return std::to_string(zoom) + "_" + std::to_string(x) + "_" + std::to_string(y);
	}

	bool isTileLoaded(int x, int y, int zoom) 
	{
		return TileCache::getInstance().getCache().find(getTileKey(x, y, zoom)) != TileCache::getInstance().getCache().end();
	}

	void loadTile(int x, int y, int zoom)
	{
		url = "http://api.mapbox.com/v4/mapbox.satellite/" + ofToString(zoom) + "/" + ofToString(x) + "/" + ofToString(y) + "@2.jpg90" + "?access_token=" + token;

		std::string folderPath = ofToDataPath("mapTiles/" + std::to_string(zoom) + "/" + std::to_string(x) + "/");
		std::filesystem::create_directories(folderPath); // Create directories if they do not exist
		std::string filePath = folderPath + std::to_string(y) + ".jpg";

		if (std::filesystem::exists(filePath)) {
			ofImage img;
			if (img.load(filePath)) {
				TileCache::getInstance().getCache()[getTileKey(x, y, zoom)] = img;
				ofLogNotice() << "Loaded existing file: " << filePath;
			}
		}

		else
		{
			ofHttpResponse response = ofLoadURL(url);

			if (response.status == 200) {

				ofFile file(filePath, ofFile::WriteOnly);
				file.writeFromBuffer(response.data);

				ofImage img;
				if (img.load(filePath)) {
					TileCache::getInstance().getCache()[getTileKey(x, y, zoom)] = img;
					ofLogNotice() << "Loaded existing file: " << filePath;
				}

				/*ofBuffer buffer = response.data;
				TileCache::getInstance().getCache()[getTileKey(x, y, zoom)].load(buffer);*/
			}
			else {
				ofLogError("Failed to fetch map tile: " + response.error);

			}

		}


		ofHttpResponse response = ofLoadURL(url);

		if (response.status == 200) {
			ofBuffer buffer = response.data;
			TileCache::getInstance().getCache()[getTileKey(x, y, zoom)].load(buffer);
		}
		else {
			ofLogError("Failed to fetch map tile: " + response.error);
			
		}
	}
	
	void loadExistingMapTiles(int zoom) {
		std::string zoomPath = ofToDataPath("mapTiles/" + std::to_string(zoom) + "/");

		if (!std::filesystem::exists(zoomPath)) {
			ofLogError() << "Directory does not exist: " << zoomPath;
			return;
		}

		ofLogNotice() << "Loading existing tiles from directory: " << zoomPath;

		for (const auto& xDir : std::filesystem::directory_iterator(zoomPath)) {
			ofLogNotice() << "Found directory: " << xDir.path().filename().string();
			if (xDir.is_directory()) {
				std::string xPath = xDir.path().string();
				std::string xName = xDir.path().filename().string();

				ofLogNotice() << "Entering directory: " << xPath;

				for (const auto& yFile : std::filesystem::directory_iterator(ofToDataPath(xPath))) {	
					ofLogNotice() << "Found file or directory: " << yFile.path().filename().string();

					if (yFile.is_regular_file()) {
						
						std::string yPath = yFile.path().string();
						std::string yName = yFile.path().filename().string();

						std::string key = std::to_string(zoom) + "_" +xName + "_" + yName.substr(0, yName.find_last_of('.'));
							ofImage img;
							if (img.load(yPath)) {
								TileCache::getInstance().getCache()[key] = img;
								ofLogNotice() << "Loaded Image offline, key: " << key;
							}


							
						}

					
					}
					
				}
			}
		}

	glm::vec2 latLonToTile(float lat, float lon, int zoom) {
		double latRad = ofDegToRad(lat);
		int n = 1 << zoom;
		float x = n * ((lon + 180.0) / 360.0);
		float y = n * (1.0 - (log(tan(latRad) + (1 / cos(latRad))) / PI)) / 2.0;

		mapDisplX = x - floor(x);
		mapDisplY = y - floor(y);

		return glm::vec2(int(x), int(y));
	}

};

#pragma endregion

// Main scenes classes, including the BaseScene used as template in order to have all the scenes saved in a vector in the ofApp implementation.
#pragma region Scene Classes

// Base scene class
class BaseScene {
public:
	virtual void setup() = 0;
	virtual void update(const INPUTS& INPUT_DATA) = 0;
	virtual void draw() = 0;

	virtual void keyPressed(int key) {}
	virtual void mouseMoved(int x, int y) {}
	virtual void mouseDragged(int x, int y, int button) {}
	virtual void mousePressed(int x, int y, int button) {}
	virtual void mouseReleased(int x, int y, int button) {}
};

class PfdScene : public BaseScene {

public:

#pragma region variables declaration

	// main indicator classes
	AttitudeIndicator attitudeIndicator;
	Compass compass;
	AirspeedIndicator airspeedIndicator;
	AltitudeIndicator altitudeIndicator;
	OtherReadings otherReadings;
	Map map;

	float mapCenterX = 0.25 * SCREEN_WIDTH;
	float mapCenterY = 0.80 * SCREEN_HEIGHT;

	float _MAP_CENTER_X = 0.25f;
	float _MAP_CENTER_Y = 0.80f;

	ofRectangle zoomPlusButton;
	ofRectangle zoomMinusButton;

	// Object to read from the INI file
	ofxIniSettings settings;

	enum MeasurementSystem { METRIC, IMPERIAL };

	// Shared pointer to the fonts to be loaded 		
	std::shared_ptr<ofTrueTypeFont> fontSmall;
	std::shared_ptr<ofTrueTypeFont> fontMedium;
	std::shared_ptr<ofTrueTypeFont> fontLarge;
	std::shared_ptr<ofTrueTypeFont> fontButton;

	INPUTS INPUT_DATA;

#pragma endregion

#pragma region custom methods

	// loads the main indicators parameters from the .ini file
	void loadIni()
	{
			settings.load(ofToDataPath("settings.ini"));

			attitudeIndicator.loadIni();
			compass.loadIni();
			airspeedIndicator.loadIni();
			altitudeIndicator.loadIni();


			_MAP_CENTER_X = settings.getFloat("MAP", "CENTER_X");
			_MAP_CENTER_Y = settings.getFloat("MAP", "CENTER_Y");

	}

	// loads the fonts
	void loadFont()
	{
		fontSmall = std::make_shared<ofTrueTypeFont>();
		fontSmall->load("fonts/Nord-Regular.ttf", 12, true, true);

		fontMedium = std::make_shared<ofTrueTypeFont>();
		fontMedium->load("fonts/Nord-Regular.ttf", 14, true, true);

		fontLarge = std::make_shared<ofTrueTypeFont>();
		fontLarge->load("fonts/Nord-Regular.ttf", 20, true, true);

		fontButton = std::make_shared<ofTrueTypeFont>();
		fontButton->load("fonts/Nord-Regular.ttf", 35, true, true);
	}

	void drawScaledMap()
	{
		ofPushMatrix();

		ofScale(0.35, 0.35, 1);

		map.drawMaskedMap();

		ofPopMatrix();
	}

#pragma endregion

#pragma region main loop

	void setup() override {
		
		loadIni();
		loadFont();

		zoomPlusButton.setSize(SCREEN_WIDTH * 0.03, SCREEN_WIDTH * 0.03);
		zoomMinusButton.setSize(SCREEN_WIDTH * 0.03, SCREEN_WIDTH * 0.03);

		zoomPlusButton.setPosition(SCREEN_WIDTH*0.1,SCREEN_HEIGHT*0.68);
		zoomMinusButton.setPosition(SCREEN_WIDTH * 0.1, SCREEN_HEIGHT * 0.87 - SCREEN_WIDTH*0.015);//(width * 0.5 + maskWidth * 0.55, height * 0.5 - maskHeight * 0.5);
		//zoomMinusButton.setPosition(width * 0.5 + maskWidth * 0.55, height * 0.5 - maskHeight * 0.5 + maskHeight - zoomMinusButton.getHeight());

		//setup function for each class (masks, complex geometries, fonts etc.)
		attitudeIndicator.setup();
		compass.setup();
		airspeedIndicator.setup();
		altitudeIndicator.setup();
		otherReadings.setup();
		map.setup(true);
		
	}

	void update(const INPUTS& INPUT_DATA) override {
		
		// calls all the indicator classes update methods
		attitudeIndicator.update(INPUT_DATA); 
		compass.update(INPUT_DATA); 
		airspeedIndicator.update(INPUT_DATA);
		altitudeIndicator.update(INPUT_DATA);
		otherReadings.update(INPUT_DATA);
		map.update(INPUT_DATA);

		mapCenterX = _MAP_CENTER_X * SCREEN_WIDTH;
		mapCenterY = _MAP_CENTER_Y * SCREEN_HEIGHT;

	}

	void draw() override {
		
		ofClear(0, 0, 0, 0); //clear the buffer at every draw cycle 

		ofPushMatrix(); // save the current transformation matrix 

		// Draw Attitude indicator, composed of: Pitch Ladder scale, Roll Indicator, Ground and Sky boxes, Horizon Line and Slip/Skid indicaor (see AttitiudeIndicator class and methods for details)
		attitudeIndicator.drawAll();

		// Draw Nav compass, composed of: Compass, Heading indicator / reading box, Airplane Icon (see Nav class and methods for more details) 
		compass.drawAll();

		// Draw Airspeed Indicator, composed of: Indicated Air Speed tape, IAS reading box (center of the tape), TAS reading box (bottom of the tape). (See AirspeedIndicator class and methods for more details) 
		airspeedIndicator.drawAll();

		// Draw Altitude Indicator, composed of: Indicated Pressure Altitude tape and reading box (center of the tape), Vertical Speed Scale and moving indicator (right of the Altitude Tape). (See AltitudeIndicator class and methods for more details) 
		altitudeIndicator.drawAll();

		// Draw other readings in a box, such as: outside temperature, static pressure, lat and lon, magnetic pitch and roll
		otherReadings.drawAll();

		//Draw the mini map in the bottom left (same Y position of the compass)
		ofPushMatrix();
		ofTranslate(mapCenterX, mapCenterY);
		ofRotateZDeg(-map.heading);
		drawScaledMap();

		ofPopMatrix();
		ofPopMatrix();

		ofSetColor(ofColor::darkSlateGrey);

		ofDrawRectangle(zoomPlusButton);
		ofDrawRectangle(zoomMinusButton);

		ofSetColor(ofColor::white);
		ofNoFill();
		ofSetLineWidth(1);
		ofDrawRectangle(zoomPlusButton);
		ofDrawRectangle(zoomMinusButton);
		ofFill();

		ofSetColor(ofColor::white);

		ofPushMatrix();
		ofTranslate(SCREEN_WIDTH * 0.115, SCREEN_HEIGHT * 0.71);

		fontButton->drawString("+", -fontButton->stringWidth("+") * 0.5, fontButton->stringHeight("+") * 0.5);

		ofTranslate(0, SCREEN_HEIGHT*0.17);
		fontButton->drawString("-", -fontButton->stringWidth("+") * 0.5, fontButton->stringHeight("-") * 0.5);
	
		ofPopMatrix();

	}

	void mouseMoved(int x, int y) override {
		
	}

	void mouseDragged(int x, int y, int button) override {
		
	}

	void mousePressed(int x, int y, int button) override {
		
		if (zoomPlusButton.inside(x, y))
		{
			mapZoom::getInstance().setValue(mapZoom::getInstance().getValue() + 1);
		}

		if (zoomMinusButton.inside(x, y))
		{
			mapZoom::getInstance().setValue(mapZoom::getInstance().getValue() - 1);
		}

	}

	void mouseReleased(int x, int y, int button) override {
		
	}

	void keyPressed(int key) override {

	}
	
#pragma endregion 

};

class MapScene : public BaseScene {
public:

	ofImage image;
	ofURLFileLoader loader;

	Map map;

	ofRectangle zoomPlusButton;
	ofRectangle zoomMinusButton;

	ofTrueTypeFont fontLarge;
	ofTrueTypeFont fontMedium;
	ofTrueTypeFont fontButton;

	void setup() override {

		map.setup(true);
		
		fontLarge.load("fonts/Nord-Regular.ttf", 35, true, true);
		fontMedium.load("fonts/Nord-Regular.ttf", 28, true, true);
		fontButton.load("fonts/Nord-Regular.ttf", 45, true, true);

		//string url = "http://api.mapbox.com/v4/mapbox.satellite/1/0/0@2x.jpg90?access_token=pk.eyJ1Ijoic2FpZmUtZGlzcGxheSIsImEiOiJjbHhua204amowM252MmtzOWdvcm93MWlsIn0.QWvcp4fk8H9yJ0IqIavl6A";

		float plusButtonWidth = fontButton.stringWidth("+") * 2;
		float plusButtonHeight = fontButton.stringHeight("+") * 2;

		float minusButtonWidth = fontButton.stringWidth("+") * 2;
		float minusButtonHeight = fontButton.stringHeight("+") * 2;

		zoomPlusButton.setSize(plusButtonWidth, plusButtonHeight);
		zoomPlusButton.setPosition(SCREEN_WIDTH * 0.85 - plusButtonWidth * 0.5, -plusButtonHeight * 0.5 + SCREEN_HEIGHT * 0.04 + SCREEN_HEIGHT * 0.96 / 3);

		zoomMinusButton.setSize(minusButtonWidth, minusButtonHeight);
		zoomMinusButton.setPosition(SCREEN_WIDTH * 0.85 - minusButtonWidth * 0.5, - minusButtonHeight * 0.5 + SCREEN_HEIGHT * 0.04 + SCREEN_HEIGHT * 0.96 / 3 + SCREEN_HEIGHT * 0.96 / 2.5);

	}

	void update(const INPUTS& INPUT_DATA) override {
		
		map.update(INPUT_DATA);

	}

	void draw() override {

		ofBackground(ofColor::darkSlateGrey);

		ofSetColor(ofColor::darkGrey);
		ofDrawRectRounded(zoomPlusButton, 4);
		ofDrawRectRounded(zoomMinusButton, 4);

		ofSetColor(ofColor::white);
		ofNoFill();
		ofSetLineWidth(2);
		ofDrawRectRounded(zoomPlusButton, 4);
		ofDrawRectRounded(zoomMinusButton, 4);
		ofFill();

		ofPushMatrix();

		ofTranslate(SCREEN_WIDTH*0.85, SCREEN_HEIGHT*0.12);

		float zoomBoxWidth = SCREEN_WIDTH * 0.105;
		float zoomBoxHeight = SCREEN_HEIGHT * 0.06;

		int zoom = mapZoom::getInstance().getValue();

		ofSetColor(ofColor::darkGrey);
		ofDrawRectRounded(-zoomBoxWidth * 0.5, -zoomBoxHeight * 0.5, zoomBoxWidth, zoomBoxHeight, 4);

		ofSetColor(ofColor::white);
		ofNoFill();
		ofSetLineWidth(1);
		ofDrawRectRounded(-zoomBoxWidth * 0.5, -zoomBoxHeight * 0.5, zoomBoxWidth, zoomBoxHeight, 4);
		ofFill();

		ofSetColor(ofColor::darkBlue);
		fontMedium.drawString("ZOOM:  " + ofToString(zoom), -zoomBoxWidth * 0.47, fontMedium.stringHeight("ZOOM:  " + ofToString(zoom)) * 0.5);

		ofPopMatrix();

		ofPushMatrix();

		ofTranslate(SCREEN_WIDTH * 0.85, SCREEN_HEIGHT * 0.04 + SCREEN_HEIGHT * 0.96 / 3);
		
		ofSetColor(ofColor::darkSlateBlue);
		fontButton.drawString("+", -fontButton.stringWidth("+") * 0.5, + fontButton.stringHeight("+")*0.6);

		ofTranslate(0,SCREEN_HEIGHT * 0.96 / 2.5);

		ofSetColor(ofColor::darkSlateBlue);
		fontButton.drawString("-", -fontButton.stringWidth("-") * 0.5, + fontButton.stringHeight("+")*0.6);

		ofPopMatrix();

		ofPushMatrix();

		ofTranslate(SCREEN_WIDTH * 0.5, SCREEN_HEIGHT * 0.5 + ofGetScreenHeight()*0.02);
		ofRotateZDeg(-map.heading);
		ofScale(0.93, 0.93, 1);

		ofSetColor(ofColor::white);

		ofPushMatrix();

		fontLarge.drawString("N", -fontLarge.stringWidth("N") * 0.5, -map.maskRadius*1.05);
		ofRotateZDeg(90);

		fontLarge.drawString("E", -fontLarge.stringWidth("E") * 0.5, -map.maskRadius*1.05);
		ofRotateZDeg(90);

		fontLarge.drawString("S", -fontLarge.stringWidth("S") * 0.5, -map.maskRadius*1.05);
		ofRotateZDeg(90);

		fontLarge.drawString("W", -fontLarge.stringWidth("W") * 0.5, -map.maskRadius*1.05);
		ofPopMatrix();
		
		map.drawMaskedMap();
		
		ofPopMatrix();

		ofPushMatrix();

		ofTranslate(SCREEN_WIDTH * 0.02, SCREEN_HEIGHT * 0.12);

		ofSetColor(ofColor::white);	
		fontLarge.drawString("Latitude: " + ofToString(map.latitude), 0, fontLarge.stringHeight("Latitude: " + ofToString(map.latitude)) * 0.5);
		fontLarge.drawString("Longitude: " + ofToString(map.longitude), 0, fontLarge.stringHeight("Longitude: " + ofToString(map.longitude)) * 0.5 + SCREEN_HEIGHT*0.08);

		ofPopMatrix();
	}

	void keyPressed(int key) override {

	}

	void mouseMoved(int x, int y) override {
		// Handle mouse moved in Scene1
	}

	void mouseDragged(int x, int y, int button) override {
		// Handle mouse dragged in Scene1
	}

	void mousePressed(int x, int y, int button) override {

		if (zoomPlusButton.inside(x, y))
		{
			mapZoom::getInstance().setValue(mapZoom::getInstance().getValue() + 1);
		}

		if (zoomMinusButton.inside(x, y))
		{
			mapZoom::getInstance().setValue(mapZoom::getInstance().getValue() - 1);
		}

	}

	void mouseReleased(int x, int y, int button) override {
		// Handle mouse released in Scene1
	}

	void fetchMapTile(string url)
	{
		ofHttpResponse response = ofLoadURL(url);

		if (response.status == 200) {
			ofBuffer buffer = response.data;
			image.load(buffer);
		}
		else {
			ofLogError("Failed to fetch map tile: " + response.error);
		}
	}

};

class DataScene : public BaseScene {
public:
#pragma region variables declaration

	ofxIniSettings settings;

	MeasurementSystem measurementSystem = METRIC;

	string gpsTimeLabel = "TIME";
	string internalClockLabel = "INT. CLOCK";

	string outsideAirTemperatureLabel = "OAT";
	string staticPressureLabel = "STATIC PRESS.";
	string pressureAltitudeLabel = "Press. ALTITUDE";
	string dynamicPressureLabel = "DYN PRESS.";
	string indicatedAirspeedLabel = "IAS";
	string calibratedAirspeedLabel = "CAS";
	string indicatedAltitudeLabel = "IND. ALTITUDE";
	string trueAirspeedLabel = "TAS";
	string aoaAsseLabel = "AOA (ASSE)";
	string aosAsseLabel = "AOS (ASSE)";
	string aoaVaneLabel = "AOA (VANE)";
	string aosVaneLabel = "AOS (VANE)";
	string rollLabel = "ROLL";
	string pitchLabel = "PITCH";
	string yawLabel = "YAW";
	string tasDotLabel = "TAS DOT";
	string verticalSpeedLabel = "CLIMB RATE";
	string magHeadingLabel = "MAG. HEADING";
	string magRollLabel = "MAG. ROLL";
	string magPitchLabel = "MAG PITCH";
	string rollRateLabel = "ROLL RATE";
	string pitchRateLabel = "PITCH RATE";
	string yawRateLabel = "YAW RATE";
	string bodyAccXLabel = "X ACC.";
	string bodyAccYLabel = "Y ACC.";
	string bodyAccZLabel = "Z ACC.";
	string latitudeLabel = "LATITUDE";
	string longitudeLabel = "LONGITUDE";
	string gpsHeightLabel = "GPS HEIGHT";
	string gpsVelNorthLabel = "GPS VEL. NORTH";
	string gpsVelEastLabel = "GPS VEL. EAST";
	string gpsVelUpLabel = "GPS VEL. UP";
	string rawStaticPressureLabel = "RAW ST. PRESS.";
	string rawDynamicPressureLabel = "RAW. DYN. PRESS";

	std::vector<std::pair<std::string, std::string>> orderedADC;
	std::vector<std::pair<std::string, std::string>> orderedGPS;
	std::vector<std::pair<std::string, std::string>> orderedAHRS;

	ofTrueTypeFont fontSmall;
	ofTrueTypeFont fontMedium;
	ofTrueTypeFont fontLarge;

	ofxHistoryPlot* plotVaneAOA;
	ofxHistoryPlot* plotVaneAOS;
	ofxHistoryPlot* plotASSEAOA;
	ofxHistoryPlot* plotASSEAOS;

	float aoaVane;
	float aoaASSE;
	float aosVane;
	float aosASSE;

	float sceneHeight = SCREEN_HEIGHT * 0.96;

	float panelWidth = SCREEN_WIDTH * 0.5;
	float panelHeight = sceneHeight * 0.5;

	float headerBoxWidth = panelWidth;
	float headerBoxHeight = SCREEN_HEIGHT * 0.05;

	float textBoxWidth = panelWidth * 0.5;
	float textBoxHeight = (panelHeight - headerBoxHeight) / 7.0f;

	int columns = 2;

	ofRectangle flagButton;

	ofVec2f TOP_LEFT = ofVec2f(ofGetWidth() * 0.25, sceneHeight * 0.25 + SCREEN_HEIGHT * 0.04);
	ofVec2f TOP_RIGHT = ofVec2f(ofGetWidth() * 0.75, sceneHeight * 0.25 + SCREEN_HEIGHT * 0.04);
	ofVec2f BOTTOM_LEFT = ofVec2f(ofGetWidth() * 0.25, sceneHeight * 0.75 + SCREEN_HEIGHT * 0.04);
	ofVec2f BOTTOM_RIGHT = ofVec2f(ofGetWidth() * 0.75, sceneHeight * 0.75 + SCREEN_HEIGHT * 0.04);

	unsigned long gpsTimeSeconds;


#pragma endregion
#pragma region main loop

	void setup() override {

		settings.load(ofToDataPath("settings.ini"));
		measurementSystem = settings.getInt("DATA", "MEASUREMENT_SYSTEM") == 1 ? IMPERIAL : METRIC;
	

		fontSmall.load(ofToDataPath("fonts/Nord-Regular.ttf"), 12, true, true);
		fontMedium.load(ofToDataPath("fonts/Nord-Regular.ttf"), 16, true, true);
		fontLarge.load(ofToDataPath("fonts/Nord-Regular.ttf"), 20, true, true);

		flagButton.setSize(panelHeight * 0.25, panelHeight * 0.15);
		flagButton.setPosition(SCREEN_WIDTH - panelHeight * 0.3, SCREEN_HEIGHT - panelHeight * 0.21);

		float numSamples = 3600;

		plotVaneAOA = new ofxHistoryPlot(&aoaVane, "AoA - ASSE VS VANE", numSamples, true);	//true for autoupdate
		plotVaneAOA->setLowerRange(0); //set only the lowest part of the range upper is adaptative to curve
		plotVaneAOA->addHorizontalGuide(0, ofColor(255));
		plotVaneAOA->setDrawTitle(true);
		plotVaneAOA->setRange(-35, 35);
		plotVaneAOA->setAutoRangeShrinksBack(false); //plot scale can shrink back after growing if plot curves requires it
		plotVaneAOA->setColor(ofColor::white);
		plotVaneAOA->setBackgroundColor(ofColor(0, 0, 0));
		plotVaneAOA->setShowNumericalInfo(true);
		plotVaneAOA->setRespectBorders(true);
		plotVaneAOA->setLineWidth(1);
		plotVaneAOA->setDrawFromRight(true);
		plotVaneAOA->setShowSmoothedCurve(true); //plot a smoothed version of the values, but alos the original in lesser alpha
		plotVaneAOA->setSmoothFilter(0.1); //smooth filter strength
		plotVaneAOA->setCropToRect(true);
		plotVaneAOA->update(0);

		plotASSEAOA = new ofxHistoryPlot(&aoaASSE, "", numSamples, true);	//true for autoupdate
		plotASSEAOA->setLowerRange(0); //set only the lowest part of the range upper is adaptative to curve
		plotASSEAOA->addHorizontalGuide(0, ofColor(255));
		plotASSEAOA->setDrawTitle(false);
		plotASSEAOA->setRange(-35, 35);
		plotASSEAOA->setAutoRangeShrinksBack(false); //plot scale can shrink back after growing if plot curves requires it
		plotASSEAOA->setColor(ofColor::yellow);
		plotASSEAOA->setBackgroundColor(ofColor(0, 0, 0,0));
		plotASSEAOA->setShowNumericalInfo(true);
		plotASSEAOA->setRespectBorders(true);
		plotASSEAOA->setLineWidth(1);
		plotASSEAOA->setDrawFromRight(true);
		plotASSEAOA->setShowSmoothedCurve(true); //plot a smoothed version of the values, but alos the original in lesser alpha
		plotASSEAOA->setSmoothFilter(0.1); //smooth filter strength
		plotASSEAOA->setCropToRect(true);
		plotASSEAOA->update(0);

		plotVaneAOS = new ofxHistoryPlot(&aosVane, "AOS - ASSE VS VANE", numSamples, true);	//true for autoupdate
		plotVaneAOS->setLowerRange(0); //set only the lowest part of the range upper is adaptative to curve
		plotVaneAOS->addHorizontalGuide(0, ofColor(255));
		plotVaneAOS->setDrawTitle(true);
		plotVaneAOS->setRange(-35, 35);
		plotVaneAOS->setAutoRangeShrinksBack(false); //plot scale can shrink back after growing if plot curves requires it
		plotVaneAOS->setColor(ofColor::white);
		plotVaneAOS->setBackgroundColor(ofColor(0, 0, 0));
		plotVaneAOS->setShowNumericalInfo(true);
		plotVaneAOS->setRespectBorders(true);
		plotVaneAOS->setLineWidth(1);
		plotVaneAOS->setDrawFromRight(true);
		plotVaneAOS->setShowSmoothedCurve(true); //plot a smoothed version of the values, but alos the original in lesser alpha
		plotVaneAOS->setSmoothFilter(0.1); //smooth filter strength
		plotVaneAOS->setCropToRect(true);
		plotVaneAOS->update(0);

		plotASSEAOS = new ofxHistoryPlot(&aosASSE, "", numSamples, true);	//true for autoupdate
		plotASSEAOS->setLowerRange(0); //set only the lowest part of the range upper is adaptative to curve
		plotASSEAOS->addHorizontalGuide(0, ofColor(255));
		plotASSEAOS->setDrawTitle(false);
		plotASSEAOS->setRange(-35, 35);
		plotASSEAOS->setAutoRangeShrinksBack(false); //plot scale can shrink back after growing if plot curves requires it
		plotASSEAOS->setColor(ofColor::yellow);
		plotASSEAOS->setBackgroundColor(ofColor(0, 0, 0, 0));
		plotASSEAOS->setShowNumericalInfo(true);
		plotASSEAOS->setRespectBorders(true);
		plotASSEAOS->setLineWidth(1);
		plotASSEAOS->setDrawFromRight(true);
		plotASSEAOS->setShowSmoothedCurve(true); //plot a smoothed version of the values, but alos the original in lesser alpha
		plotASSEAOS->setSmoothFilter(0.1); //smooth filter strength
		plotASSEAOS->setCropToRect(true);
		plotASSEAOS->update(0);

		plotVaneAOA->setDrawFromRight(false);
		plotASSEAOA->setDrawFromRight(false);

		plotVaneAOS->setDrawFromRight(false);
		plotASSEAOS->setDrawFromRight(false);

	}

	void update(const INPUTS& INPUT_DATA) override {

		aoaVane = INPUT_DATA.ADC_OUT_AOA_VANE.value;
		aoaASSE = INPUT_DATA.ADC_OUT_AOA.value;

		aosVane = INPUT_DATA.ADC_OUT_AOS_VANE.value;
		aosASSE = INPUT_DATA.ADC_OUT_AOS.value;

		switch (measurementSystem)
		{
		case METRIC : 

			orderedADC.clear();

			orderedADC.push_back({ outsideAirTemperatureLabel, ofToString(ofClamp(INPUT_DATA.ADC_OUT_OAT.value, INPUT_DATA.ADC_OUT_OAT.min, INPUT_DATA.ADC_OUT_OAT.max)) + " C" });
			orderedADC.push_back({ staticPressureLabel, ofToString(ofClamp(INPUT_DATA.ADC_OUT_STATICPRESS.value, INPUT_DATA.ADC_OUT_STATICPRESS.min, INPUT_DATA.ADC_OUT_STATICPRESS.max)) + " Pa" });
			orderedADC.push_back({ pressureAltitudeLabel, ofToString(ofClamp(INPUT_DATA.ADC_OUT_PRESSALT.value, INPUT_DATA.ADC_OUT_PRESSALT.min, INPUT_DATA.ADC_OUT_PRESSALT.max)) + " M" });
			orderedADC.push_back({ dynamicPressureLabel, ofToString(ofClamp(INPUT_DATA.ADC_OUT_DYNPRESS.value, INPUT_DATA.ADC_OUT_DYNPRESS.min, INPUT_DATA.ADC_OUT_DYNPRESS.max)) + " Pa" });
			orderedADC.push_back({ rawStaticPressureLabel, ofToString(ofClamp(INPUT_DATA.ADC_OUT_STATICPRESS_LOC.value, INPUT_DATA.ADC_OUT_STATICPRESS_LOC.min, INPUT_DATA.ADC_OUT_STATICPRESS_LOC.max)) + " PA" });
			orderedADC.push_back({ rawDynamicPressureLabel, ofToString(ofClamp(INPUT_DATA.ADC_OUT_DYNPRESS_LOC.value, INPUT_DATA.ADC_OUT_DYNPRESS_LOC.min, INPUT_DATA.ADC_OUT_DYNPRESS_LOC.max)) + " PA" });
			orderedADC.push_back({ indicatedAltitudeLabel, ofToString(ofClamp(INPUT_DATA.ADC_OUT_INDPRESSALT.value, INPUT_DATA.ADC_OUT_INDPRESSALT.min, INPUT_DATA.ADC_OUT_INDPRESSALT.max)) + " M" });
			orderedADC.push_back({ calibratedAirspeedLabel, ofToString(ofClamp(INPUT_DATA.ADC_OUT_IAS.value, INPUT_DATA.ADC_OUT_IAS.min, INPUT_DATA.ADC_OUT_IAS.max)) + " M/S" });
			orderedADC.push_back({ indicatedAirspeedLabel, ofToString(ofClamp(INPUT_DATA.ADC_OUT_CAS.value, INPUT_DATA.ADC_OUT_CAS.min, INPUT_DATA.ADC_OUT_CAS.max)) + " M/S" });
			orderedADC.push_back({ trueAirspeedLabel, ofToString(ofClamp(INPUT_DATA.ADC_OUT_TAS.value, INPUT_DATA.ADC_OUT_TAS.min, INPUT_DATA.ADC_OUT_TAS.max)) + " M/S" });
			orderedADC.push_back({ aoaAsseLabel, ofToString(ofClamp(INPUT_DATA.ADC_OUT_AOA.value, INPUT_DATA.ADC_OUT_AOA.min, INPUT_DATA.ADC_OUT_AOA.max)) + " DEG" });
			orderedADC.push_back({ aosAsseLabel, ofToString(ofClamp(INPUT_DATA.ADC_OUT_AOS.value, INPUT_DATA.ADC_OUT_AOS.min, INPUT_DATA.ADC_OUT_AOS.max)) + " DEG" });
			orderedADC.push_back({ aoaVaneLabel, ofToString(ofClamp(INPUT_DATA.ADC_OUT_AOA_VANE.value, INPUT_DATA.ADC_OUT_AOA_VANE.min, INPUT_DATA.ADC_OUT_AOA_VANE.max)) + " DEG" });
			orderedADC.push_back({ aosVaneLabel, ofToString(ofClamp(INPUT_DATA.ADC_OUT_AOS_VANE.value, INPUT_DATA.ADC_OUT_AOS_VANE.min, INPUT_DATA.ADC_OUT_AOS_VANE.max)) + " DEG" });

			orderedAHRS.clear();

			orderedAHRS.push_back({ rollLabel, ofToString(ofClamp(INPUT_DATA.ADC_OUT_ROLL.value, INPUT_DATA.ADC_OUT_ROLL.min, INPUT_DATA.ADC_OUT_ROLL.max)) + " DEG" });
			orderedAHRS.push_back({ pitchLabel, ofToString(ofClamp(INPUT_DATA.ADC_OUT_PITCH.value, INPUT_DATA.ADC_OUT_PITCH.min, INPUT_DATA.ADC_OUT_PITCH.max)) + " DEG" });
			orderedAHRS.push_back({ yawLabel, ofToString(ofClamp(INPUT_DATA.ADC_OUT_YAW.value, INPUT_DATA.ADC_OUT_YAW.min, INPUT_DATA.ADC_OUT_YAW.max)) + " DEG" });
			orderedAHRS.push_back({ tasDotLabel, ofToString(ofClamp(INPUT_DATA.ADC_OUT_TASDOT.value, INPUT_DATA.ADC_OUT_TASDOT.min, INPUT_DATA.ADC_OUT_TASDOT.max)) + " M/S^2" });
			orderedAHRS.push_back({ verticalSpeedLabel, ofToString(ofClamp(INPUT_DATA.ADC_OUT_VERTICAL_SPEED.value, INPUT_DATA.ADC_OUT_VERTICAL_SPEED.min, INPUT_DATA.ADC_OUT_VERTICAL_SPEED.max)) + " M/S" });
			orderedAHRS.push_back({ magHeadingLabel, ofToString(ofClamp(INPUT_DATA.ADC_OUT_MAGHEADING.value, INPUT_DATA.ADC_OUT_MAGHEADING.min, INPUT_DATA.ADC_OUT_MAGHEADING.max)) + " DEG" });
			orderedAHRS.push_back({ magRollLabel, ofToString(ofClamp(INPUT_DATA.ADC_OUT_MAGPITCH.value, INPUT_DATA.ADC_OUT_MAGPITCH.min, INPUT_DATA.ADC_OUT_MAGPITCH.max)) + " DEG" });
			orderedAHRS.push_back({ magPitchLabel, ofToString(ofClamp(INPUT_DATA.ADC_OUT_MAGROLL.value, INPUT_DATA.ADC_OUT_MAGROLL.min, INPUT_DATA.ADC_OUT_MAGROLL.max)) + " DEG" });
			orderedAHRS.push_back({ rollRateLabel, ofToString(ofClamp(INPUT_DATA.ADC_OUT_ROLLRATE.value, INPUT_DATA.ADC_OUT_ROLLRATE.min, INPUT_DATA.ADC_OUT_ROLLRATE.max)) + " DEG/S" });
			orderedAHRS.push_back({ pitchRateLabel, ofToString(ofClamp(INPUT_DATA.ADC_OUT_PITCHRATE.value, INPUT_DATA.ADC_OUT_PITCHRATE.min, INPUT_DATA.ADC_OUT_PITCHRATE.max)) + " DEG/S" });
			orderedAHRS.push_back({ yawRateLabel, ofToString(ofClamp(INPUT_DATA.ADC_OUT_YAWRATE.value, INPUT_DATA.ADC_OUT_YAWRATE.min, INPUT_DATA.ADC_OUT_YAWRATE.max)) + " DEG/S" });
			orderedAHRS.push_back({ bodyAccXLabel, ofToString(ofClamp(INPUT_DATA.ADC_OUT_NX.value, INPUT_DATA.ADC_OUT_NX.min, INPUT_DATA.ADC_OUT_NX.max) / 9.81f) + " G" });
			orderedAHRS.push_back({ bodyAccYLabel, ofToString(ofClamp(INPUT_DATA.ADC_OUT_NY.value, INPUT_DATA.ADC_OUT_NY.min, INPUT_DATA.ADC_OUT_NY.max) / 9.81f) + " G" });
			orderedAHRS.push_back({ bodyAccZLabel, ofToString(ofClamp(INPUT_DATA.ADC_OUT_NZ.value, INPUT_DATA.ADC_OUT_NZ.min, INPUT_DATA.ADC_OUT_NZ.max) / 9.81f) + " G" });

			orderedGPS.clear();

			gpsTimeSeconds = ofClamp(INPUT_DATA.ADC_OUT_GPS_TIME.value, INPUT_DATA.ADC_OUT_GPS_TIME.min, INPUT_DATA.ADC_OUT_GPS_TIME.max);
			orderedGPS.push_back({ gpsTimeLabel , convertGPSTimeToString(gpsTimeSeconds) });
			orderedGPS.push_back({ internalClockLabel , "" });
			orderedGPS.push_back({ latitudeLabel , ofToString(ofClamp(INPUT_DATA.ADC_OUT_LAT.value, INPUT_DATA.ADC_OUT_LAT.min, INPUT_DATA.ADC_OUT_LAT.max)) + " DEG" });
			orderedGPS.push_back({ longitudeLabel , ofToString(ofClamp(INPUT_DATA.ADC_OUT_LON.value, INPUT_DATA.ADC_OUT_LON.min, INPUT_DATA.ADC_OUT_LON.max)) + " DEG" });
			orderedGPS.push_back({ gpsVelNorthLabel , ofToString(ofClamp(INPUT_DATA.ADC_OUT_VN.value, INPUT_DATA.ADC_OUT_VN.min, INPUT_DATA.ADC_OUT_VN.max)) + " M/S" });
			orderedGPS.push_back({ gpsVelEastLabel , ofToString(ofClamp(INPUT_DATA.ADC_OUT_VE.value, INPUT_DATA.ADC_OUT_VE.min, INPUT_DATA.ADC_OUT_VE.max)) + " M/S" });
			orderedGPS.push_back({ gpsVelUpLabel , ofToString(ofClamp(INPUT_DATA.ADC_OUT_VU.value, INPUT_DATA.ADC_OUT_VU.min, INPUT_DATA.ADC_OUT_VU.max)) + " M/S" });
			orderedGPS.push_back({ gpsHeightLabel , ofToString(ofClamp(INPUT_DATA.ADC_OUT_HGPS.value, INPUT_DATA.ADC_OUT_HGPS.min, INPUT_DATA.ADC_OUT_HGPS.max)) + "M" });

			break;

		case IMPERIAL :

			orderedADC.clear();

			orderedADC.push_back({ outsideAirTemperatureLabel, ofToString(ofClamp(INPUT_DATA.ADC_OUT_OAT.value, INPUT_DATA.ADC_OUT_OAT.min, INPUT_DATA.ADC_OUT_OAT.max)) + " C" });
			orderedADC.push_back({ staticPressureLabel, ofToString(ofClamp(INPUT_DATA.ADC_OUT_STATICPRESS.value, INPUT_DATA.ADC_OUT_STATICPRESS.min, INPUT_DATA.ADC_OUT_STATICPRESS.max) * PA_TO_HG_INCH)  + " HG IN" });
			orderedADC.push_back({ pressureAltitudeLabel, ofToString(ofClamp(INPUT_DATA.ADC_OUT_PRESSALT.value, INPUT_DATA.ADC_OUT_PRESSALT.min, INPUT_DATA.ADC_OUT_PRESSALT.max)*M_TO_FEET) + " FEET" });
			orderedADC.push_back({ dynamicPressureLabel, ofToString(ofClamp(INPUT_DATA.ADC_OUT_DYNPRESS.value, INPUT_DATA.ADC_OUT_DYNPRESS.min, INPUT_DATA.ADC_OUT_DYNPRESS.max)* PA_TO_HG_INCH) + " HG IN" });
			orderedADC.push_back({ rawStaticPressureLabel, ofToString(ofClamp(INPUT_DATA.ADC_OUT_STATICPRESS_LOC.value, INPUT_DATA.ADC_OUT_STATICPRESS_LOC.min, INPUT_DATA.ADC_OUT_STATICPRESS_LOC.max) * PA_TO_HG_INCH) + " HG IN" });
			orderedADC.push_back({ rawDynamicPressureLabel, ofToString(ofClamp(INPUT_DATA.ADC_OUT_DYNPRESS_LOC.value, INPUT_DATA.ADC_OUT_DYNPRESS_LOC.min, INPUT_DATA.ADC_OUT_DYNPRESS_LOC.max) * PA_TO_HG_INCH) + " HG IN" });
			orderedADC.push_back({ indicatedAltitudeLabel, ofToString(ofClamp(INPUT_DATA.ADC_OUT_INDPRESSALT.value, INPUT_DATA.ADC_OUT_INDPRESSALT.min, INPUT_DATA.ADC_OUT_INDPRESSALT.max) * M_TO_FEET) + " FEET" });
			orderedADC.push_back({ calibratedAirspeedLabel, ofToString(ofClamp(INPUT_DATA.ADC_OUT_IAS.value, INPUT_DATA.ADC_OUT_IAS.min, INPUT_DATA.ADC_OUT_IAS.max)* M_S_TO_KNOTS) + " KNOTS" });
			orderedADC.push_back({ indicatedAirspeedLabel, ofToString(ofClamp(INPUT_DATA.ADC_OUT_CAS.value, INPUT_DATA.ADC_OUT_CAS.min, INPUT_DATA.ADC_OUT_CAS.max) * M_S_TO_KNOTS) + " KNOTS" });
			orderedADC.push_back({ trueAirspeedLabel, ofToString(ofClamp(INPUT_DATA.ADC_OUT_TAS.value, INPUT_DATA.ADC_OUT_TAS.min, INPUT_DATA.ADC_OUT_TAS.max) * M_S_TO_KNOTS) + " KNOTS" });
			orderedADC.push_back({ aoaAsseLabel, ofToString(ofClamp(INPUT_DATA.ADC_OUT_AOA.value, INPUT_DATA.ADC_OUT_AOA.min, INPUT_DATA.ADC_OUT_AOA.max)) + " DEG" });
			orderedADC.push_back({ aosAsseLabel, ofToString(ofClamp(INPUT_DATA.ADC_OUT_AOS.value, INPUT_DATA.ADC_OUT_AOS.min, INPUT_DATA.ADC_OUT_AOS.max)) + " DEG" });
			orderedADC.push_back({ aoaVaneLabel, ofToString(ofClamp(INPUT_DATA.ADC_OUT_AOA_VANE.value, INPUT_DATA.ADC_OUT_AOA_VANE.min, INPUT_DATA.ADC_OUT_AOA_VANE.max)) + " DEG" });
			orderedADC.push_back({ aosVaneLabel, ofToString(ofClamp(INPUT_DATA.ADC_OUT_AOS_VANE.value, INPUT_DATA.ADC_OUT_AOS_VANE.min, INPUT_DATA.ADC_OUT_AOS_VANE.max)) + " DEG" });

			orderedAHRS.clear();

			orderedAHRS.push_back({ rollLabel, ofToString(ofClamp(INPUT_DATA.ADC_OUT_ROLL.value, INPUT_DATA.ADC_OUT_ROLL.min, INPUT_DATA.ADC_OUT_ROLL.max)) + " DEG" });
			orderedAHRS.push_back({ pitchLabel, ofToString(ofClamp(INPUT_DATA.ADC_OUT_PITCH.value, INPUT_DATA.ADC_OUT_PITCH.min, INPUT_DATA.ADC_OUT_PITCH.max)) + " DEG" });
			orderedAHRS.push_back({ yawLabel, ofToString(ofClamp(INPUT_DATA.ADC_OUT_YAW.value, INPUT_DATA.ADC_OUT_YAW.min, INPUT_DATA.ADC_OUT_YAW.max)) + " DEG" });
			orderedAHRS.push_back({ tasDotLabel, ofToString(ofClamp(INPUT_DATA.ADC_OUT_TASDOT.value, INPUT_DATA.ADC_OUT_TASDOT.min, INPUT_DATA.ADC_OUT_TASDOT.max) *M_S_TO_KNOTS) + " KN/S" });
			orderedAHRS.push_back({ verticalSpeedLabel, ofToString(ofClamp(INPUT_DATA.ADC_OUT_VERTICAL_SPEED.value, INPUT_DATA.ADC_OUT_VERTICAL_SPEED.min, INPUT_DATA.ADC_OUT_VERTICAL_SPEED.max) * M_S_TO_KNOTS) + " KNOTS" });
			orderedAHRS.push_back({ magHeadingLabel, ofToString(ofClamp(INPUT_DATA.ADC_OUT_MAGHEADING.value, INPUT_DATA.ADC_OUT_MAGHEADING.min, INPUT_DATA.ADC_OUT_MAGHEADING.max)) + " DEG" });
			orderedAHRS.push_back({ magRollLabel, ofToString(ofClamp(INPUT_DATA.ADC_OUT_MAGPITCH.value, INPUT_DATA.ADC_OUT_MAGPITCH.min, INPUT_DATA.ADC_OUT_MAGPITCH.max)) + " DEG" });
			orderedAHRS.push_back({ magPitchLabel, ofToString(ofClamp(INPUT_DATA.ADC_OUT_MAGROLL.value, INPUT_DATA.ADC_OUT_MAGROLL.min, INPUT_DATA.ADC_OUT_MAGROLL.max)) + " DEG" });
			orderedAHRS.push_back({ rollRateLabel, ofToString(ofClamp(INPUT_DATA.ADC_OUT_ROLLRATE.value, INPUT_DATA.ADC_OUT_ROLLRATE.min, INPUT_DATA.ADC_OUT_ROLLRATE.max)) + " DEG/S" });
			orderedAHRS.push_back({ pitchRateLabel, ofToString(ofClamp(INPUT_DATA.ADC_OUT_PITCHRATE.value, INPUT_DATA.ADC_OUT_PITCHRATE.min, INPUT_DATA.ADC_OUT_PITCHRATE.max)) + " DEG/S" });
			orderedAHRS.push_back({ yawRateLabel, ofToString(ofClamp(INPUT_DATA.ADC_OUT_YAWRATE.value, INPUT_DATA.ADC_OUT_YAWRATE.min, INPUT_DATA.ADC_OUT_YAWRATE.max)) + " DEG/S" });
			orderedAHRS.push_back({ bodyAccXLabel, ofToString(ofClamp(INPUT_DATA.ADC_OUT_NX.value, INPUT_DATA.ADC_OUT_NX.min, INPUT_DATA.ADC_OUT_NX.max) / 9.81f) + " G" });
			orderedAHRS.push_back({ bodyAccYLabel, ofToString(ofClamp(INPUT_DATA.ADC_OUT_NY.value, INPUT_DATA.ADC_OUT_NY.min, INPUT_DATA.ADC_OUT_NY.max) / 9.81f) + " G" });
			orderedAHRS.push_back({ bodyAccZLabel, ofToString(ofClamp(INPUT_DATA.ADC_OUT_NZ.value, INPUT_DATA.ADC_OUT_NZ.min, INPUT_DATA.ADC_OUT_NZ.max) / 9.81f) + " G" });

			orderedGPS.clear();

			gpsTimeSeconds = ofClamp(INPUT_DATA.ADC_OUT_GPS_TIME.value, INPUT_DATA.ADC_OUT_GPS_TIME.min, INPUT_DATA.ADC_OUT_GPS_TIME.max);

			orderedGPS.push_back({ gpsTimeLabel , convertGPSTimeToString(gpsTimeSeconds) });
			orderedGPS.push_back({ internalClockLabel , "" });
			orderedGPS.push_back({ latitudeLabel , ofToString(ofClamp(INPUT_DATA.ADC_OUT_LAT.value, INPUT_DATA.ADC_OUT_LAT.min, INPUT_DATA.ADC_OUT_LAT.max)) + " DEG" });
			orderedGPS.push_back({ longitudeLabel , ofToString(ofClamp(INPUT_DATA.ADC_OUT_LON.value, INPUT_DATA.ADC_OUT_LON.min, INPUT_DATA.ADC_OUT_LON.max)) + " DEG" });
			orderedGPS.push_back({ gpsVelNorthLabel , ofToString(ofClamp(INPUT_DATA.ADC_OUT_VN.value, INPUT_DATA.ADC_OUT_VN.min, INPUT_DATA.ADC_OUT_VN.max) * M_S_TO_KNOTS) + " KNOTS" });
			orderedGPS.push_back({ gpsVelEastLabel , ofToString(ofClamp(INPUT_DATA.ADC_OUT_VE.value, INPUT_DATA.ADC_OUT_VE.min, INPUT_DATA.ADC_OUT_VE.max) * M_S_TO_KNOTS) + " KNOTS" });
			orderedGPS.push_back({ gpsVelUpLabel , ofToString(ofClamp(INPUT_DATA.ADC_OUT_VU.value, INPUT_DATA.ADC_OUT_VU.min, INPUT_DATA.ADC_OUT_VU.max) * M_S_TO_KNOTS) + " KNOTS" });
			orderedGPS.push_back({ gpsHeightLabel , ofToString(ofClamp(INPUT_DATA.ADC_OUT_HGPS.value, INPUT_DATA.ADC_OUT_HGPS.min, INPUT_DATA.ADC_OUT_HGPS.max)*M_TO_FEET) + " FEET" });

			break;
		}
		
	}

	void draw() override {

		ofBackground(ofColor::darkGrey);

		ofPushMatrix();
		ofTranslate(TOP_LEFT);

		drawADCPanel();

		ofPopMatrix();

		ofPushMatrix();
		ofTranslate(TOP_RIGHT);

		drawAHRSPanel();

		ofPopMatrix();


		ofPushMatrix();
		ofTranslate(BOTTOM_LEFT);

		drawGPSPanel();

		ofPopMatrix();

		ofPushMatrix();
		ofTranslate(BOTTOM_RIGHT);

		
		drawVaneASSEPanel();

		ofPopMatrix();

		ofPushMatrix();

		ofTranslate(SCREEN_WIDTH * 0.5, sceneHeight * 0.5 + SCREEN_HEIGHT * 0.04);

		ofSetColor(ofColor::darkSlateGrey);
		ofSetLineWidth(3);
		ofDrawLine(-SCREEN_WIDTH * 0.5, 0, SCREEN_WIDTH * 0.5, 0);
		ofDrawLine(0, sceneHeight * 0.5, 0, -sceneHeight);

		ofSetLineWidth(3);
		ofNoFill();
		ofDrawRectangle(-SCREEN_WIDTH * 0.5, -sceneHeight * 0.5, SCREEN_WIDTH, sceneHeight);
		ofFill();


		ofPopMatrix();

		ofPushMatrix();
		plotVaneAOA->draw(panelWidth + panelHeight * 0.07, panelHeight + ofGetHeight() * 0.04 + headerBoxHeight + panelHeight * 0.07, panelWidth * 0.55, panelHeight * 0.35);
		plotASSEAOA->draw(panelWidth + panelHeight * 0.07, panelHeight + ofGetHeight() * 0.04 + headerBoxHeight + panelHeight * 0.07, panelWidth * 0.55, panelHeight * 0.35);

		plotVaneAOS->draw(panelWidth + panelHeight * 0.07, panelHeight + ofGetHeight() * 0.04 + headerBoxHeight + panelHeight * 0.07 + panelHeight * 0.35 + panelHeight*0.06, panelWidth * 0.55, panelHeight * 0.35);
		plotASSEAOS->draw(panelWidth + panelHeight * 0.07, panelHeight + ofGetHeight() * 0.04 + headerBoxHeight + panelHeight * 0.07 + panelHeight * 0.35 + panelHeight*0.06, panelWidth * 0.55, panelHeight * 0.35);

		ofTranslate(20 + panelWidth * 0.55 + panelWidth + panelHeight * 0.07, panelHeight + ofGetHeight() * 0.04 + headerBoxHeight + panelHeight * 0.07);

		ofSetColor(ofColor::white);
		fontMedium.drawString(aoaVaneLabel + ":   " + ofToString(aoaVane), 0, fontMedium.stringHeight(aoaVaneLabel + ":   " + ofToString(aoaVane)) );

		ofSetColor(ofColor::yellow);
		fontMedium.drawString(aoaAsseLabel + ":   " + ofToString(aoaASSE), 0, fontMedium.stringHeight(aoaAsseLabel + ":   " + ofToString(aoaASSE)) * 2.5);

		ofTranslate(0, panelHeight * 0.35 + panelHeight * 0.06);

		ofSetColor(ofColor::white);
		fontMedium.drawString(aosVaneLabel + ":   " + ofToString(aosVane), 0, fontMedium.stringHeight(aosVaneLabel + ":   " + ofToString(aosVane)));

		ofSetColor(ofColor::yellow);
		fontMedium.drawString(aosAsseLabel + ":   " + ofToString(aosASSE), 0, fontMedium.stringHeight(aosAsseLabel + ":   " + ofToString(aosASSE)) * 2.5);
		 
		ofPopMatrix();

		if (ASSE_VS_VANE_FLAG) {
			ofSetColor(ofColor::red);
		}
		else {
			ofSetColor(ofColor::black);
		}
		ofDrawRectRounded(flagButton, 5);

		ofSetColor(ofColor::white);

		fontLarge.drawString("FLAG", flagButton.getCenter().x - fontLarge.stringWidth("FLAG")*0.5, flagButton.getCenter().y + fontLarge.stringHeight("FLAG")*0.5);
		
	}

	void keyPressed(int key) override {

		if (key == 'f' || key == 'F') {

			ASSE_VS_VANE_FLAG = !ASSE_VS_VANE_FLAG;
		}
	}

	void mouseMoved(int x, int y) override {
		// Handle mouse moved in Scene1
	}

	void mouseDragged(int x, int y, int button) override {
		// Handle mouse dragged in Scene1
	}

	void mousePressed(int x, int y, int button) override {
		
		if (flagButton.inside(x, y))
		{
			ASSE_VS_VANE_FLAG = !ASSE_VS_VANE_FLAG;
		}
	}

	void mouseReleased(int x, int y, int button) override {
		// Handle mouse released in Scene1
	}

#pragma endregion

#pragma region custom methods

	void drawADCPanel() {

		drawHeader(-panelWidth * 0.5, -panelHeight * 0.5, "AIR DATA");

		int col = 0;
		int row = 0;

		float startX = -panelWidth * 0.5;
		float startY = -panelHeight * 0.5 + headerBoxHeight;

		int i = 0;
		for (const auto& kv : orderedADC)
		{
			col = i % columns;
			row = i / columns;

			drawTextBox(startX + col * textBoxWidth, startY + row * textBoxHeight, textBoxWidth, textBoxHeight, kv.first, kv.second);

			i++;
		}


	}

	void drawGPSPanel()
	{
		drawHeader(-panelWidth * 0.5, -panelHeight * 0.5, "GNSS DATA");

		int col = 0;
		int row = 0;

		float startX = - panelWidth * 0.5; 
		float startY = - panelHeight * 0.5 + headerBoxHeight;

		int i = 0;
		for (const auto& kv : orderedGPS)
		{
			col = i % columns;
			row = i / columns;

			drawTextBox(startX + col * textBoxWidth, startY + row * textBoxHeight, textBoxWidth, textBoxHeight, kv.first, kv.second);

			i++;
		}


	}

	void drawAHRSPanel()
	{
		drawHeader(-panelWidth * 0.5, -panelHeight * 0.5, "ATTITUDE & HEADING DATA");

		int col = 0;
		int row = 0;

		float startX = -panelWidth * 0.5;
		float startY = -panelHeight * 0.5 + headerBoxHeight;

		int i = 0;
		for (const auto& kv : orderedAHRS)
		{
			col = i % columns;
			row = i / columns;

			drawTextBox(startX + col * textBoxWidth, startY + row * textBoxHeight, textBoxWidth, textBoxHeight, kv.first, kv.second);

			i++;
		}

	}

	void drawVaneASSEPanel()
	{
		drawHeader(-panelWidth * 0.5, -panelHeight * 0.5, "ASSE VS VANE");
		
	}

	void drawHeader(float x, float y, string label) {


		ofSetLineWidth(3);
		ofSetColor(ofColor::darkSlateBlue);
		ofDrawRectangle(x+3, y+3, headerBoxWidth-6, headerBoxHeight-6);

		ofSetColor(ofColor::darkSlateGrey);
		ofSetLineWidth(3);
		ofNoFill();
		ofDrawRectangle(x, y, headerBoxWidth, headerBoxHeight);
		ofFill();

		ofSetColor(ofColor::white);
		fontLarge.drawString(label, x + headerBoxWidth * 0.5 - fontLarge.stringWidth(label) * 0.5, y + headerBoxHeight * 0.5 + fontLarge.stringHeight(label)*0.5);

	}

	void drawTextBox(float x, float y, float width, float height, string label, string value)
	{

		ofNoFill();
		ofSetLineWidth(2);
		ofSetColor(ofColor::darkSlateGrey);
		ofDrawRectangle(x, y, width, height);
		ofFill();

		ofSetColor(ofColor::darkBlue);
		fontMedium.drawString(label, x + 7 , y + height * 0.5 + fontMedium.stringHeight(label) * 0.5);

		ofSetColor(ofColor::black);
		fontMedium.drawString(value, x + width - fontMedium.stringWidth(value) -7, y + height * 0.5 + fontMedium.stringHeight(label) * 0.5);

	}

#pragma endregion
};

#pragma endregion 

// ofApp class
class ofApp : public ofBaseApp {
public:

	// Object to read from the INI file
	ofxIniSettings settings;

	// Classes objects inizitalization
	AttitudeIndicator attitudeIndicator;
	Compass compass;
	AirspeedIndicator airspeedIndicator;
	AltitudeIndicator altitudeIndicator;
	OtherReadings otherReadings;
	Map map;

	// Measurement system definition (Inputs are received in the metric system)
	enum MeasurementSystem { METRIC, IMPERIAL };

	// Shared pointer to the fonts to be loaded 		
	ofTrueTypeFont fontSmall;
	ofTrueTypeFont fontMedium;
	ofTrueTypeFont fontLarge;

	// Input DATA creation, as structure 
	INPUTS INPUT_DATA;

	// FakeNoise / Perlin noise parameters: duration and time variables (for testing only)
	float duration;
	float startTime;
	float elapsedTime;

	ofRectangle pfdButton, mapButton, dataButton;

	// general parameters
	int startingScene;
	float circleRes;
	float frameRate;
	float outputFrequency; // Hz

	// vector for storing the scenes.
	std::vector<BaseScene*> scenes;
	
	// enum to keep track and switch between scenes
	enum Scene currentScene;

	// output text file to save Vane and ASSE angles (AOA + AOS)
	std::ofstream outputFile;

	bool preloadMapTiles = true;

	// variables to control the frequency for writing to the file
	uint64_t lastWriteTime;
	uint64_t writeInterval;


	// ofApp methods declarations
	void setup();
	void update();
	void draw();
	void exit();


	void keyPressed(int key);
	void keyReleased(int key);
	void mouseMoved(int x, int y);
	void mouseDragged(int x, int y, int button);
	void mousePressed(int x, int y, int button);
	void mouseReleased(int x, int y, int button);
	void mouseEntered(int x, int y);
	void mouseExited(int x, int y);
	void windowResized(int w, int h);
	void dragEvent(ofDragInfo dragInfo);
	void gotMessage(ofMessage msg);
	
	// fucntion to generate a different filename each time by usging system date + time
	std::string generateFilename()
	{
		std::time_t now = std::time(nullptr);
		std::tm* ltm = std::localtime(&now);

		// Create a string stream to format the filename
		std::ostringstream oss;
		oss << "ASSE_vs_VANE_"
			<< 1900 + ltm->tm_year << "_"
			<< 1 + ltm->tm_mon << "_"
			<< ltm->tm_mday << "_"
			<< ltm->tm_hour << "_"
			<< ltm->tm_min << "_"
			<< ltm->tm_sec << ".txt";

		return oss.str();
	}
	
	// function to load general parameters and settings from the .ini file. scene and inidicator specific parameters are loeaded by the relevant classes
	void loadIni()
	{
		ofFile file(ofToDataPath("settings.ini"));

		if (file.exists())
		{
			settings.load(ofToDataPath("settings.ini"));
		}

		else
		{
			saveIni();

			settings.load(ofToDataPath("settings.ini"));

		}

		startingScene = settings.getFloat("GENERAL", "STARTING_TAB");
		
		switch (startingScene)
		{
		case(0):
			currentScene = MAP;
			break;

		case (1):
			currentScene = PFD;
			break;

		case (2): 
			currentScene = DATA;
			break;

		default:
			currentScene = PFD;
		}

		frameRate = settings.getFloat("GENERAL", "FRAME_RATE");
		circleRes = settings.getFloat("GENERAL", "CIRCLE_RES");
		outputFrequency = settings.getFloat("DATA", "OUTPUT_FREQUENCY");

		preloadMapTiles = settings.getBool("MAP", "preload_tiles");

		mapZoom::getInstance().setValueMin(settings.getInt("MAP", "MIN_ZOOM"));
		mapZoom::getInstance().setValueMax(settings.getInt("MAP", "MAX_ZOOM"));

		mapZoom::getInstance().setValue(ofClamp((settings.getInt("MAP", "STARTING_ZOOM")), mapZoom::getInstance().getValueMin(), mapZoom::getInstance().getValueMax()));

		INPUT_DATA.ADC_OUT_LAT.min = settings.getFloat("MAP", "MIN_LAT");
		INPUT_DATA.ADC_OUT_LAT.max = settings.getFloat("MAP", "MAX_LAT");
		INPUT_DATA.ADC_OUT_LON.min = settings.getFloat("MAP", "MIN_LON");
		INPUT_DATA.ADC_OUT_LON.max = settings.getFloat("MAP", "MAX_LON");

	}

	// function to create a defualt .ini file in case one is not found. Unlike loadIni, this writes the whole default .ini file.
	void saveIni()
	{
		settings.setInt("GENERAL", "STARTING_TAB", 1);
		settings.setFloat("GENERAL", "FRAME_RATE", 60);
		settings.setFloat("GENERAL", "CIRCLE_RES", 100);

		settings.setFloat("ATTITUDE INDICATOR", "CENTER_X", 0.5f);
		settings.setFloat("ATTITUDE INDICATOR", "CENTER_Y", 0.333f);
		settings.setFloat("ATTITUDE INDICATOR", "ROLL_INDICATOR_RADIUS", 0.25f);

		settings.setFloat("NAV", "CENTER_X", 0.5f);
		settings.setFloat("NAV", "CENTER_Y", 0.8f);
		settings.setFloat("NAV", "COMPASS_RADIUS", 0.190f);

		settings.setInt("AIRSPEED INDICATOR", "MEASUREMENT_SYSTEM", 1);
		settings.setFloat("AIRSPEED INDICATOR", "CENTER_X", 0.25f);
		settings.setFloat("AIRSPEED INDICATOR", "CENTER_Y", 0.333f);
		settings.setFloat("AIRSPEED INDICATOR", "WIDTH", 0.085f);
		settings.setFloat("AIRSPEED INDICATOR", "HEIGHT", 0.5f);

		settings.setInt("ALTITUDE INDICATOR", "MEASUREMENT_SYSTEM", 1);
		settings.setFloat("ALTITUDE INDICATOR", "CENTER_X", 0.75f);
		settings.setFloat("ALTITUDE INDICATOR", "CENTER_Y", 0.333f);
		settings.setFloat("ALTITUDE INDICATOR", "WIDTH", 0.08f);
		settings.setFloat("ALTITUDE INDICATOR", "HEIGHT", 0.5f);

		settings.setBool("MAP", "preload_tiles", true);
		settings.setString("MAP", "MAP_BOX TOKEN", "pk.eyJ1Ijoic2FpZmUtZGlzcGxheSIsImEiOiJjbHhua204amowM252MmtzOWdvcm93MWlsIn0.QWvcp4fk8H9yJ0IqIavl6A");
		settings.setInt("MAP", "MIN_ZOOM", 11);
		settings.setInt("MAP", "MAX_ZOOM", 17);
		settings.setInt("MAP", "STARTING_ZOOM", 12);
		settings.setFloat("MAP", "MIN_LAT", 45.018398);
		settings.setFloat("MAP", "MAX_LAT", 45.121661);
		settings.setFloat("MAP", "MIN_LON", 7.577037);
		settings.setFloat("MAP", "MAX_LON", 7.793988);
		settings.setFloat("MAP", "CENTER_X", 0.25);
		settings.setFloat("MAP", "CENTER_Y", 0.8);

		settings.setInt("OTHER_READINGS", "MEASUREMENT_SYSTEM", 1);

		settings.setInt("DATA", "MEASUREMENT_SYSTEM", 1);
		settings.setFloat("DATA", "OUTPUT_FREQUENCY", 10);

		settings.save(ofToDataPath("settings.ini"));
	}

};



