/*
 * Copyright (c) 2021 Nordic Semiconductor ASA
 *
 * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
 */
#include <unity.h>
#include <stdbool.h>
#include <stdlib.h>
#include <cmock_modules_common.h>
#include <cmock_app_event_manager.h>
#include <cmock_app_event_manager_priv.h>
#include <cmock_location.h>
#include <cmock_lte_lc.h>
#include <cmock_nrf_modem_gnss.h>

#include "app_module_event.h"
#include "location_module_event.h"
#include "data_module_event.h"
#include "modem_module_event.h"

extern struct event_listener __event_listener_location_module;

/* The addresses of the following structures will be returned when the app_event_manager_alloc()
 * function is called.
 */
static struct app_module_event app_module_event_memory;
static struct modem_module_event modem_module_event_memory;
static struct location_module_event location_module_event_memory;
static struct data_module_event data_module_event_memory;

#define LOCATION_MODULE_EVT_HANDLER(aeh) __event_listener_location_module.notification(aeh)

/* location_event_handler() is implemented in location module and we'll call it directly
 * to fake received location library events.
 */
extern void location_event_handler(const struct location_event_data *event_data);

#define LOCATION_MODULE_MAX_EVENTS 5

/* Counter for received location module events. */
static uint32_t location_module_event_count;
/* Number of expected location module events. */
static uint32_t expected_location_module_event_count;
/* Array for expected location module events. */
static struct location_module_event expected_location_module_events[LOCATION_MODULE_MAX_EVENTS];
/* Semaphore for waiting for events to be received. */
static K_SEM_DEFINE(location_module_event_sem, 0, 5);

/* Dummy functions and objects. */

/* The following function needs to be stubbed this way because Application Event Manager
 * uses heap to allocate memory for events.
 * The other alternative is to mock the k_malloc using CMock framework, but
 * that will pollute the test code and will be an overkill.
 */
void *k_malloc(size_t size)
{
	return malloc(size);
}

/* Dummy structs to please the linker. The APP_EVENT_SUBSCRIBE macros in location_module.c
 * depend on these to exist. But since we are unit testing, we dont need
 * these subscriptions and hence these structs can remain uninitialized.
 */
struct event_type __event_type_location_module_event;
struct event_type __event_type_app_module_event;
struct event_type __event_type_data_module_event;
struct event_type __event_type_util_module_event;
struct event_type __event_type_modem_module_event;

/* Dummy functions and objects - End.  */

/* The following is required because unity is using a different main signature
 * (returns int) and zephyr expects main to not return value.
 */
extern int unity_main(void);


/* Suite teardown finalizes with mandatory call to generic_suiteTearDown. */
extern int generic_suiteTearDown(int num_failures);

int test_suiteTearDown(int num_failures)
{
	return generic_suiteTearDown(num_failures);
}


void setUp(void)
{
	cmock_modules_common_Init();
	cmock_app_event_manager_Init();
	cmock_location_Init();
	cmock_nrf_modem_gnss_Init();

	location_module_event_count = 0;
	expected_location_module_event_count = 0;
	memset(&expected_location_module_events, 0, sizeof(expected_location_module_events));
}

void tearDown(void)
{
	/* Wait until we've received all events. */
	for (int i = 0; i < expected_location_module_event_count; i++) {
		k_sem_take(&location_module_event_sem, K_SECONDS(1));
	}
	TEST_ASSERT_EQUAL(expected_location_module_event_count, location_module_event_count);

	cmock_modules_common_Verify();
	cmock_app_event_manager_Verify();
	cmock_location_Verify();
	cmock_nrf_modem_gnss_Verify();
}

static void validate_location_module_evt(struct app_event_header *aeh, int no_of_calls)
{
	uint32_t index = location_module_event_count;
	struct location_module_event *event = cast_location_module_event(aeh);

	/* Make sure we don't get more events than expected. */
	TEST_ASSERT_LESS_THAN(expected_location_module_event_count, location_module_event_count);

	TEST_ASSERT_EQUAL(expected_location_module_events[index].type, event->type);

	switch (event->type) {
	case LOCATION_MODULE_EVT_GNSS_DATA_READY:
		TEST_ASSERT_EQUAL(
			expected_location_module_events[index].data.location.pvt.latitude,
			event->data.location.pvt.latitude);
		TEST_ASSERT_EQUAL(
			expected_location_module_events[index].data.location.pvt.longitude,
			event->data.location.pvt.longitude);
		TEST_ASSERT_EQUAL(
			expected_location_module_events[index].data.location.pvt.altitude,
			event->data.location.pvt.altitude);
		TEST_ASSERT_EQUAL(
			expected_location_module_events[index].data.location.pvt.accuracy,
			event->data.location.pvt.accuracy);
		TEST_ASSERT_EQUAL(
			expected_location_module_events[index].data.location.pvt.speed,
			event->data.location.pvt.speed);
		TEST_ASSERT_EQUAL(
			expected_location_module_events[index].data.location.pvt.heading,
			event->data.location.pvt.heading);
		TEST_ASSERT_EQUAL(
			expected_location_module_events[index].data.location.satellites_tracked,
			event->data.location.satellites_tracked);

		/* Ignore search_time and timestamp verification as those values might change */
		break;
	case LOCATION_MODULE_EVT_NEIGHBOR_CELLS_DATA_READY: {
		struct location_module_neighbor_cells *expected_ncells =
			&expected_location_module_events[index].data.neighbor_cells;
		struct location_module_neighbor_cells *received_ncells =
			&event->data.neighbor_cells;

		TEST_ASSERT_EQUAL(
			expected_ncells->cell_data.current_cell.mcc,
			received_ncells->cell_data.current_cell.mcc);
		TEST_ASSERT_EQUAL(
			expected_ncells->cell_data.current_cell.mnc,
			received_ncells->cell_data.current_cell.mnc);
		TEST_ASSERT_EQUAL(
			expected_ncells->cell_data.current_cell.id,
			received_ncells->cell_data.current_cell.id);
		TEST_ASSERT_EQUAL(
			expected_ncells->cell_data.current_cell.tac,
			received_ncells->cell_data.current_cell.tac);
		TEST_ASSERT_EQUAL(
			expected_ncells->cell_data.current_cell.earfcn,
			received_ncells->cell_data.current_cell.earfcn);
		TEST_ASSERT_EQUAL(
			expected_ncells->cell_data.current_cell.timing_advance,
			received_ncells->cell_data.current_cell.timing_advance);
		TEST_ASSERT_EQUAL(
			expected_ncells->cell_data.current_cell.timing_advance_meas_time,
			received_ncells->cell_data.current_cell.timing_advance_meas_time);
		TEST_ASSERT_EQUAL(
			expected_ncells->cell_data.current_cell.measurement_time,
			received_ncells->cell_data.current_cell.measurement_time);
		TEST_ASSERT_EQUAL(
			expected_ncells->cell_data.current_cell.phys_cell_id,
			received_ncells->cell_data.current_cell.phys_cell_id);
		TEST_ASSERT_EQUAL(
			expected_ncells->cell_data.current_cell.rsrp,
			received_ncells->cell_data.current_cell.rsrp);
		TEST_ASSERT_EQUAL(
			expected_ncells->cell_data.current_cell.rsrq,
			received_ncells->cell_data.current_cell.rsrq);

		TEST_ASSERT_EQUAL(
			expected_ncells->cell_data.ncells_count,
			received_ncells->cell_data.ncells_count);
		for (int i = 0; i < received_ncells->cell_data.ncells_count; i++) {
			TEST_ASSERT_EQUAL(
				expected_ncells->neighbor_cells[i].earfcn,
				received_ncells->neighbor_cells[i].earfcn);
			TEST_ASSERT_EQUAL(
				expected_ncells->neighbor_cells[i].time_diff,
				received_ncells->neighbor_cells[i].time_diff);
			TEST_ASSERT_EQUAL(
				expected_ncells->neighbor_cells[i].phys_cell_id,
				received_ncells->neighbor_cells[i].phys_cell_id);
			TEST_ASSERT_EQUAL(
				expected_ncells->neighbor_cells[i].rsrp,
				received_ncells->neighbor_cells[i].rsrp);
			TEST_ASSERT_EQUAL(
				expected_ncells->neighbor_cells[i].rsrq,
				received_ncells->neighbor_cells[i].rsrq);
		}
		break;
	}
	case LOCATION_MODULE_EVT_SHUTDOWN_READY:
		TEST_FAIL();
		break;
	case LOCATION_MODULE_EVT_AGPS_NEEDED:
		TEST_ASSERT_EQUAL(
			expected_location_module_events[index].data.agps_request.sv_mask_ephe,
			event->data.agps_request.sv_mask_ephe);
		TEST_ASSERT_EQUAL(
			expected_location_module_events[index].data.agps_request.sv_mask_alm,
			event->data.agps_request.sv_mask_alm);
		TEST_ASSERT_EQUAL(
			expected_location_module_events[index].data.agps_request.data_flags,
			event->data.agps_request.data_flags);
		break;
	case LOCATION_MODULE_EVT_PGPS_NEEDED:
		TEST_FAIL();
		break;
	case LOCATION_MODULE_EVT_ERROR_CODE:
		TEST_ASSERT_EQUAL(
			expected_location_module_events[index].data.err,
			event->data.err);
		break;

	case LOCATION_MODULE_EVT_DATA_NOT_READY:
	case LOCATION_MODULE_EVT_TIMEOUT:
	case LOCATION_MODULE_EVT_ACTIVE:
	case LOCATION_MODULE_EVT_INACTIVE:
	default:
		break;
	}

	location_module_event_count++;

	/* Signal that an event was received. */
	k_sem_give(&location_module_event_sem);
}

/* Stub used to verify parameters passed into module_start(). */
static int module_start_stub(struct module_data *module, int num_calls)
{
	TEST_ASSERT_EQUAL_STRING("location", module->name);
	TEST_ASSERT_NULL(module->msg_q);
	TEST_ASSERT_TRUE(module->supports_shutdown);

	return 0;
}

static void setup_location_module_in_init_state(void)
{
	/* Send APP_EVT_START. */
	__cmock_app_event_manager_alloc_ExpectAnyArgsAndReturn(&app_module_event_memory);
	__cmock_app_event_manager_free_ExpectAnyArgs();
	struct app_module_event *app_module_event = new_app_module_event();

	__cmock_module_start_Stub(&module_start_stub);

	app_module_event->type = APP_EVT_START;

	__cmock_app_event_manager_alloc_ExpectAnyArgsAndReturn(&location_module_event_memory);
	bool ret = LOCATION_MODULE_EVT_HANDLER((struct app_event_header *)app_module_event);

	app_event_manager_free(app_module_event);

	TEST_ASSERT_EQUAL(0, ret);
}

static void setup_location_module_in_running_state(void)
{
	setup_location_module_in_init_state();

	/* Send MODEM_EVT_INITIALIZED. */
	__cmock_location_init_ExpectAndReturn(&location_event_handler, 0);

	__cmock_app_event_manager_alloc_ExpectAnyArgsAndReturn(&modem_module_event_memory);
	__cmock_app_event_manager_free_ExpectAnyArgs();
	struct modem_module_event *modem_module_event = new_modem_module_event();

	modem_module_event->type = MODEM_EVT_INITIALIZED;

	bool ret = LOCATION_MODULE_EVT_HANDLER((struct app_event_header *)modem_module_event);

	app_event_manager_free(modem_module_event);

	/* Send DATA_EVT_CONFIG_INIT. */
	__cmock_app_event_manager_alloc_ExpectAnyArgsAndReturn(&data_module_event_memory);
	__cmock_app_event_manager_free_ExpectAnyArgs();
	struct data_module_event *data_module_event = new_data_module_event();

	data_module_event->type = DATA_EVT_CONFIG_INIT;
	data_module_event->data.cfg.location_timeout = 30;
	data_module_event->data.cfg.no_data.gnss = false;
	data_module_event->data.cfg.no_data.neighbor_cell = false;

	ret = LOCATION_MODULE_EVT_HANDLER((struct app_event_header *)data_module_event);

	app_event_manager_free(data_module_event);

	TEST_ASSERT_EQUAL(0, ret);
}

/* Set location module into state where location_request() has been called.
 */
static void setup_location_module_in_active_state(void)
{
	/* Pre-condition. */
	setup_location_module_in_running_state();

	/* Set up expectations.  */
	/* Set callback to validate location module events. */
	__cmock__event_submit_Stub(&validate_location_module_evt);

	/* Set default location configuration. */
	enum location_method methods[] = { LOCATION_METHOD_GNSS, LOCATION_METHOD_CELLULAR };

	struct location_config config_defaults = {
		.methods_count = 2,
		.interval = 0,
		.timeout = 300000,
		.mode = LOCATION_REQ_MODE_FALLBACK,
		.methods = {
			{.method = LOCATION_METHOD_GNSS, .gnss.timeout = 90000},
			{.method = LOCATION_METHOD_CELLULAR, .cellular.timeout = 11000}
		}
	};
	__cmock_location_config_defaults_set_Expect(NULL, 2, methods);
	__cmock_location_config_defaults_set_IgnoreArg_config();
	__cmock_location_config_defaults_set_ReturnThruPtr_config(&config_defaults);

	/* Location configuration modified by location module for location request. */
	struct location_config config_location_request = {
		.methods_count = 2,
		.interval = 0,
		.timeout = 300000,
		.mode = LOCATION_REQ_MODE_FALLBACK,
		.methods = {
			{.method = LOCATION_METHOD_GNSS, .gnss.timeout = 90000},
			{.method = LOCATION_METHOD_CELLULAR, .cellular.timeout = 11000}
		}
	};
	__cmock_location_request_ExpectAndReturn(&config_location_request, 0);
	__cmock_location_request_IgnoreArg_config(); /* TODO: REMOVE */

	/* Send APP_EVT_DATA_GET with APP_DATA_LOCATION. */
	__cmock_app_event_manager_alloc_ExpectAnyArgsAndReturn(&app_module_event_memory);
	__cmock_app_event_manager_free_ExpectAnyArgs();
	struct app_module_event *app_module_event = new_app_module_event();

	app_module_event->type = APP_EVT_DATA_GET;
	app_module_event->count = 1;
	app_module_event->data_list[0] = APP_DATA_LOCATION;

	__cmock_app_event_manager_alloc_IgnoreAndReturn(&location_module_event_memory);
	bool ret = LOCATION_MODULE_EVT_HANDLER((struct app_event_header *)app_module_event);

	app_event_manager_free(app_module_event);

	TEST_ASSERT_EQUAL(0, ret);
}

/* Test whether the location module generates an event
 * - for A-GPS data on receiving an A-GPS data request from the location library.
 * - the location module generates an event with GNSS position on receiving a
 *   location from the location library.
 */
void test_location_gnss_with_agps(void)
{
	/* Set expected location module events. */
	expected_location_module_event_count = 4;
	expected_location_module_events[0].type = LOCATION_MODULE_EVT_ACTIVE;

	expected_location_module_events[1].type = LOCATION_MODULE_EVT_AGPS_NEEDED;
	expected_location_module_events[1].data.agps_request.sv_mask_ephe = 0xabbaabba,
	expected_location_module_events[1].data.agps_request.sv_mask_alm = 0xdeaddead,
	expected_location_module_events[1].data.agps_request.data_flags =
		NRF_MODEM_GNSS_AGPS_GPS_UTC_REQUEST |
		NRF_MODEM_GNSS_AGPS_NEQUICK_REQUEST |
		NRF_MODEM_GNSS_AGPS_POSITION_REQUEST;

	expected_location_module_events[2].type = LOCATION_MODULE_EVT_GNSS_DATA_READY;
	expected_location_module_events[2].data.location.pvt.latitude = 34.087;
	expected_location_module_events[2].data.location.pvt.longitude = 10.45;
	expected_location_module_events[2].data.location.pvt.altitude = 35;
	expected_location_module_events[2].data.location.pvt.accuracy = 7;
	expected_location_module_events[2].data.location.pvt.speed = 37;
	expected_location_module_events[2].data.location.pvt.heading = 189;
	expected_location_module_events[2].data.location.satellites_tracked = 8;

	expected_location_module_events[3].type = LOCATION_MODULE_EVT_INACTIVE;

	/* Set location module into state where location_request() has been called. */
	setup_location_module_in_active_state();

	/* Location request is responded with location library events. */
	struct location_event_data event_data_agps = {
		.id = LOCATION_EVT_GNSS_ASSISTANCE_REQUEST,
		.agps_request.sv_mask_ephe = 0xabbaabba,
		.agps_request.sv_mask_alm = 0xdeaddead,
		.agps_request.data_flags = NRF_MODEM_GNSS_AGPS_GPS_UTC_REQUEST |
					   NRF_MODEM_GNSS_AGPS_NEQUICK_REQUEST |
					   NRF_MODEM_GNSS_AGPS_POSITION_REQUEST
	};
	location_event_handler(&event_data_agps);

	struct location_event_data event_data_gnss_location = {
		.id = LOCATION_EVT_LOCATION,
		.method = LOCATION_METHOD_GNSS,

		.location.latitude = 34.087,
		.location.details.gnss.pvt_data.latitude = 34.087,
		.location.longitude = 10.45,
		.location.details.gnss.pvt_data.longitude = 10.45,
		.location.accuracy = 7,
		.location.details.gnss.pvt_data.accuracy = 7,

		.location.details.gnss.pvt_data.altitude = 35,
		.location.details.gnss.pvt_data.speed = 37,
		.location.details.gnss.pvt_data.heading = 189,
		.location.details.gnss.satellites_tracked = 8
	};
	location_event_handler(&event_data_gnss_location);
}

/* Test whether the location module generates an event for neighbor cell measurements
 * on receiving cellular request from location library.
 */
void test_location_cellular(void)
{
	struct lte_lc_ncell neighbor_cells[2] = {
		{2300, 0, 8, 60, 29},
		{2400, 184, 11, 55, 26}
	};

	struct lte_lc_cells_info lte_cells = {
		.current_cell = {
			262, 95, 0x00011B07, 0x00B7, 2300, 10512, 9034,
			150344527, 7, 63, 31 },
		.ncells_count = 2,
		.neighbor_cells = neighbor_cells
	};

	/* Set expected location module events. */
	expected_location_module_event_count = 3;
	expected_location_module_events[0].type = LOCATION_MODULE_EVT_ACTIVE;

	expected_location_module_events[1].type = LOCATION_MODULE_EVT_NEIGHBOR_CELLS_DATA_READY;
	expected_location_module_events[1].data.neighbor_cells.cell_data = lte_cells;
	expected_location_module_events[1].data.neighbor_cells.neighbor_cells[0] =
		neighbor_cells[0];
	expected_location_module_events[1].data.neighbor_cells.neighbor_cells[1] =
		neighbor_cells[1];
	expected_location_module_events[1].data.neighbor_cells.timestamp = 123456789;

	expected_location_module_events[2].type = LOCATION_MODULE_EVT_INACTIVE;

	/* Set location module into state where location_request() has been called. */
	setup_location_module_in_active_state();

	/* Location module indicates that it has handled neighbor cells
	 * but the location is undefined.
	 */
	__cmock_location_cellular_ext_result_set_Expect(LOCATION_CELLULAR_EXT_RESULT_UNKNOWN, NULL);

	/* Location request is responded with location library events. */
	struct location_event_data event_data_cellular = {
		.id = LOCATION_EVT_CELLULAR_EXT_REQUEST,
		.cellular_request = lte_cells
	};
	location_event_handler(&event_data_cellular);

	/* Location library responds with and undefined status. */
	struct location_event_data event_data_undefined = {
		.id = LOCATION_EVT_RESULT_UNKNOWN,
	};
	location_event_handler(&event_data_undefined);
}

/* Test timeout for location request.
 */
void test_location_fail_timeout(void)
{
	/* Set expected location module events. */
	expected_location_module_event_count = 3;
	expected_location_module_events[0].type = LOCATION_MODULE_EVT_ACTIVE;
	expected_location_module_events[1].type = LOCATION_MODULE_EVT_TIMEOUT;
	expected_location_module_events[2].type = LOCATION_MODULE_EVT_INACTIVE;

	/* Set location module into state where location_request() has been called. */
	setup_location_module_in_active_state();

	/* Location request is responded with location library events. */
	struct location_event_data event_data = {
		.id = LOCATION_EVT_TIMEOUT,
	};
	location_event_handler(&event_data);
}

/* Test error while processing for location request.
 */
void test_location_fail_error(void)
{
	/* Set expected location module events. */
	expected_location_module_event_count = 3;
	expected_location_module_events[0].type = LOCATION_MODULE_EVT_ACTIVE;
	expected_location_module_events[1].type = LOCATION_MODULE_EVT_DATA_NOT_READY;
	expected_location_module_events[2].type = LOCATION_MODULE_EVT_INACTIVE;

	/* Set location module into state where location_request() has been called. */
	setup_location_module_in_active_state();

	/* Location request is responded with location library events. */
	struct location_event_data event_data = {
		.id = LOCATION_EVT_ERROR,
	};
	location_event_handler(&event_data);
}

/* Test that an error event is sent when location library initialization fails.
 */
void test_location_fail_init(void)
{
	setup_location_module_in_init_state();

	__cmock_location_init_ExpectAndReturn(&location_event_handler, -EINVAL);

	/* Set expected GNSS module events. */
	expected_location_module_event_count = 1;
	expected_location_module_events[0].type = LOCATION_MODULE_EVT_ERROR_CODE;
	expected_location_module_events[0].data.err = -1;

	__cmock_app_event_manager_alloc_ExpectAnyArgsAndReturn(&modem_module_event_memory);
	__cmock_app_event_manager_free_ExpectAnyArgs();
	struct modem_module_event *modem_module_event = new_modem_module_event();

	modem_module_event->type = MODEM_EVT_INITIALIZED;

	bool ret = LOCATION_MODULE_EVT_HANDLER((struct app_event_header *)modem_module_event);

	app_event_manager_free(modem_module_event);

	TEST_ASSERT_EQUAL(0, ret);
}

void main(void)
{
	(void)unity_main();
}
