/*
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 * Copyright (C) 2016 Furrtek
 *
 * This file is part of PortaPack.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street,
 * Boston, MA 02110-1301, USA.
 */

#ifndef __APRS_PACKET_H__
#define __APRS_PACKET_H__

#include <cstdint>
#include <cstddef>
#include <cctype>

#include "baseband.hpp"

namespace aprs {

const int APRS_MIN_LENGTH = 18; //14 bytes address, control byte and pid. 2 CRC.

struct aprs_pos{
	float latitude;
	float longitude;	
	uint8_t symbol_code;
	uint8_t  sym_table_id;
};

enum ADDRESS_TYPE {
	SOURCE,
	DESTINATION,
	REPEATER
};

class APRSPacket {
public:
	void set_timestamp(const Timestamp& value) {
		timestamp_ = value;
	}
	
	Timestamp timestamp() const {
		return timestamp_;
	}

	void set(const size_t index, const uint8_t data) {		
		payload[index] = data;

		if(index + 1 > payload_size){
			payload_size = index + 1;
		}
	}

	uint32_t operator[](const size_t index) const {
		return payload[index];
	}
	

	uint8_t size() const {
		return payload_size;
	}

	void set_valid_checksum(const bool valid) {
		valid_checksum = valid;
	}

	bool is_valid_checksum() const {
		return valid_checksum;
	}

	uint64_t get_source(){
		uint64_t source = 0x0;

		for(uint8_t i = SOURCE_START; i < SOURCE_START + ADDRESS_SIZE; i++){
			source |= ( ((uint64_t)payload[i]) << ((i - SOURCE_START) * 8));
		}

		return source;
	}

	std::string get_source_formatted(){
		parse_address(SOURCE_START, SOURCE);
		return std::string(address_buffer);
	}

	std::string get_destination_formatted(){
		parse_address(DESTINATION_START, DESTINATION);
		return std::string(address_buffer);
	}

	std::string get_digipeaters_formatted(){
		uint8_t position = DIGIPEATER_START;
		bool has_more = parse_address(SOURCE_START, REPEATER);

		std::string repeaters = "";
		while(has_more){			
			has_more = parse_address(position, REPEATER);	
			repeaters += std::string(address_buffer);

			position += ADDRESS_SIZE;

			if(has_more){			
				repeaters += ">";
			}		
		}

		return repeaters;
	}

	uint8_t get_number_of_digipeaters(){
		uint8_t position = DIGIPEATER_START;		
		bool has_more = parse_address(SOURCE_START, REPEATER);
		uint8_t repeaters = 0;
		while(has_more){			
			has_more = parse_address(position, REPEATER);	
			position += ADDRESS_SIZE;
			repeaters++;	
		}

		return repeaters;
	}

	uint8_t get_information_start_index(){
		return DIGIPEATER_START + (get_number_of_digipeaters() * ADDRESS_SIZE) + 2;
	}

	std::string get_information_text_formatted(){
		std::string information_text = "";
		for(uint8_t i = get_information_start_index(); i < payload_size - 2; i++){
			information_text += payload[i];
		}

		return information_text;
	}

	std::string get_stream_text(){
		std::string stream = get_source_formatted() + ">" + get_destination_formatted() + ";" + get_digipeaters_formatted() + ";" + get_information_text_formatted();

		return stream;
	}

	char get_data_type_identifier(){
		char ident = '\0';
		for(uint8_t i = get_information_start_index(); i < payload_size - 2; i++){
			ident = payload[i];
			break;
		}
		return ident;
	}

	bool has_position(){		
		char ident = get_data_type_identifier();

		return 
			ident == '!' || 
			ident == '=' || 
			ident == '/' || 
			ident == '@' || 
			ident == ';' || 
			ident == '`' ||
			ident == '\''||
			ident == 0x1d||
			ident == 0x1c;
	}

	aprs_pos get_position(){
		aprs::aprs_pos pos;

		char ident = get_data_type_identifier();
		std::string info_text = get_information_text_formatted();

		std::string lat_str, lng_str;
		char first;
		//bool supports_compression = true;
		bool is_mic_e_format = false;
		std::string::size_type start;

		switch(ident){
			case '/':
			case '@':
				start = 8;
				break;
			case '=':
			case '!':
				start = 1;
				break;
			case ';':			
				start = 18;
				//supports_compression = false;
				break;
			case '`':
			case '\'':
			case 0x1c:
			case 0x1d:
				is_mic_e_format = true;
				break;
			default:
				return pos;
		}		
		if(is_mic_e_format){
			parse_mic_e_format(pos);
		}
		else {			
			if(start < info_text.size()){
				first = info_text.at(start);

				if(std::isdigit(first)){
					if(start + 18 < info_text.size()){
						lat_str = info_text.substr(start, 8);
						pos.sym_table_id = info_text.at(start + 8);
						lng_str = info_text.substr(start + 9, 9);
						pos.symbol_code = info_text.at(start + 18);

						pos.latitude = parse_lat_str(lat_str);
						pos.longitude = parse_lng_str(lng_str);
					}
					
				}
				else {
					if(start + 9 < info_text.size()){
						pos.sym_table_id = info_text.at(start);
						lat_str = info_text.substr(start + 1, 4);
						lng_str = info_text.substr(start + 5, 4);
						pos.symbol_code = info_text.at(start + 9);

						pos.latitude = parse_lat_str_cmp(lat_str);
						pos.longitude = parse_lng_str_cmp(lng_str);	
					}		
				}
			}			
		}		

		return pos;
	}

	void clear() {
		payload_size = 0;
	}

private:
	const uint8_t DIGIPEATER_START = 14;
	const uint8_t SOURCE_START = 7;
	const uint8_t DESTINATION_START = 0;
	const uint8_t ADDRESS_SIZE = 7;

	bool valid_checksum = false;
	uint8_t payload[256];
	char address_buffer[15];
	uint8_t payload_size = 0 ;
	Timestamp timestamp_ { };

	float parse_lat_str_cmp(const std::string& lat_str){
		return 90.0 - ( (lat_str.at(0) - 33) * (91*91*91) + (lat_str.at(1) - 33) * (91*91) + (lat_str.at(2) - 33) * 91 + (lat_str.at(3)) ) / 380926.0;		
	}

	float parse_lng_str_cmp(const std::string& lng_str){
		return -180.0 + ( (lng_str.at(0) - 33) * (91*91*91) + (lng_str.at(1) - 33) * (91*91) + (lng_str.at(2) - 33) * 91 + (lng_str.at(3)) ) / 190463.0;		
	} 

	uint8_t parse_digits(const std::string& str){
		if(str.at(0) == ' '){
			return 0;
		}
		uint8_t end = str.find_last_not_of(' ') + 1;
		std::string sub = str.substr(0, end);

		if(!is_digits(sub)){
			return 0;
		}
		else {
			return std::stoi(sub);
		}
	}

	bool is_digits(const std::string& str){
		return str.find_last_not_of("0123456789") == std::string::npos;
	}

	float parse_lat_str(const std::string& lat_str){
		float lat = 0.0;

		std::string str_lat_deg = lat_str.substr(0, 2);
		std::string str_lat_min = lat_str.substr(2, 2);
		std::string str_lat_hund = lat_str.substr(5, 2);
		std::string dir = lat_str.substr(7, 1);

		uint8_t lat_deg = parse_digits(str_lat_deg);
		uint8_t lat_min = parse_digits(str_lat_min);
		uint8_t lat_hund = parse_digits(str_lat_hund);

		lat += lat_deg;
		lat += (lat_min + (lat_hund/ 100.0))/60.0;

		if(dir.c_str()[0] == 'S'){
			lat = -lat;
		}

		return lat;

	}

	float parse_lng_str(std::string& lng_str){
		float lng = 0.0;

		std::string str_lng_deg = lng_str.substr(0, 3);
		std::string str_lng_min = lng_str.substr(3, 2);
		std::string str_lng_hund = lng_str.substr(6, 2);
		std::string dir = lng_str.substr(8, 1);

		uint8_t lng_deg = parse_digits(str_lng_deg);
		uint8_t lng_min = parse_digits(str_lng_min);
		uint8_t lng_hund = parse_digits(str_lng_hund);

		lng += lng_deg;
		lng += (lng_min + (lng_hund/ 100.0))/60.0;

		if(dir.c_str()[0] == 'W'){
			lng = -lng;
		}

		return lng;

	}

	void parse_mic_e_format(aprs::aprs_pos& pos){
		std::string lat_str = "";
		std::string lng_str = "";

		bool is_north = false;
		bool is_west = false;
		uint8_t lng_offset = 0;
		for(uint8_t i = DESTINATION_START; i < DESTINATION_START + ADDRESS_SIZE - 1; i++){
			uint8_t ascii = payload[i] >> 1;

			lat_str += get_mic_e_lat_digit(ascii);

			if(i - DESTINATION_START == 3){
				lat_str += ".";
			}
			if(i - DESTINATION_START == 3){
				is_north = is_mic_e_lat_N(ascii);
			}
			if(i - DESTINATION_START == 4){
				lng_offset = get_mic_e_lng_offset(ascii);
			}
			if(i - DESTINATION_START == 5){
				is_west = is_mic_e_lng_W(ascii);
			}
		}
		if(is_north){
			lat_str += "N";
		}
		else {
			lat_str += "S";
		}

		pos.latitude = parse_lat_str(lat_str);	

		float lng = 0.0;
		uint8_t information_start = get_information_start_index() + 1;
		for(uint8_t i = information_start; i < information_start + 3 && i < payload_size - 2; i++){
			uint8_t ascii = payload[i];
			
			if(i - information_start == 0){ //deg
				ascii -=28;
				ascii += lng_offset;

				if(ascii >= 180 && ascii <= 189){
					ascii -= 80;
				}
				else if (ascii >= 190 && ascii <= 199){
					ascii -= 190;
				}

				lng += ascii;
			}	
			else if(i - information_start == 1){ //min
				ascii -= 28;
				if(ascii >= 60){
					ascii -= 60;
				}
				lng += ascii/60.0;
			}	
			else if(i - information_start == 2){ //hundredth minutes
				ascii -= 28;

				lng += (ascii/100.0)/60.0;
			}	
		}

		if(is_west){
			lng = -lng;
		}

		pos.longitude = lng;

	}

	uint8_t get_mic_e_lat_digit(uint8_t ascii){
		if(ascii >= '0' && ascii <= '9'){
			return ascii;
		}
		if(ascii >= 'A' && ascii <= 'J'){
			return ascii - 17;
		}
		if(ascii >= 'P' && ascii <='Y'){
			return ascii - 32;
		}
		if(ascii == 'K' || ascii == 'L' || ascii == 'Z'){
			return ' ';
		}

		return '\0';
	}

	bool is_mic_e_lat_N(uint8_t ascii){
		if(ascii >= 'P' && ascii <='Z'){
			return true;
		}		
		return false; //not technical definition, but the other case is invalid
	}

	bool is_mic_e_lng_W(uint8_t ascii){
		if(ascii >= 'P' && ascii <='Z'){
			return true;
		}		
		return false; //not technical definition, but the other case is invalid
	}

	uint8_t get_mic_e_lng_offset(uint8_t ascii){
		if(ascii >= 'P' && ascii <='Z'){
			return 100;
		}		
		return 0; //not technical definition, but the other case is invalid
	}

	bool parse_address(uint8_t start, ADDRESS_TYPE address_type){
		uint8_t byte = 0;
		uint8_t has_more = false;
		uint8_t ssid = 0;
		uint8_t buffer_index = 0;

		for(uint8_t i = start; i < start + ADDRESS_SIZE && i < payload_size - 2; i++){
			byte = payload[i];

			if(i - start == 6){
				 has_more = (byte & 0x1) == 0;
				 ssid = (byte >> 1) & 0x0F;

				 if(ssid != 0 || address_type == REPEATER){
				 	address_buffer[buffer_index++] = '-';				 	

				 	if(ssid < 10){
				 		address_buffer[buffer_index++] = '0' + ssid;				 		
				 		address_buffer[buffer_index++] = '\0';
				 	}
				 	else {
				 		address_buffer[buffer_index++] = '1';
				 		address_buffer[buffer_index++] = '0' + ssid - 10;
				 		address_buffer[buffer_index++] = '\0';
				 	}
				 }
				 else {
					 address_buffer[buffer_index++] = '\0';
				 }
			}
			else {
				byte >>= 1;

				if(byte != ' '){
					address_buffer[buffer_index++] = byte;		
				}			
			}
		}

		return has_more;
	}
};

} /* namespace aprs */

#endif/*__APRS_PACKET_H__*/