/*
 * aprsis.c
 *
 *  Created on: Feb 20, 2022
 *      Author: mateusz
 */

#include "aprsis.h"
#include "etc/aprsis_config.h"
#include "text.h"
#include "backup_registers.h"

#include "aprs/status.h"
#include "aprs/message.h"

#include "gsm/sim800c.h"
#include "gsm/sim800c_poolers.h"

#include "main.h"
#include "rte_main.h"

#include <stdio.h>
#include <string.h>

#ifdef UNIT_TEST
#define STATIC
#else
#define STATIC static
#endif

srl_context_t * aprsis_serial_port;

/**
 * Pointer to used gsm_modem_state
 */
gsm_sim800_state_t * aprsis_gsm_modem_state;

/**
 * Size of transmit buffer
 */
#define APRSIS_TX_BUFFER_LN	512

/**
 * Buffer for sending packet to aprs-is
 */
static char aprsis_packet_tx_buffer[APRSIS_TX_BUFFER_LN];

/**
 * Lenght of buffer
 */
#define APRSIS_TELEMETRY_BUFFER_LN	96

/**
 * Buffer used to sent telemetry frames to APRS-IS. It is also
 * used to construct frame with GPS connection status, which is sent
 * only once after APRS-IS connection is established
 */
char aprsis_packet_telemetry_buffer[APRSIS_TELEMETRY_BUFFER_LN];

/**
 *
 */
uint16_t aprsis_packet_tx_message_size = 0;

/**
 * Passocde to APRS-IS server
 */
int32_t aprsis_passcode;

/**
 * Buffer for generate and then permanently store login string
 */
char aprsis_login_string[64];

/**
 * Default APRS-IS address to be used by 'aprsis_connect_and_login_default' function
 */
const char * aprsis_default_server_address;

/**
 * String with a callsign and a ssid in aprsis format. Used sent auto beacon
 * when connection to APRS-IS server is established
 */
const char * aprsis_callsign_with_ssid;

/**
 * Pointer to callsign from configuration
 */
const char * aprsis_callsign;

/**
 * ssid from configuration
 */
uint8_t aprsis_ssid;

/**
 * Lenght of APRS-IS server address string
 */
uint16_t aprsis_default_server_address_ln = 0;

/**
 * TCP port used to connect to default APRS-IS server.
 */
uint16_t aprsis_default_server_port;

/**
 * Set to one if connections is established AND user is logged
 */
uint8_t aprsis_logged = 0;

/**
 * Set to one if connection to server is established (but maybe not logged)
 */
uint8_t aprsis_connected = 0;

const char * aprsis_sucessfull_login = "# logresp\0";

static uint8_t aprsis_successfull_conn_counter = 0;

/**
 * Counter of unsuccessful connects to APRS-IS, to trigger GSM modem reset.
 * Please note that it works differently that 'aprsis_reset_on_timeout' and
 * has nothing to do with a timeout of already established connection. This
 * counter will trigger GSM modem reset even if no APRS-IS connection has
 * been established at all. It protects against a situation when GSM modem
 * is not able to register in cellular network, SIM card is not working for
 * some reason etc. Of course there is no guarantee that a reset in such
 * case will help, but there is nothing better to do.
 */
static uint8_t aprsis_unsucessfull_conn_counter = 0;

/**
 * Set to one if the GSM modem shall be immediately reset when APRS-IS communication
 * time out. Please note that this is different that 'aprsis_unsucessfull_conn_counter'
 * and reseting GSM mode after many unsuccessfull connection attempts! This
 * comes in when the connection has been established at least one time. It won't
 * help if there is some problem with establishing connection at all.
 */
static uint32_t aprsis_reset_on_timeout = 0;

/**
 * Number of RF packets igated to APRS-IS system
 */
static uint16_t aprsis_igated_counter = 0;

/**
 * Counter of all packets originated from the station transmitted to APRS-IS server.
 * This doesn't include igated packets!
 */
static uint16_t aprsis_tx_counter = 0;

/**
 * Amount of keepalive packet received from the server. It is reset to zero
 * every connect event
 */
static uint16_t aprsis_keepalive_received_counter = 0;

/**
 * Amount of packets which are not keepalive
 */
static uint16_t aprsis_another_received_counter = 0;

/**
 * A timestamp when server has send anything
 */
static uint32_t aprsis_last_keepalive_ts = 0;

/**
 * This is the second timestamp of last keepalive message
 * from APRS-IS server. It is used by 'aprsis_check_connection_attempt_alive'
 * and not incremented anywhere except receive callback. Where a timeout
 * calculated using this value is too long the controller is restarted.
 */
static uint32_t aprsis_last_keepalive_long_ts = 0;

/**
 * A timestamp when any packet has been sent to
 */
static uint32_t aprsis_last_packet_transmit_ts = 0;

/**
 *
 */
static uint32_t aprsis_last_packet_transmit_long_ts = 0;

/**
 * Only for debugging purposes
 */
static uint8_t aprsis_debug_simulate_timeout = 0;

#define APRSIS_LOGIN_STRING_RECEIVED_LN	64

char aprsis_login_string_reveived[APRSIS_LOGIN_STRING_RECEIVED_LN];

#define APRSIS_TIMEOUT_MS	123000//123000

/**
 * Lenght of 6 letter callsign + two digit SSID + end character like '>' or ","
 */
#define MAXIMUM_CALL_SSID_DASH_LN	10

/**
 * Checks if data in a buffer contains APRS message
 * @param message
 * @param message_ln
 * @return position at which content of message starts
 */
STATIC uint16_t aprsis_check_is_message(const uint8_t * const message, const uint16_t message_ln) {
	// example message
	//			Details:"SP8EBC>APX216,TCPIP*,qAC,NINTH::SR9WXZ   :tedt{0s}\r\n", '\0' <repeats 715 times>

	// go through a buffer and look for double ':'

	uint16_t message_start_position = 0;

	for (int i = 0; i < message_ln; i++) {
		const uint8_t * this_character = message + i;

		const uint8_t * next_character = message + i + 1;

		if (*this_character == 0x00) {
			break;
		}

		if ((*this_character == ':') && (*next_character == ':')) {
			message_start_position = i + 2;
			break;
		}
	}

	return message_start_position;
}

/**
 *
 * @param srl_context
 */
STATIC void aprsis_receive_callback(srl_context_t* srl_context) {

	const uint8_t * buffer = srl_get_rx_buffer(srl_context);

	const uint16_t message_ln = srl_context->srl_rx_bytes_counter;

	// if something was actually received
	if (srl_context->srl_rx_state == SRL_RX_DONE) {
		// check if this is keepalive message
		if (*buffer == '#') {
			// set last timestamps
			aprsis_last_keepalive_ts = main_get_master_time();
			aprsis_last_keepalive_long_ts = main_get_master_time();

			// increase received keepalive counter
			aprsis_keepalive_received_counter++;

			// restart receiving from serial port
			gsm_sim800_tcpip_async_receive(aprsis_serial_port, aprsis_gsm_modem_state, 0, 61000, aprsis_receive_callback);
		}
		else {
			// check if this is an aprs message
			const int message_position = aprsis_check_is_message(buffer, message_ln);

			// if yes try to decode it
			if (message_position != 0) {

				// prevent overwriting message received from radio channel if it hasn't been serviced yet
				if (rte_main_received_message_source == MESSAGE_SOURCE_UNINITIALIZED) {

					// if decoding was successfull
					if (message_decode(buffer, message_ln, message_position, MESSAGE_SOURCE_APRSIS, &rte_main_received_message) == 0) {

						// check if it is for me
						if (message_is_for_me(aprsis_callsign, aprsis_ssid, &rte_main_received_message) == 0) {
							// set a source of this message
							rte_main_received_message_source = MESSAGE_SOURCE_APRSIS;

							// trigger preparing ACK
							rte_main_trigger_message_ack = 1;
						}


					}
				}
			}
			else {
				;
			}

			aprsis_another_received_counter++;

			gsm_sim800_tcpip_async_receive(aprsis_serial_port, aprsis_gsm_modem_state, 0, 61000, aprsis_receive_callback);

		}
	}
}

/**
 *
 * @param context
 * @param gsm_modem_state
 * @param callsign
 * @param ssid
 * @param passcode
 * @param default_server
 * @param default_port
 * @param reset_on_timeout
 * @param callsign_with_ssid
 */
void aprsis_init(
		srl_context_t * context,
		gsm_sim800_state_t * gsm_modem_state,
		const char * callsign,
		uint8_t ssid,
		uint32_t passcode,
		const char * default_server,
		const uint16_t default_port,
		uint8_t reset_on_timeout,
		const char * callsign_with_ssid) {
	aprsis_serial_port = context;

	aprsis_gsm_modem_state = gsm_modem_state;

	aprsis_passcode = (int32_t)passcode;

	aprsis_callsign_with_ssid = callsign_with_ssid;

	memset(aprsis_login_string, 0x00, 0x40);

	sprintf(aprsis_login_string, "user %s pass %ld vers ParaMETEO %s \r\n", callsign_with_ssid, aprsis_passcode, SW_VER);

	aprsis_logged = 0;

	aprsis_default_server_port = default_port;

	aprsis_default_server_address = default_server;

	aprsis_default_server_address_ln = strlen(aprsis_default_server_address);

	aprsis_reset_on_timeout = reset_on_timeout;

	aprsis_callsign = callsign;

	aprsis_ssid = ssid;

}

/**
 * Connect and login to APRS-IS server
 * @param address	ip or dns address to aprs-is server
 * @param address_ln	lenht of a buffer with an address
 * @param port	TCP port to use (typically 14580)
 * @param auto_send_beacon
 * @return
 */
aprsis_return_t aprsis_connect_and_login(const char * address, uint8_t address_ln, uint16_t port, uint8_t auto_send_beacon) {
	// this function has blocking io
	uint8_t out = APRSIS_WRONG_STATE;

	if (aprsis_logged == 1) {
		return APRSIS_ALREADY_CONNECTED;
	}

	if (aprsis_serial_port == 0 || aprsis_gsm_modem_state == 0) {
		return APRSIS_NOT_CONFIGURED;
	}

	if (*aprsis_gsm_modem_state == SIM800_ALIVE) {

		char port_str[6];

		uint8_t * receive_buff;

		int8_t retval = 0xFF;

		int offset = 0;

		aprsis_tx_counter = 0;

		aprsis_keepalive_received_counter = 0;

		aprsis_another_received_counter = 0;

		aprsis_igated_counter = 0;

		memset(port_str, 0x00, 0x6);

		memset(aprsis_login_string_reveived, 0x00, APRSIS_LOGIN_STRING_RECEIVED_LN);

		snprintf(port_str, 6, "%d", port);

		// result of a disconnecting from APRS-IS server
		sim800_return_t disconnection_result = SIM800_UNSET;

		// connecting has blocking I/O
		retval = gsm_sim800_tcpip_connect(address, address_ln, port_str, 0x6, aprsis_serial_port, aprsis_gsm_modem_state);

		// if connection was successful
		if (retval == SIM800_OK) {
			// wait for hello message '# aprsc 2.1.10-gd72a17c'
			retval = gsm_sim800_tcpip_receive(0, 0, aprsis_serial_port, aprsis_gsm_modem_state, 0, 2000);

			if (retval == 0) {
				receive_buff = srl_get_rx_buffer(aprsis_serial_port);

				// check if receive buffer starts from printable character and needs fast forward or not
				// maybe APRS-IS put a newline before hello message
				offset = text_fast_forward_to_first_printable((char*)receive_buff, srl_get_num_bytes_rxed(aprsis_serial_port));

				// if hello message has been received
				if (offset >= 0 && (*(receive_buff + offset) == '#' && *(receive_buff + offset + 1) == ' ')) {
					// send long string to server
					gsm_sim800_tcpip_write((uint8_t *)aprsis_login_string, strlen(aprsis_login_string), aprsis_serial_port, aprsis_gsm_modem_state);

					// store received login string (with an information about which server is connected now)
					strncpy(aprsis_login_string_reveived, receive_buff + offset, APRSIS_LOGIN_STRING_RECEIVED_LN);

					// wait for server response
					retval = gsm_sim800_tcpip_receive(0, 0, aprsis_serial_port, aprsis_gsm_modem_state, 0, 2000);

					if (retval == SIM800_OK) {
						receive_buff = srl_get_rx_buffer(aprsis_serial_port);

						aprsis_connected = 1;

						aprsis_successfull_conn_counter++;

						// fast forward to the beginning of a response
						offset = text_fast_forward_to_first_printable((char*)receive_buff, srl_get_num_bytes_rxed(aprsis_serial_port));

						// check if authorization has been successfull
						retval = strncmp(aprsis_sucessfull_login, (const char * )(receive_buff + offset), (size_t)9);
						if (retval == 0) {
							aprsis_logged = 1;

							// trigger GSM status APRS-IS packet, when connection is ready
							rte_main_trigger_gsm_status = 1;

							// set current timestamp as last
							aprsis_last_keepalive_ts = master_time;

							aprsis_last_keepalive_long_ts = aprsis_last_keepalive_ts;

							if (auto_send_beacon != 0) {
								aprsis_send_beacon(0, aprsis_callsign_with_ssid, main_string_latitude, main_symbol_f, main_string_longitude, main_symbol_s, main_config_data_basic);
							}

							// trigger GSM status packet
							rte_main_trigger_gsm_loginstring_packet = 1;

							// set timeout for aprs-is server
							srl_switch_timeout(aprsis_serial_port, 1, APRSIS_TIMEOUT_MS);

							// wait for consecutive data
							gsm_sim800_tcpip_async_receive(aprsis_serial_port, aprsis_gsm_modem_state, 0, 61000, aprsis_receive_callback);

							out = APRSIS_OK;

						}
						else {
							// if authoruzation wasn't successfull drop a connection
							disconnection_result = aprsis_disconnect();

							// increase failure counter
							aprsis_unsucessfull_conn_counter++;
						}
					}
				}
				else {
					disconnection_result = aprsis_disconnect();

					// increase failure counter
					aprsis_unsucessfull_conn_counter++;
				}
			}
			else {
				disconnection_result = aprsis_disconnect();

				// increase failure counter
				aprsis_unsucessfull_conn_counter++;
			}
		}

		// if a connection has been ordered to close, but there were severe errors during that
		if (disconnection_result == SIM800_TCP_CLOSE_UNCERTAIN ||
			//disconnection_result == SIM800_RECEIVING_TIMEOUT ||
			aprsis_unsucessfull_conn_counter > APRSIS_FAILED_CONN_ATTEMPTS_TO_RESET_GSM) {

			// and reset GSM modem
			gsm_sim800_reset(aprsis_gsm_modem_state);

			// reset unsuccesfull connection counter back to zero
			aprsis_unsucessfull_conn_counter = 0;
		}

	}

	return out;

}

/**
 * Connect and login to APRS-IS using default credentials using during initialization
 * @param auto_send_beacon
 * @return
 */
aprsis_return_t aprsis_connect_and_login_default(uint8_t auto_send_beacon) {

	return aprsis_connect_and_login(aprsis_default_server_address, aprsis_default_server_address_ln, aprsis_default_server_port, auto_send_beacon);
}

sim800_return_t aprsis_disconnect(void) {

	sim800_return_t out = SIM800_UNSET;

	if (aprsis_connected == 1) {

		out = gsm_sim800_tcpip_close(aprsis_serial_port, aprsis_gsm_modem_state, 0);

		aprsis_logged = 0;

		aprsis_connected = 0;
	}

	return out;
}

/**
 * Pooler function which check periodically if APRS-IS connection is alive.
 */
void aprsis_check_alive(void) {

	const uint32_t timestamp = master_time;

	uint8_t dead = 0;

	if (aprsis_debug_simulate_timeout == 1) {
		dead = 1;
	}

	if (aprsis_successfull_conn_counter > 0) {
		if (timestamp > (aprsis_last_keepalive_ts + APRSIS_TIMEOUT_MS)) {
			dead =  1;
		}

		if (timestamp > (aprsis_last_packet_transmit_ts + APRSIS_TIMEOUT_MS * 3 )) {
			dead = 1;
		}
	}

	// check if connection is alive
	if (dead == 1){
		// reset the flag
		aprsis_logged = 0;

		aprsis_connected = 0;

		aprsis_debug_simulate_timeout = 0;

		aprsis_last_keepalive_ts = master_time;

		aprsis_last_packet_transmit_ts = master_time;

		if (rte_main_curret_powersave_mode != PWSAVE_AGGRESV) {
			// send a status message that APRS-IS connectios is gone
			status_send_aprsis_timeout(aprsis_unsucessfull_conn_counter);
		}

		// check if it is intended to reset GSM modem in case of timeout
		if (aprsis_reset_on_timeout == 0) {
			// close connection with force flag as it is uncertain if a remote server
			// finished connection explicitly, or the connection is stuck for
			// some other reason
			gsm_sim800_tcpip_close(aprsis_serial_port, aprsis_gsm_modem_state, 1);
		}
		else {
			gsm_sim800_reset(aprsis_gsm_modem_state);
		}
	}
}

/**
 * This is another alive check which is fully independent from
 * if the connection has been even already established and how many times
 * it waas. The intention here is to reset the whole controller if
 * for some reason APRS-++IS connection cannot be established for very long time
 * @return
 */
int aprsis_check_connection_attempt_alive(void) {

	int out = 0;

	const uint32_t timestamp = main_get_master_time();

	if (timestamp > (aprsis_last_keepalive_long_ts + APRSIS_TIMEOUT_MS * 6)) {
		out =  1;
	}

	if (timestamp > (aprsis_last_packet_transmit_long_ts + APRSIS_TIMEOUT_MS * 6 )) {
		out = 1;
	}

	return out;
}

/**
 *
 * @param windspeed
 * @param windgusts
 * @param winddirection
 * @param temperatura
 * @param cisnienie
 * @param humidity
 * @param callsign_with_ssid
 * @param string_latitude
 * @param string_longitude
 * @param config_data_basic
 */
void aprsis_send_wx_frame(
		uint16_t windspeed,
		uint16_t windgusts,
		uint16_t winddirection,
		float temperatura,
		float cisnienie,
		uint8_t humidity,
		const char * callsign_with_ssid,
		const char * string_latitude,
		const char * string_longitude,
		const config_data_basic_t * config_data_basic) {

	if (aprsis_logged == 0) {
		return;
	}

	if (gsm_sim800_tcpip_tx_busy() == 1) {
		// will die here
		backup_assert(BACKUP_REG_ASSERT_CONCURENT_ACCES_APRSIS_WX);
	}

	aprsis_tx_counter++;

	float max_wind_speed = 0.0f, temp = 0.0f;
	uint8_t wind_speed_mph = 0, wind_gusts_mph = 0;
	uint32_t pressure = 0;

	uint16_t direction = winddirection;

	// windspeed is stored as an increment of 0.1 meters per second in 16bit unsigned integer
	temp =   ((float)windspeed / 10.0f);
	max_wind_speed =  ((float)windgusts / 10.0f);

	temp /= 0.45;																						// Konwersja na mile na godzine
	max_wind_speed /= 0.45;
	if ((temp - (short)temp) >= 0.5)												// Zaokraglanie wartosci
		/* Odejmuje od wartosci zmiennoprzecinkowej w milach nad godzine wartosc
			 po zrzutowaniu na typ short czyli z odcienta czescia po przecinku.
			 Jezeli wynik jest wiekszy albo rowny 0.5 to trzeba zaokraglic w gore */
		wind_speed_mph = (short)temp + 1;
	else
		/* A jezeli jest mniejsza niz 0.5 to zaokraglamy w dol */
		wind_speed_mph = (short)temp;
	if ((max_wind_speed - (short)max_wind_speed) >= 0.5)
		/* Analogiczna procedura ma miejsce dla porywow wiatru*/
		wind_gusts_mph = (short)max_wind_speed + 1;
	else
		wind_gusts_mph = (short)max_wind_speed;

 	pressure = (unsigned)(cisnienie * 10);

 	memset(aprsis_packet_tx_buffer, 0x00, sizeof(aprsis_packet_tx_buffer));
 	// 	  main_own_aprs_msg_len = sprintf(main_own_aprs_msg, "=%s%c%c%s%c%c %s", main_string_latitude, main_config_data_basic->n_or_s, main_symbol_f, main_string_longitude, main_config_data_basic->e_or_w, main_symbol_s, main_config_data_basic->comment);
 	aprsis_packet_tx_message_size = snprintf(
 			aprsis_packet_tx_buffer,
			APRSIS_TX_BUFFER_LN,
			"%s>AKLPRZ,qAR,%s:!%s%c%c%s%c%c%03d/%03dg%03dt%03dr...p...P...b%05ldh%02d\r\n",
			callsign_with_ssid,
			callsign_with_ssid,
			string_latitude,
			config_data_basic->n_or_s,
			'/',
			string_longitude,
			config_data_basic->e_or_w,
			'_',
			/* kierunek */direction,
			/* predkosc*/(int)wind_speed_mph,
			/* porywy */(short)(wind_gusts_mph),
			/*temperatura */(short)(temperatura*1.8+32),
			pressure,
			humidity);
 	aprsis_packet_tx_buffer[aprsis_packet_tx_message_size] = 0;

 	aprsis_last_packet_transmit_ts = main_get_master_time();

 	aprsis_last_packet_transmit_long_ts = main_get_master_time();

 	gsm_sim800_tcpip_async_write((uint8_t *)aprsis_packet_tx_buffer, aprsis_packet_tx_message_size, aprsis_serial_port, aprsis_gsm_modem_state);
}

/**
 * Sends beacon packet to APRS-IS
 * @param async zero for blocking io, which lock this function during transmission.
 * 				non zero for non blocking io, function will return immediately and sending will be done in background
 */
void aprsis_send_beacon(
		uint8_t async,
		const char * callsign_with_ssid,
		const char * string_latitude,
		char symbol_f,
		const char * string_longitude,
		char symbol_s,
		const config_data_basic_t * config_data_basic
		) {

	if (aprsis_logged == 0) {
		return;
	}

	if (gsm_sim800_tcpip_tx_busy() == 1) {
		// will die here
		backup_assert(BACKUP_REG_ASSERT_CONCURENT_ACCES_APRSIS_BEACON);
	}

	aprsis_tx_counter++;

	aprsis_packet_tx_message_size = snprintf(
			aprsis_packet_tx_buffer,
			APRSIS_TX_BUFFER_LN,
			"%s>AKLPRZ,qAR,%s:=%s%c%c%s%c%c %s\r\n",
			callsign_with_ssid,
			callsign_with_ssid,
			string_latitude,
			config_data_basic->n_or_s,
			symbol_f,
			string_longitude,
			config_data_basic->e_or_w,
			symbol_s,
			config_data_basic->comment);
	  aprsis_packet_tx_buffer[aprsis_packet_tx_message_size] = 0;

	  if (async > 0) {
		  gsm_sim800_tcpip_async_write((uint8_t *)aprsis_packet_tx_buffer, aprsis_packet_tx_message_size, aprsis_serial_port, aprsis_gsm_modem_state);
	  }
	  else {
		 	gsm_sim800_tcpip_write((uint8_t *)aprsis_packet_tx_buffer, aprsis_packet_tx_message_size, aprsis_serial_port, aprsis_gsm_modem_state);
	  }

	  aprsis_last_packet_transmit_ts = main_get_master_time();
}

/**
 * Prepare telemetry packets to be sent later to the APRS-IS. Just store a string
 * with all values, which will be then embedded into packet to be sent to APRS-IS
 * @param _telemetry_counter
 * @param _rx_pkts
 * @param _tx_pkts
 * @param _digi_pkts
 * @param _scaled_vbatt_voltage
 * @param _viscous_drop_pkts
 * @param _scaled_temperature
 * @param _telemetry_qf
 * @param _telemetry_degr
 * @param _telemetry_nav
 * @param _telemetry_pressure_qf_navaliable
 * @param _telemetry_humidity_qf_navaliable
 * @param _telemetry_anemometer_degradated
 * @param _telemetry_anemometer_navble
 * @param _telemetry_vbatt_low
 * @param _config_mode
 */
void aprsis_prepare_telemetry(
		uint16_t _telemetry_counter,
		uint8_t _rx_pkts,
		uint8_t _tx_pkts,
		uint8_t _digi_pkts,
		uint8_t _scaled_vbatt_voltage,
		uint8_t _viscous_drop_pkts,
		uint8_t _scaled_temperature,
		char _telemetry_qf,
		char _telemetry_degr,
		char _telemetry_nav,
		char _telemetry_pressure_qf_navaliable,
		char _telemetry_humidity_qf_navaliable,
		char _telemetry_anemometer_degradated,
		char _telemetry_anemometer_navble,
		char _telemetry_vbatt_low,
		const config_data_mode_t * const _config_mode) {

	// clear buffer before doin anything
	memset(aprsis_packet_telemetry_buffer, 0x00, APRSIS_TELEMETRY_BUFFER_LN);

	// string lenght returned by snprintf
	int snprintf_size = 0;

	if (_config_mode->digi_viscous == 0) {
		snprintf_size = snprintf(
				aprsis_packet_telemetry_buffer,
				APRSIS_TELEMETRY_BUFFER_LN,
				"T#%03d,%03d,%03d,%03d,%03d,%03d,%c%c%c%c%c%c%c%c\r\n",
				_telemetry_counter,
				_rx_pkts,
				_tx_pkts,
				_digi_pkts,
				_scaled_vbatt_voltage,
				_scaled_temperature,
				_telemetry_qf,
				_telemetry_degr,
				_telemetry_nav,
				_telemetry_pressure_qf_navaliable,
				_telemetry_humidity_qf_navaliable,
				_telemetry_anemometer_degradated,
				_telemetry_anemometer_navble,
				_telemetry_vbatt_low);

	}
	else {
		snprintf_size = snprintf(
				aprsis_packet_telemetry_buffer,
				APRSIS_TELEMETRY_BUFFER_LN,
				"T#%03d,%03d,%03d,%03d,%03d,%03d,%c%c%c%c%c%c%c%c\r\n",
				_telemetry_counter,
				_rx_pkts,
				_viscous_drop_pkts,
				_digi_pkts,
				_scaled_vbatt_voltage,
				_scaled_temperature,
				_telemetry_qf,
				_telemetry_degr,
				_telemetry_nav,
				_telemetry_pressure_qf_navaliable,
				_telemetry_humidity_qf_navaliable,
				_telemetry_anemometer_degradated,
				_telemetry_anemometer_navble,
				_telemetry_vbatt_low);
	}

	aprsis_packet_telemetry_buffer[snprintf_size] = 0;

}

/**
 * Sends to APRS-IS prepared telemetry frame prepared in advance
 * @param async
 * @param callsign_with_ssid
 */
void aprsis_send_telemetry(uint8_t async, const char * callsign_with_ssid) {

	// exif if APRSIS is not logged
	if (aprsis_logged == 0) {
		return;
	}

	// exit if message is empty
	if (*aprsis_packet_telemetry_buffer == 0) {
		return;
	}

	if (gsm_sim800_tcpip_tx_busy() == 1) {
		// will die here
		backup_assert(BACKUP_REG_ASSERT_CONCURENT_ACCES_APRSIS_TELEMETRY);
	}

	aprsis_tx_counter++;

	aprsis_packet_tx_message_size = snprintf(
			aprsis_packet_tx_buffer,
			APRSIS_TX_BUFFER_LN,
			"%s>AKLPRZ,qAR,%s:%s\r\n",
			callsign_with_ssid,
			callsign_with_ssid,
			aprsis_packet_telemetry_buffer);
	  aprsis_packet_tx_buffer[aprsis_packet_tx_message_size] = 0;

	  if (async > 0) {
		  gsm_sim800_tcpip_async_write((uint8_t *)aprsis_packet_tx_buffer, aprsis_packet_tx_message_size, aprsis_serial_port, aprsis_gsm_modem_state);
	  }
	  else {
		 	gsm_sim800_tcpip_write((uint8_t *)aprsis_packet_tx_buffer, aprsis_packet_tx_message_size, aprsis_serial_port, aprsis_gsm_modem_state);
	  }

	  aprsis_last_packet_transmit_ts = main_get_master_time();

	// clear buffer after it has been used
	memset(aprsis_packet_telemetry_buffer, 0x00, APRSIS_TELEMETRY_BUFFER_LN);
}

/**
 * Sends
 * @param async
 * @param what kind of telemetry description packet should be sent now
 * @param callsign_with_ssid
 * @return what kind of telemetry description should be sent next
 */
telemetry_description_t aprsis_send_description_telemetry(uint8_t async,
								const telemetry_description_t what,
								const config_data_basic_t * const config_basic,
								const config_data_mode_t * const config_mode,
								const char * callsign_with_ssid) {

	telemetry_description_t next = TELEMETRY_NOTHING;

	// check what we want to send and what
	switch (what) {
		case TELEMETRY_PV_PARM: 		next = TELEMETRY_PV_EQNS; break;
		case TELEMETRY_PV_EQNS: 		next = TELEMETRY_PV_UNIT; break;
		case TELEMETRY_PV_UNIT: 		next = TELEMETRY_NOTHING; break;
		case TELEMETRY_NORMAL_PARAM: 	next = TELEMETRY_NORMAL_EQNS; break;
		case TELEMETRY_NORMAL_EQNS: 	next = TELEMETRY_NORMAL_UNIT; break;
		case TELEMETRY_NORMAL_UNIT: 	next = TELEMETRY_NOTHING; break;
		case TELEMETRY_VISCOUS_PARAM:	next = TELEMETRY_VISCOUS_EQNS; break;
		case TELEMETRY_VISCOUS_EQNS:	next = TELEMETRY_VISCOUS_UNIT; break;
		case TELEMETRY_VISCOUS_UNIT:	next = TELEMETRY_NOTHING; break;
		case TELEMETRY_NOTHING:
		default:						next = TELEMETRY_NOTHING; break;
	}

	// exif if APRSIS is not logged
	if (aprsis_logged == 0) {
		return next;
	}

	if (gsm_sim800_tcpip_tx_busy() == 1) {
		// will die here
		backup_assert(BACKUP_REG_ASSERT_CONCURENT_ACCES_APRSIS_DESCR);
	}

	telemetry_create_description_string(config_basic, what, main_own_aprs_msg, OWN_APRS_MSG_LN);

	aprsis_tx_counter++;

	aprsis_packet_tx_message_size = snprintf(
			aprsis_packet_tx_buffer,
			APRSIS_TX_BUFFER_LN,
			"%s>AKLPRZ,qAR,%s:%s\r\n",
			callsign_with_ssid,
			callsign_with_ssid,
			main_own_aprs_msg);
	  aprsis_packet_tx_buffer[aprsis_packet_tx_message_size] = 0;

	if (async > 0) {
	  gsm_sim800_tcpip_async_write((uint8_t *)aprsis_packet_tx_buffer, aprsis_packet_tx_message_size, aprsis_serial_port, aprsis_gsm_modem_state);
	}
	else {
		gsm_sim800_tcpip_write((uint8_t *)aprsis_packet_tx_buffer, aprsis_packet_tx_message_size, aprsis_serial_port, aprsis_gsm_modem_state);
	}

	return next;
}

void aprsis_igate_to_aprsis(AX25Msg *msg, const char * callsign_with_ssid) {

	// iterator to move across tx buffer
	uint16_t tx_buffer_it = 0;

	uint16_t payload_ln = 0;

	// string lenght returned by snprintf
	int snprintf_size = 0;

	// exif if APRSIS is not logged
	if (aprsis_logged == 0 || msg == 0) {
		return;
	}

	if (gsm_sim800_tcpip_tx_busy() == 1) {
		// will die here
		backup_assert(BACKUP_REG_ASSERT_CONCURENT_ACCES_APRSIS_IGATE);
	}

	aprsis_igated_counter++;

	// prepare buffer for message
	memset(aprsis_packet_tx_buffer, 0x00, APRSIS_TX_BUFFER_LN);

	// put callsign
	strncat(aprsis_packet_tx_buffer, msg->src.call, 6);

	// put source call
	if ((msg->src.ssid & 0xF) != 0) {
		snprintf_size = snprintf(aprsis_packet_tx_buffer, MAXIMUM_CALL_SSID_DASH_LN, "%.6s-%d>", msg->src.call, msg->src.ssid & 0xF);
	}
	else {
		// callsign without SSID
		snprintf_size = snprintf(aprsis_packet_tx_buffer, MAXIMUM_CALL_SSID_DASH_LN, "%.6s>", msg->src.call);
	}

	// move iterator forward
	tx_buffer_it = (uint8_t) snprintf_size & 0xFFU;

	// put destination call - for sake of simplicity ignore SSID
	snprintf_size = snprintf(aprsis_packet_tx_buffer + tx_buffer_it, MAXIMUM_CALL_SSID_DASH_LN, "%.6s,", msg->dst.call);

	// move iterator
	tx_buffer_it += (uint8_t) snprintf_size & 0xFFU;

	// put original path
	for (int i = 0; i < msg->rpt_cnt; i++) {
		if ((msg->rpt_lst[i].ssid & 0x0F) == 0) {
			if ((msg->rpt_lst[i].ssid & 0x40) == 0x40)
				snprintf_size = sprintf(aprsis_packet_tx_buffer + tx_buffer_it, "%.6s*,", msg->rpt_lst[i].call);
			else
				snprintf_size = sprintf(aprsis_packet_tx_buffer + tx_buffer_it, "%.6s,", msg->rpt_lst[i].call);
		}
		else {
			if ((msg->rpt_lst[i].ssid & 0x40) == 0x40)
				snprintf_size = sprintf(aprsis_packet_tx_buffer + tx_buffer_it, "%.6s-%d*,", msg->rpt_lst[i].call, (msg->rpt_lst[i].ssid & 0x0F));
			else
				snprintf_size = sprintf(aprsis_packet_tx_buffer + tx_buffer_it, "%.6s-%d,", msg->rpt_lst[i].call, (msg->rpt_lst[i].ssid & 0x0F));
		}

		// move iterator
		tx_buffer_it += (uint8_t) snprintf_size & 0xFFU;
	}

	snprintf_size = snprintf(aprsis_packet_tx_buffer + tx_buffer_it, MAXIMUM_CALL_SSID_DASH_LN + 5, "qAR,%s:", callsign_with_ssid);

	// move iterator
	tx_buffer_it += (uint8_t) snprintf_size & 0xFFU;

	// cut the data field if it is too long to fit in transmission buffer
	if (msg->len + tx_buffer_it >= APRSIS_TX_BUFFER_LN) {
		payload_ln = APRSIS_TX_BUFFER_LN - tx_buffer_it - 1;	// keep one byte for null terminator
	}
	else {
		payload_ln = msg->len;
	}

	memcpy(aprsis_packet_tx_buffer + tx_buffer_it, msg->info, payload_ln);

	// move iterator forward by payload size
	tx_buffer_it += payload_ln;

	// put newline at the end
	aprsis_packet_tx_buffer[tx_buffer_it++] = '\r';
	aprsis_packet_tx_buffer[tx_buffer_it++] = '\n';

	aprsis_packet_tx_message_size = strlen(aprsis_packet_tx_buffer);


	gsm_sim800_tcpip_async_write((uint8_t *)aprsis_packet_tx_buffer, aprsis_packet_tx_message_size, aprsis_serial_port, aprsis_gsm_modem_state);


}

void aprsis_send_server_comm_counters(const char * callsign_with_ssid) {

	if (aprsis_logged == 0) {
		return;
	}

	if (gsm_sim800_tcpip_tx_busy() == 1) {
		// will die here
		backup_assert(BACKUP_REG_ASSERT_CONCURENT_ACCES_APRSIS_CNTRS);
	}

	memset (aprsis_packet_tx_buffer, 0x00, APRSIS_TX_BUFFER_LN);

	aprsis_tx_counter++;

	aprsis_packet_tx_message_size = snprintf(
									aprsis_packet_tx_buffer,
									APRSIS_TX_BUFFER_LN - 1,
									"%s>AKLPRZ,qAR,%s:>[aprsis][igated: %d][transmited: %d][keepalive: %d][another: %d]\r\n",
									callsign_with_ssid,
									callsign_with_ssid,
									aprsis_igated_counter,
									aprsis_tx_counter,
									aprsis_keepalive_received_counter,
									aprsis_another_received_counter);

 	aprsis_last_packet_transmit_ts = main_get_master_time();

 	gsm_sim800_tcpip_async_write((uint8_t *)aprsis_packet_tx_buffer, aprsis_packet_tx_message_size, aprsis_serial_port, aprsis_gsm_modem_state);
}

void aprsis_send_loginstring(const char * callsign_with_ssid, uint8_t rtc_ok, uint16_t voltage) {

	if (aprsis_logged == 0) {
		return;
	}

	if (gsm_sim800_tcpip_tx_busy() == 1) {
		// will die here
		backup_assert(BACKUP_REG_ASSERT_CONCURENT_ACCES_APRSIS_LOGINSTRING);
	}

	memset (aprsis_packet_tx_buffer, 0x00, APRSIS_TX_BUFFER_LN);

	aprsis_tx_counter++;

	aprsis_packet_tx_message_size = snprintf(
									aprsis_packet_tx_buffer,
									APRSIS_TX_BUFFER_LN - 1,
									"%s>AKLPRZ,qAR,%s:>[rtc_ok: %d][vbat: %d][aprsis]%s\r\n",
									callsign_with_ssid,
									callsign_with_ssid,
									rtc_ok,
									voltage,
									aprsis_login_string_reveived);

 	gsm_sim800_tcpip_async_write((uint8_t *)aprsis_packet_tx_buffer, aprsis_packet_tx_message_size, aprsis_serial_port, aprsis_gsm_modem_state);

}

void aprsis_send_gsm_status(const char * callsign_with_ssid) {
	if (aprsis_logged == 0) {
		return;
	}

	memset (aprsis_packet_tx_buffer, 0x00, APRSIS_TX_BUFFER_LN);

	aprsis_tx_counter++;

	// reuse a buffer for telemetry for this one occasion
	gsm_sim800_create_status(aprsis_packet_telemetry_buffer, APRSIS_TELEMETRY_BUFFER_LN);

	aprsis_packet_tx_message_size = snprintf(
									aprsis_packet_tx_buffer,
									APRSIS_TX_BUFFER_LN - 1,
									"%s>AKLPRZ,qAR,%s:%s\r\n",
									callsign_with_ssid,
									callsign_with_ssid,
									aprsis_packet_telemetry_buffer);

 	gsm_sim800_tcpip_async_write((uint8_t *)aprsis_packet_tx_buffer, aprsis_packet_tx_message_size, aprsis_serial_port, aprsis_gsm_modem_state);
}

/**
 *
 * @param message
 */
void aprsis_send_ack_for_message(const message_t * const message) {
	aprsis_packet_tx_message_size = message_create_ack_for((uint8_t*)aprsis_packet_tx_buffer, APRSIS_TX_BUFFER_LN - 1, message, MESSAGE_SOURCE_APRSIS);

 	gsm_sim800_tcpip_async_write((uint8_t *)aprsis_packet_tx_buffer, aprsis_packet_tx_message_size, aprsis_serial_port, aprsis_gsm_modem_state);

 	rte_main_received_message_source = MESSAGE_SOURCE_UNINITIALIZED;

}

#ifdef UNIT_TEST
char * aprsis_get_tx_buffer(void) {
	return aprsis_packet_tx_buffer;
}
#endif

uint8_t aprsis_get_aprsis_logged(void) {
	return aprsis_logged;
}

void aprsis_debug_set_simulate_timeout(void) {
	aprsis_debug_simulate_timeout = 1;
}
