#include <string>
#include <vector>
#include <iomanip>
#include <cstring>
#include <cstdint>
#include "includes/connect_code.hpp"
#include "includes/numeric_base.hpp"

namespace connect_code {

	 /* Tokenizes a string, based on the given delimiter */
	std::vector<std::string> tokenize_str(std::string str, std::string delim) {
		std::vector<std::string> result;
		char* c_str = str.data();
		char* c_delim = delim.data();

		char* tok = strtok(c_str, c_delim);
		while (tok != NULL) {
			result.push_back(std::string(tok));
			tok = strtok(NULL, c_delim);
		}

		return result;
	}

	/* Convert an IPv4 address from decimal to dotted decimal notation */
	std::string dec_to_dotted_dec(std::string addr) {
		uint32_t addr_val = std::stoul(addr); /* 32 bit address */
		uint8_t addr_1 = (addr_val & (0xFF << 24)) >> 24;   /* First octet (Bitwise AND the address with 255.0.0.0, and shift it to the right to obtain the first octet) */
		uint8_t addr_2 = (addr_val & (0xFF << 16)) >> 16;
		uint8_t addr_3 = (addr_val & (0xFF << 8)) >> 8;
		uint8_t addr_4 = (addr_val & 0xFF);

		std::string ret_val = std::string(std::to_string(addr_1) + "." + std::to_string(addr_2) + "." + std::to_string(addr_3) + "." + std::to_string(addr_4));

		return ret_val;
	}

	/* Convert an IPv4 address from dotted deecimal to decimal */
	std::string dotted_dec_to_dec(std::string addr) {
		std::vector<std::string> octets = tokenize_str(addr, ".");
		uint32_t addr_val = (std::stoul(octets[0]) << 24) + (std::stoul(octets[1]) << 16) + (std::stoul(octets[2]) << 8) + (std::stoul(octets[3]));
		return std::to_string(addr_val);
	}


	std::string encode(std::string address, std::string port) {
		/* Convert the address to decimal, and convert that to hex */
		std::string addr_coded = dotted_dec_to_dec(address);
		addr_coded = base_convert(addr_coded, 10, 32);

		/* Convert the port to hex */
		std::string port_coded = base_convert(port, 10, 32);
		std::string ret_val = addr_coded + "_" + port_coded;

		return ret_val;

	}

	std::vector<std::string> decode(std::string connect_code) {
		if (connect_code.find("_") == std::string::npos) {
			throw std::invalid_argument("Invalid code entered.");
		}
		std::vector<std::string> result = tokenize_str(connect_code, "_"); /* Split the string into address and port */
		std::string address = result[0]; /* Address (in base 16) */
		std::string port = result[1]; /* Port (in base 16) */

		/* Base 16 to base 10 - These lines convert the string to a base 10 number, and convert the result back into a string */
		address = std::to_string(std::stoul(address, 0, 32));
		port = std::to_string(std::stoul(port, 0, 32));

		/* Convert decimal address to dotted decimal */
		address = dec_to_dotted_dec(address);

		std::vector<std::string> ret_val;
		ret_val.push_back(address);
		ret_val.push_back(port);
		return ret_val;
	}
}
