/*
 * BLEUUID.cpp
 *
 *  Created on: Jun 21, 2017
 *      Author: kolban
 */
#include "soc/soc_caps.h"
#if SOC_BLE_SUPPORTED

#include "sdkconfig.h"
#if defined(CONFIG_BLUEDROID_ENABLED)
#include <string.h>
#include <sstream>
#include <iomanip>
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include "BLEUUID.h"
#include "esp32-hal-log.h"

/**
 * @brief Copy memory from source to target but in reverse order.
 *
 * When we move memory from one location it is normally:
 *
 * ```
 * [0][1][2]...[n] -> [0][1][2]...[n]
 * ```
 *
 * with this function, it is:
 *
 * ```
 * [0][1][2]...[n] -> [n][n-1][n-2]...[0]
 * ```
 *
 * @param [in] target The target of the copy
 * @param [in] source The source of the copy
 * @param [in] size The number of bytes to copy
 */
static void memrcpy(uint8_t* target, uint8_t* source, uint32_t size) {
	assert(size > 0);
	target += (size - 1); // Point target to the last byte of the target data
	while (size > 0) {
		*target = *source;
		target--;
		source++;
		size--;
	}
} // memrcpy


/**
 * @brief Create a UUID from a string.
 *
 * Create a UUID from a string.  There will be two possible stories here.  Either the string represents
 * a binary data field or the string represents a hex encoding of a UUID.
 * For the hex encoding, here is an example:
 *
 * ```
 * "beb5483e-36e1-4688-b7f5-ea07361b26a8"
 *  0 1 2 3  4 5  6 7  8 9  0 1 2 3 4 5
 *  12345678-90ab-cdef-1234-567890abcdef
 * ```
 *
 * This has a length of 36 characters.  We need to parse this into 16 bytes.
 *
 * @param [in] value The string to build a UUID from.
 */
BLEUUID::BLEUUID(String value) {
	//Serial.printf("BLEUUID constructor from String=\"%s\"\n", value.c_str());
	m_valueSet = true;
	if (value.length() == 4) {
		m_uuid.len         = ESP_UUID_LEN_16;
		m_uuid.uuid.uuid16 = 0;
		for(int i=0;i<value.length();){
			uint8_t MSB = value.c_str()[i];
			uint8_t LSB = value.c_str()[i+1];
			
			if(MSB > '9') MSB -= 7;
			if(LSB > '9') LSB -= 7;
			m_uuid.uuid.uuid16 += (((MSB&0x0F) <<4) | (LSB & 0x0F))<<(2-i)*4;
			i+=2;	
		}
	}
	else if (value.length() == 8) {
		m_uuid.len         = ESP_UUID_LEN_32;
		m_uuid.uuid.uuid32 = 0;
		for(int i=0;i<value.length();){
			uint8_t MSB = value.c_str()[i];
			uint8_t LSB = value.c_str()[i+1];
			
			if(MSB > '9') MSB -= 7; 
			if(LSB > '9') LSB -= 7;
			m_uuid.uuid.uuid32 += (((MSB&0x0F) <<4) | (LSB & 0x0F))<<(6-i)*4;
			i+=2;
		}		
	}
	else if (value.length() == 16) {  // How we can have 16 byte length string representing 128 bit uuid??? needs to be investigated (lack of time) - maybe raw data encoded as String (128b==16B)?
		m_uuid.len = ESP_UUID_LEN_128;
		memrcpy(m_uuid.uuid.uuid128, (uint8_t*)value.c_str(), 16);
	}
	else if (value.length() == 36) {
		//log_d("36 characters:");
		// If the length of the string is 36 bytes then we will assume it is a long hex string in
		// UUID format.
		m_uuid.len = ESP_UUID_LEN_128;
		int n = 0;
		for(int i=0;i<value.length();){
			if(value.c_str()[i] == '-')
				i++;
			uint8_t MSB = value.c_str()[i];
			uint8_t LSB = value.c_str()[i+1];
			
			if(MSB > '9') MSB -= 7; 
			if(LSB > '9') LSB -= 7;
			m_uuid.uuid.uuid128[15-n++] = ((MSB&0x0F) <<4) | (LSB & 0x0F);
			i+=2;	
		}
	}
	else {
		log_e("ERROR: UUID value not 2, 4, 16 or 36 bytes");
		m_valueSet = false;
	}
} //BLEUUID(String)

/*
BLEUUID::BLEUUID(String value) {
	this.BLEUUID(String(value.c_str(), value.length()));
} //BLEUUID(String)
*/

/**
 * @brief Create a UUID from 16 bytes of memory.
 *
 * @param [in] pData The pointer to the start of the UUID.
 * @param [in] size The size of the data.
 * @param [in] msbFirst Is the MSB first in pData memory?
 */
BLEUUID::BLEUUID(uint8_t* pData, size_t size, bool msbFirst) {
	if (size != 16) {
		log_e("ERROR: UUID length not 16 bytes");
		return;
	}
	m_uuid.len = ESP_UUID_LEN_128;
	if (msbFirst) {
		memrcpy(m_uuid.uuid.uuid128, pData, 16);
	} else {
		memcpy(m_uuid.uuid.uuid128, pData, 16);
	}
	m_valueSet = true;
} // BLEUUID


/**
 * @brief Create a UUID from the 16bit value.
 *
 * @param [in] uuid The 16bit short form UUID.
 */
BLEUUID::BLEUUID(uint16_t uuid) {
	m_uuid.len         = ESP_UUID_LEN_16;
	m_uuid.uuid.uuid16 = uuid;
	m_valueSet         = true;
} // BLEUUID


/**
 * @brief Create a UUID from the 32bit value.
 *
 * @param [in] uuid The 32bit short form UUID.
 */
BLEUUID::BLEUUID(uint32_t uuid) {
	m_uuid.len         = ESP_UUID_LEN_32;
	m_uuid.uuid.uuid32 = uuid;
	m_valueSet         = true;
} // BLEUUID


/**
 * @brief Create a UUID from the native UUID.
 *
 * @param [in] uuid The native UUID.
 */
BLEUUID::BLEUUID(esp_bt_uuid_t uuid) {
	m_uuid     = uuid;
	m_valueSet = true;
} // BLEUUID


/**
 * @brief Create a UUID from the ESP32 esp_gat_id_t.
 *
 * @param [in] gattId The data to create the UUID from.
 */
BLEUUID::BLEUUID(esp_gatt_id_t gattId) : BLEUUID(gattId.uuid) {
} // BLEUUID


BLEUUID::BLEUUID() {
	m_valueSet = false;
} // BLEUUID


/**
 * @brief Get the number of bits in this uuid.
 * @return The number of bits in the UUID.  One of 16, 32 or 128.
 */
uint8_t BLEUUID::bitSize() {
	if (!m_valueSet) return 0;
	switch (m_uuid.len) {
		case ESP_UUID_LEN_16:
			return 16;
		case ESP_UUID_LEN_32:
			return 32;
		case ESP_UUID_LEN_128:
			return 128;
		default:
			log_e("Unknown UUID length: %d", m_uuid.len);
			return 0;
	} // End of switch
} // bitSize


/**
 * @brief Compare a UUID against this UUID.
 *
 * @param [in] uuid The UUID to compare against.
 * @return True if the UUIDs are equal and false otherwise.
 */
bool BLEUUID::equals(BLEUUID uuid) {
	//log_d("Comparing: %s to %s", toString().c_str(), uuid.toString().c_str());
	if (!m_valueSet || !uuid.m_valueSet) return false;

	if (uuid.m_uuid.len != m_uuid.len) {
		return uuid.toString() == toString();
	}

	if (uuid.m_uuid.len == ESP_UUID_LEN_16) {
		return uuid.m_uuid.uuid.uuid16 == m_uuid.uuid.uuid16;
	}

	if (uuid.m_uuid.len == ESP_UUID_LEN_32) {
		return uuid.m_uuid.uuid.uuid32 == m_uuid.uuid.uuid32;
	}

	return memcmp(uuid.m_uuid.uuid.uuid128, m_uuid.uuid.uuid128, 16) == 0;
} // equals


/**
 * Create a BLEUUID from a string of the form:
 * 0xNNNN
 * 0xNNNNNNNN
 * 0x<UUID>
 * NNNN
 * NNNNNNNN
 * <UUID>
 */
BLEUUID BLEUUID::fromString(String _uuid) {
	uint8_t start = 0;
	if (strstr(_uuid.c_str(), "0x") != nullptr) { // If the string starts with 0x, skip those characters.
		start = 2;
	}
	uint8_t len = _uuid.length() - start; // Calculate the length of the string we are going to use.

	if(len == 4) {
		uint16_t x = strtoul(_uuid.substring(start, start+len).c_str(), NULL, 16);
		return BLEUUID(x);
	} else if (len == 8) {
		uint32_t x = strtoul(_uuid.substring(start, start+len).c_str(), NULL, 16);
		return BLEUUID(x);
	} else if (len == 36) {
		return BLEUUID(_uuid);
	}
	return BLEUUID();
} // fromString


/**
 * @brief Get the native UUID value.
 *
 * @return The native UUID value or NULL if not set.
 */
esp_bt_uuid_t* BLEUUID::getNative() {
	//log_d(">> getNative()")
	if (m_valueSet == false) {
		log_v("<< Return of un-initialized UUID!");
		return nullptr;
	}
	//log_d("<< getNative()");
	return &m_uuid;
} // getNative


/**
 * @brief Convert a UUID to its 128 bit representation.
 *
 * A UUID can be internally represented as 16bit, 32bit or the full 128bit.  This method
 * will convert 16 or 32 bit representations to the full 128bit.
 */
BLEUUID BLEUUID::to128() {
	//log_v(">> toFull() - %s", toString().c_str());

	// If we either don't have a value or are already a 128 bit UUID, nothing further to do.
	if (!m_valueSet || m_uuid.len == ESP_UUID_LEN_128) {
		return *this;
	}

	// If we are 16 bit or 32 bit, then set the 4 bytes of the variable part of the UUID.
	if (m_uuid.len == ESP_UUID_LEN_16) {
		uint16_t temp = m_uuid.uuid.uuid16;
		m_uuid.uuid.uuid128[15] = 0;
		m_uuid.uuid.uuid128[14] = 0;
		m_uuid.uuid.uuid128[13] = (temp >> 8) & 0xff;
		m_uuid.uuid.uuid128[12] = temp & 0xff;

	}
	else if (m_uuid.len == ESP_UUID_LEN_32) {
		uint32_t temp = m_uuid.uuid.uuid32;
		m_uuid.uuid.uuid128[15] = (temp >> 24) & 0xff;
		m_uuid.uuid.uuid128[14] = (temp >> 16) & 0xff;
		m_uuid.uuid.uuid128[13] = (temp >> 8) & 0xff;
		m_uuid.uuid.uuid128[12] = temp & 0xff;
	}

	// Set the fixed parts of the UUID.
	m_uuid.uuid.uuid128[11] = 0x00;
	m_uuid.uuid.uuid128[10] = 0x00;

	m_uuid.uuid.uuid128[9]  = 0x10;
	m_uuid.uuid.uuid128[8]  = 0x00;

	m_uuid.uuid.uuid128[7]  = 0x80;
	m_uuid.uuid.uuid128[6]  = 0x00;

	m_uuid.uuid.uuid128[5]  = 0x00;
	m_uuid.uuid.uuid128[4]  = 0x80;
	m_uuid.uuid.uuid128[3]  = 0x5f;
	m_uuid.uuid.uuid128[2]  = 0x9b;
	m_uuid.uuid.uuid128[1]  = 0x34;
	m_uuid.uuid.uuid128[0]  = 0xfb;

	m_uuid.len = ESP_UUID_LEN_128;
	//log_d("<< toFull <-  %s", toString().c_str());
	return *this;
} // to128




/**
 * @brief Get a string representation of the UUID.
 *
 * The format of a string is:
 * 01234567 8901 2345 6789 012345678901
 * 0000180d-0000-1000-8000-00805f9b34fb
 * 0 1 2 3  4 5  6 7  8 9  0 1 2 3 4 5
 *
 * @return A string representation of the UUID.
 */
String BLEUUID::toString() {
	if (!m_valueSet) return "<NULL>";   // If we have no value, nothing to format.
	// If the UUIDs are 16 or 32 bit, pad correctly.

	if (m_uuid.len == ESP_UUID_LEN_16) {  // If the UUID is 16bit, pad correctly.
		char hex[9];
		snprintf(hex, sizeof(hex), "%08x", m_uuid.uuid.uuid16);
		return String(hex) + "-0000-1000-8000-00805f9b34fb";
	} // End 16bit UUID

	if (m_uuid.len == ESP_UUID_LEN_32) {  // If the UUID is 32bit, pad correctly.
		char hex[9];
		snprintf(hex, sizeof(hex), "%08lx", m_uuid.uuid.uuid32);
		return String(hex) + "-0000-1000-8000-00805f9b34fb";
	} // End 32bit UUID

	// The UUID is not 16bit or 32bit which means that it is 128bit.
	//
	// UUID string format:
	// AABBCCDD-EEFF-GGHH-IIJJ-KKLLMMNNOOPP
	auto size = 37; // 32 for UUID data, 4 for '-' delimiters and one for a terminator == 37 chars
	char *hex = (char *)malloc(size);
	snprintf(hex, size, "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
			m_uuid.uuid.uuid128[15], m_uuid.uuid.uuid128[14],
			m_uuid.uuid.uuid128[13], m_uuid.uuid.uuid128[12],
			m_uuid.uuid.uuid128[11], m_uuid.uuid.uuid128[10],
			m_uuid.uuid.uuid128[9], m_uuid.uuid.uuid128[8],
			m_uuid.uuid.uuid128[7], m_uuid.uuid.uuid128[6],
			m_uuid.uuid.uuid128[5], m_uuid.uuid.uuid128[4],
			m_uuid.uuid.uuid128[3], m_uuid.uuid.uuid128[2],
			m_uuid.uuid.uuid128[1], m_uuid.uuid.uuid128[0]);
	String res(hex);
	free(hex);
	return res;
} // toString

#endif /* CONFIG_BLUEDROID_ENABLED */
#endif /* SOC_BLE_SUPPORTED */
