#include "ble.h"
#include <zephyr/logging/log.h>
#include <zephyr/settings/settings.h>

#define LOG_MODULE_NAME ble_name
LOG_MODULE_REGISTER(LOG_MODULE_NAME);

static void auth_cancel(struct bt_conn *conn);
static void pairing_complete(struct bt_conn *conn, bool bonded);
static void pairing_failed(struct bt_conn *conn, enum bt_security_err reason);
static void connected(struct bt_conn *conn, uint8_t conn_err);
static void exchange_func(struct bt_conn *conn, uint8_t err, struct bt_gatt_exchange_params *params);
static void gatt_discover(struct bt_conn *conn);
static void disconnected(struct bt_conn *conn, uint8_t reason);
static void security_changed(struct bt_conn *conn, bt_security_t level, enum bt_security_err err);
static void discovery_complete(struct bt_gatt_dm *dm, void *context);
static void discovery_service_not_found(struct bt_conn *conn, void *context);
static void discovery_error(struct bt_conn *conn, int err, void *context);
static void scan_connecting_error(struct bt_scan_device_info *device_info);
static void scan_connecting(struct bt_scan_device_info *device_info,  struct bt_conn *conn);
static void scan_filter_match(struct bt_scan_device_info *device_info,
			      struct bt_scan_filter_match *filter_match,
			      bool connectable);
static bool check_blank_addr(uint8_t *addr_ptr);				  

static struct bt_conn *default_conn;
static struct bt_custom_client custom_client;
struct bt_gatt_read_params *params;
static uint8_t association_value;
static char addr[BT_ADDR_LE_STR_LEN];
static bool new_pairing = false;

#define DEVICE_NAME "ExLRT-00000"

// Specify tha callback function for when authorisation is cancelled. 
// We might be able to get rid of this as we're not using security
static struct bt_conn_auth_cb conn_auth_callbacks = 
{
	.cancel = auth_cancel,
};

/*
* These are callbacks that work with the bluetooth system and are 
* not part of the custom_client setup. So, are needed
*/
struct bt_gatt_dm_cb discovery_cb = 
{
	.completed         = discovery_complete,
	.service_not_found = discovery_service_not_found,
	.error_found       = discovery_error,
};

// Specify tha callback function for when pairing has been completed or failed
// Not really sure if these are needed as they do nothing but print a debug message
// although they could be used to flash the LED in a certain way
static struct bt_conn_auth_info_cb conn_auth_info_callbacks = 
{
	.pairing_complete = pairing_complete,
	.pairing_failed = pairing_failed
};

// Register a callback structure for connection events. How or why, who knows?
BT_CONN_CB_DEFINE(conn_callbacks) = 
{
	.connected = connected,
	.disconnected = disconnected,
	.security_changed = security_changed
};

/* This foul macro sets up the scanning apparently
* scan_filter_match is a function that is called when whatever filter
* that has been set up is matched

Parameters:

        _name – [in] Unique name for bt_scan_cb structure.

        match_fun – [in] Scan filter matched function pointer.

        no_match_fun – [in] Scan filter unmatched (the device was not found) function pointer.

        error_fun – [in] Error when connecting function pointer.

        connecting_fun – [in] Connecting data function pointer.

https://docs.nordicsemi.com/bundle/ncs-2.5.0/page/nrf/libraries/bluetooth_services/scan.html
*/
BT_SCAN_CB_INIT(scan_cb, scan_filter_match, NULL, scan_connecting_error, scan_connecting);

// Called presumably, if authorisation  was cancelled. Why would it be?
// What *is* authorisation?  
static void auth_cancel(struct bt_conn *conn)
{
	char addr[BT_ADDR_LE_STR_LEN];

	bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));

	LOG_INF("Pairing cancelled: %s", addr);
}

// Called when connected to another unit I suppose
// All this is doing is prting a message which we don't see so it's pointless
// But we might want to change the LED flash pattern here
static void pairing_complete(struct bt_conn *conn, bool bonded)
{
	char addr[BT_ADDR_LE_STR_LEN];

	bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
	if (true == new_pairing)
	{
		erase_mac_page();
		store_mac_addr(addr);
	}

	LOG_INF("Pairing completed: %s, bonded: %d", addr, bonded);
}

// Called if pairing failed  I suppose
static void pairing_failed(struct bt_conn *conn, enum bt_security_err reason)
{
	

	char addr[BT_ADDR_LE_STR_LEN];

	bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
	
	const char *reason_str = bt_security_err_to_str(reason);

	LOG_WRN("Pairing failed conn: %s, reason %d %s", addr, reason, reason_str);

}

void set_association_value(uint8_t new_value)
{

	association_value = new_value;

}


// At boot, intiialse ble stuff
void initiliase_ble(void)
{

    int err;

    err = bt_conn_auth_cb_register(&conn_auth_callbacks);
#ifdef LOGGING    
	if (err) 
	{
		LOG_ERR("Failed to register authorization callbacks.");
		return 0;
	}
#endif    

	err = bt_conn_auth_info_cb_register(&conn_auth_info_callbacks);
#ifdef LOGGING       
	if (err) 
	{
		LOG_ERR("Failed to register authorization info callbacks.\n");
		return 0;
	}
#endif
	err = bt_enable(NULL);
#ifdef LOGGING       
	if (err) 
	{
		LOG_ERR("Bluetooth init failed (err %d)", err);
		return 0;
	}    
	LOG_INF("Bluetooth initialized");
#endif    


}

// Called after we started a scan for a matching unit
static void connected(struct bt_conn *conn, uint8_t conn_err)
{
	char addr[BT_ADDR_LE_STR_LEN];
	int err;
	
	bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));

	if (conn_err != 0) 
	{
		const char * err_str = bt_hci_err_to_str(conn_err);
		LOG_INF("Failed to connect to %s, 0x%02x %s", addr, conn_err, err_str);

		if (default_conn == conn) 
		{
			bt_conn_unref(default_conn);
			default_conn = NULL;

			err = bt_scan_start(BT_SCAN_TYPE_SCAN_ACTIVE);
			if (NO_ERROR != err) 
			{
				LOG_ERR("Scanning failed to start (err %d)", err);
			}
		}
		return;
	}

	LOG_INF("Connected: %s", addr);

	static struct bt_gatt_exchange_params exchange_params;

	exchange_params.func = exchange_func;
	err = bt_gatt_exchange_mtu(conn, &exchange_params);
	if (NO_ERROR != err) 
	{
		LOG_WRN("MTU exchange failed (err %d)", err);
	}

	gatt_discover(conn);

	err = bt_scan_stop();
	if ((NO_ERROR == err) && (err != -EALREADY)) 
	{
		LOG_ERR("Stop LE scan failed (err %d)", err);
	}
}

// Goodness knows what this is about
static void exchange_func(struct bt_conn *conn, uint8_t err, struct bt_gatt_exchange_params *params)
{
	if (!err) 
	{
		LOG_INF("MTU exchange done");
	} else 
	{
		LOG_WRN("MTU exchange failed (err %" PRIu8 ")", err);
	}
}

// Called when we have connected to another unit or when security has changed,
// buut we're not using security, so how relevant that part is, is is anyone's guess
static void gatt_discover(struct bt_conn *conn)
{
	
	if (conn != default_conn) 
	{
		return;
	}

 	bt_gatt_dm_start(conn, BT_UUID_CUSTOM_SERVICE, &discovery_cb, &custom_client);

}


// I guess this is called after we get disconnected from the xlrt
// Might want to use it to change the LED flash pattern to indicate
// no connection
static void disconnected(struct bt_conn *conn, uint8_t reason)
{
	char addr[BT_ADDR_LE_STR_LEN];
	int err;

	bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));

	LOG_INF("Disconnected: %s, reason 0x%02x %s", addr, reason, bt_hci_err_to_str(reason));

	if (default_conn != conn) 
	{
		return;
	}

	bt_conn_unref(default_conn);
	default_conn = NULL;

	err = bt_scan_start(BT_SCAN_TYPE_SCAN_ACTIVE);
	if (err != NO_ERROR) 
	{
		LOG_ERR("Scanning failed to start (err %d)", err);
	}
}


// Do we need this as we're not using security?
static void security_changed(struct bt_conn *conn, bt_security_t level, enum bt_security_err err)
{
	char addr[BT_ADDR_LE_STR_LEN];

	bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));

	if (NO_ERROR == err) 
	{
		LOG_INF("Security changed: %s level %u", addr, level);
	} 
	else 
	{
		LOG_WRN("Security failed: %s level %u err %d %s", addr, level, err,
			bt_security_err_to_str(err));
	}

	gatt_discover(conn);
}

// This is setup via 
// struct bt_gatt_dm_cb discovery_cb
// And is called when, I think, the gatt discovery has finished
static void discovery_complete(struct bt_gatt_dm *dm, void *context)
{
	struct bt_custom_client *custom = context;
	LOG_INF("Service discovery completed");

	set_flash_length(200);
	bt_gatt_dm_data_print(dm);

	bt_custom_handles_assign(dm, custom);	
	bt_custom_subscribe_tx_receive(custom);
	bt_custom_subscribe_rx_receive(custom);	
#ifdef DISCOVERY
	init_association_discovery(custom);
#endif	
	bt_gatt_dm_data_release(dm);
}

// RJW - can we lose this as they  are just logs?
static void discovery_service_not_found(struct bt_conn *conn, void *context)
{
	LOG_INF("Service not found");
}

// RJW - can we lose this as they  are just logs?
static void discovery_error(struct bt_conn *conn, int err, void *context)
{
	LOG_WRN("Error while discovering GATT database: (%d)", err);
}


static void scan_filter_match(struct bt_scan_device_info *device_info,
			      struct bt_scan_filter_match *filter_match,
			      bool connectable)
{
	
	bt_addr_le_to_str(device_info->recv_info->addr, addr, sizeof(addr));

	LOG_INF("Filters matched. Address: %s connectable: %d", addr, connectable);
}

// RJW - can we lose this as they  are just logs?
static void scan_connecting_error(struct bt_scan_device_info *device_info)
{
	LOG_WRN("Connecting failed");
}

static void scan_connecting(struct bt_scan_device_info *device_info,  struct bt_conn *conn)
{
	default_conn = bt_conn_ref(conn);
}

static bool check_blank_addr(uint8_t *addr_ptr)
{

	bool ret_val; 

	ret_val = true;
	uint8_t i;

	for (i=0;i<BT_ADDR_LE_STR_LEN;i++)
	{
		if (0xFF != *addr_ptr++)
		{
			ret_val = false;
		}		
	}

	return ret_val;

}

/* This is called from the start of main loop to inistalise 
* scanning for an XLRT. That scan, long term, will be a pre saved 
* MAC address, but the module will be able to be put into a mode
* where it can search for a new device to "pair" with
*/
int scan_init(enum ble_scan_types scan_type)
{
	
	int err;
	bt_addr_le_t addr;
	uint8_t read_mac_addr[6];	
	uint8_t i;
	enum ble_scan_types filter_type;
#define RANCID_DEFINE_TYPES
#ifndef RANCID_DEFINE_TYPES
	bt_addr_le_t addr;
#endif
		
	struct bt_scan_init_param scan_init_struct = 
	{
		.connect_if_match = 1,
	};

	bt_scan_init(&scan_init_struct);
	bt_scan_cb_register(&scan_cb);
/*
#ifdef SCAN_BY_SERVICE_UUID	 */
	//err = bt_scan_filter_add(BT_SCAN_FILTER_TYPE_UUID, BT_UUID_CUSTOM_SERVICE);
//#elif defined SCAN_BY_NAME
	get_mac_addr(read_mac_addr);
	if ((SCAN_FOR_NAME == scan_type) || (true == check_blank_addr(read_mac_addr)))
	{
		err = bt_scan_filter_add(BT_SCAN_FILTER_TYPE_NAME, DEVICE_NAME);
		filter_type = SCAN_FOR_NAME;
		new_pairing = true;
	}
	else if (SCAN_FOR_ADDR == scan_type)
	{						
		addr.type = BT_ADDR_LE_RANDOM;
		for (i=0; i<BT_ADDR_LE_STR_LEN; i++)
			addr.a.val[i] = read_mac_addr[i];	
		err = bt_scan_filter_add(BT_SCAN_FILTER_TYPE_ADDR, &addr);	
		filter_type = SCAN_FOR_ADDR;			
	}
//#else
#if 0
#ifdef RANCID_DEFINE_TYPES
	bt_addr_le_t addr = 
	{
		.type = BT_ADDR_LE_RANDOM,
		.a.val = {0x62, 0x21, 0x24, 0xDB, 0xA0, 0xE8},  //   {0xE8, 0xA0, 0xDB, 0x24, 0x21, 0x62}, //
	};
#else
	addr.type = BT_ADDR_LE_RANDOM;
	addr.a.val[0] = 0x62;
	addr.a.val[1] = 0x21;
	addr.a.val[2] = 0x24;
	addr.a.val[3] = 0xDB;
	addr.a.val[4] = 0xA0;
	addr.a.val[5] = 0xE8;

#endif
	err = bt_scan_filter_add(BT_SCAN_FILTER_TYPE_ADDR, &addr);
#endif	

	if (err) 
	{
		LOG_ERR("Scanning filters cannot be set (err %d)", err);
		return err;
	}

//#ifdef SCAN_BY_SERVICE_UUID	
//	err = bt_scan_filter_enable(BT_SCAN_UUID_FILTER, false);
//#elif defined SCAN_BY_NAME
	if (SCAN_FOR_NAME == filter_type)
		err = bt_scan_filter_enable(BT_SCAN_NAME_FILTER, false);
	else if (SCAN_FOR_ADDR == filter_type)
		err = bt_scan_filter_enable(BT_SCAN_ADDR_FILTER, false);
//#endif
	if (err) 
	{
		LOG_ERR("Filters cannot be turned on (err %d)", err);
		return err;
	}

	LOG_INF("Scan module initialized");


	return err;
}

// When there is UART data  come in that needs sending out to the xlrt this is  called
// RJW - There should be a check to make sure that len is not greater than the char length I suspect
void send_ble_data_to_xlrt(uint8_t *data_ptr, uint16_t len)
{
	int err;
	 err = bt_custom_client_send(&custom_client, data_ptr, len);
	 if (err != NO_ERROR) 
	{
		LOG_WRN("CUSTOM send timeout");
	}

}

