#include "mbed.h"
#include "greentea-client/test_env.h"
#include "unity.h"
#include "utest.h"
#include "UbloxATCellularInterfaceExt.h"
#include "UDPSocket.h"
#ifdef FEATURE_COMMON_PAL
#include "mbed_trace.h"
#define TRACE_GROUP "TEST"
#else
#define tr_debug(format, ...) debug(format "\n", ## __VA_ARGS__)
#define tr_info(format, ...)  debug(format "\n", ## __VA_ARGS__)
#define tr_warn(format, ...)  debug(format "\n", ## __VA_ARGS__)
#define tr_error(format, ...) debug(format "\n", ## __VA_ARGS__)
#endif

using namespace utest::v1;

// ----------------------------------------------------------------
// COMPILE-TIME MACROS
// ----------------------------------------------------------------

// These macros can be overridden with an mbed_app.json file and
// contents of the following form:
//
//{
//    "config": {
//        "apn": {
//            "value": "\"my_apn\""
//        },
//        "run-tcp-server-test": {
//            "value": 1
//        },
//        "mga-token": {
//            "value": "\"my_token\""
//        }
//}

// Whether debug trace is on
#ifndef MBED_CONF_APP_DEBUG_ON
# define MBED_CONF_APP_DEBUG_ON false
#endif

// The credentials of the SIM in the board.
#ifndef MBED_CONF_APP_DEFAULT_PIN
// Note: if PIN is enabled on your SIM, you must define the PIN
// for your SIM jere (e.g. using mbed_app.json to do so).
# define MBED_CONF_APP_DEFAULT_PIN "0000"
#endif

// Network credentials.
#ifndef MBED_CONF_APP_APN
# define MBED_CONF_APP_APN         NULL
#endif
#ifndef MBED_CONF_APP_USERNAME
# define MBED_CONF_APP_USERNAME    NULL
#endif
#ifndef MBED_CONF_APP_PASSWORD
# define MBED_CONF_APP_PASSWORD    NULL
#endif

#ifndef MBED_CONF_APP_RUN_CELL_LOCATE_TCP_SERVER_TEST
#  define MBED_CONF_APP_RUN_CELL_LOCATE_TCP_SERVER_TEST 0
#endif

// The authentication token for TCP access to the MGA server.
#if MBED_CONF_APP_RUN_CELL_LOCATE_TCP_SERVER_TEST
#  ifndef MBED_CONF_APP_CELL_LOCATE_MGA_TOKEN
#    error "You must have a token for MGA server access to run Cell Locate with a TCP server"
#  endif
#endif

// The type of response requested
#ifndef MBED_CONF_APP_RESP_TYPE
#  define MBED_CONF_APP_RESP_TYPE 1 // CELL_DETAILED
#endif

// The maximum number of hypotheses requested
#ifndef MBED_CONF_APP_CELL_LOCATE_MAX_NUM_HYPOTHESIS
#  if MBED_CONF_APP_RESP_TYPE == 2 // CELL_MULTIHYP
#    define MBED_CONF_APP_CELL_LOCATE_MAX_NUM_HYPOTHESIS 16
#  else
#    define MBED_CONF_APP_CELL_LOCATE_MAX_NUM_HYPOTHESIS 1
#  endif
#endif

#ifndef MBED_CONF_APP_CELL_LOCATE_MGA_TOKEN
#  define MBED_CONF_APP_CELL_LOCATE_MGA_TOKEN "0"
#endif

// ----------------------------------------------------------------
// PRIVATE VARIABLES
// ----------------------------------------------------------------

#ifdef FEATURE_COMMON_PAL
// Lock for debug prints
static Mutex mtx;
#endif

// Power up GNSS
#ifdef TARGET_UBLOX_C030
static DigitalInOut gnssEnable(GNSSEN, PIN_OUTPUT, PushPullNoPull, 1);
#elif TARGET_UBLOX_C027
static DigitalOut gnssEnable(GPSEN, 1);
#endif

// An instance of the cellular interface
static UbloxATCellularInterfaceExt *pDriver =
       new UbloxATCellularInterfaceExt(MDMTXD, MDMRXD,
                                       MBED_CONF_UBLOX_CELL_BAUD_RATE,
                                       MBED_CONF_APP_DEBUG_ON);

// ----------------------------------------------------------------
// PRIVATE FUNCTIONS
// ----------------------------------------------------------------

#ifdef FEATURE_COMMON_PAL
// Locks for debug prints
static void lock()
{
    mtx.lock();
}

static void unlock()
{
    mtx.unlock();
}
#endif

static void printCellLocateData(UbloxATCellularInterfaceExt::CellLocData *pData)
{
    char timeString[25];

    tr_debug("Cell Locate data:");
    if (strftime(timeString, sizeof(timeString), "%F %T", (const tm *) &(pData->time)) > 0) {
        tr_debug("  time:               %s", timeString);
    }
    tr_debug("  longitude:          %.6f", pData->longitude);
    tr_debug("  latitude:           %.6f", pData->latitude);
    tr_debug("  altitude:           %d metre(s)", pData->altitude);
    switch (pData->sensor) {
        case UbloxATCellularInterfaceExt::CELL_LAST:
            tr_debug("  sensor type:        last");
            break;
        case UbloxATCellularInterfaceExt::CELL_GNSS:
            tr_debug("  sensor type:        GNSS");
            break;
        case UbloxATCellularInterfaceExt::CELL_LOCATE:
            tr_debug("  sensor type:        Cell Locate");
            break;
        case UbloxATCellularInterfaceExt::CELL_HYBRID:
            tr_debug("  sensor type:        hybrid");
            break;
        default:
            tr_debug("  sensor type:        unknown");
            break;
    }
    tr_debug("  uncertainty:        %d metre(s)", pData->uncertainty);
    tr_debug("  speed:              %d metre(s)/second", pData->speed);
    tr_debug("  direction:          %d degree(s)", pData->direction);
    tr_debug("  vertical accuracy:  %d metre(s)/second", pData->speed);
    tr_debug("  satellite(s) used:  %d", pData->svUsed);
    tr_debug("I am here:            "
            "https://maps.google.com/?q=%.5f,%.5f", pData->latitude, pData->longitude);       
}

// ----------------------------------------------------------------
// TESTS
// ----------------------------------------------------------------

// Test Cell Locate talking to a UDP server
void test_udp_server() {
    int numRes = 0;
    UbloxATCellularInterfaceExt::CellLocData data;

    memset(&data, 0, sizeof(data));
    TEST_ASSERT(pDriver->connect(MBED_CONF_APP_DEFAULT_PIN, MBED_CONF_APP_APN,
                                 MBED_CONF_APP_USERNAME, MBED_CONF_APP_PASSWORD) == 0);

    TEST_ASSERT(pDriver->cellLocSrvUdp());
    TEST_ASSERT(pDriver->cellLocConfig(1));
    TEST_ASSERT(pDriver->cellLocRequest(UbloxATCellularInterfaceExt::CELL_HYBRID, 10, 100,
                                        (UbloxATCellularInterfaceExt::CellRespType) MBED_CONF_APP_RESP_TYPE,
                                        MBED_CONF_APP_CELL_LOCATE_MAX_NUM_HYPOTHESIS));

    for (int x = 0; (numRes == 0) && (x < 10); x++) {
        numRes = pDriver->cellLocGetRes();
    }

    TEST_ASSERT(numRes > 0);
    TEST_ASSERT(pDriver->cellLocGetData(&data));

    TEST_ASSERT(data.validData);

    printCellLocateData(&data);

    TEST_ASSERT(pDriver->disconnect() == 0);
    // Wait for printfs to leave the building or the test result string gets messed up
    wait_ms(500);
}

// Test Cell Locate talking to a TCP server
void test_tcp_server() {
    int numRes = 0;
    UbloxATCellularInterfaceExt::CellLocData data;

    memset(&data, 0, sizeof(data));
    TEST_ASSERT(pDriver->connect(MBED_CONF_APP_DEFAULT_PIN, MBED_CONF_APP_APN,
                                 MBED_CONF_APP_USERNAME, MBED_CONF_APP_PASSWORD) == 0);

    TEST_ASSERT(pDriver->cellLocSrvTcp(MBED_CONF_APP_CELL_LOCATE_MGA_TOKEN));
    TEST_ASSERT(pDriver->cellLocConfig(1));
    TEST_ASSERT(pDriver->cellLocRequest(UbloxATCellularInterfaceExt::CELL_HYBRID, 10, 100,
                                        (UbloxATCellularInterfaceExt::CellRespType) MBED_CONF_APP_RESP_TYPE,
                                        MBED_CONF_APP_CELL_LOCATE_MAX_NUM_HYPOTHESIS));

    for (int x = 0; (numRes == 0) && (x < 10); x++) {
        numRes = pDriver->cellLocGetRes();
    }

    TEST_ASSERT(numRes > 0);
    TEST_ASSERT(pDriver->cellLocGetData(&data));

    TEST_ASSERT(data.validData);

    printCellLocateData(&data);

    TEST_ASSERT(pDriver->disconnect() == 0);
    // Wait for printfs to leave the building or the test result string gets messed up
    wait_ms(500);
}

// Tidy up after testing so as not to screw with the test output strings
void test_tidy_up() {
    pDriver->deinit();
}

// ----------------------------------------------------------------
// TEST ENVIRONMENT
// ----------------------------------------------------------------

// Setup the test environment
utest::v1::status_t test_setup(const size_t number_of_cases) {
    // Setup Greentea with a timeout
    GREENTEA_SETUP(540, "default_auto");
    return verbose_test_setup_handler(number_of_cases);
}

// Test cases
Case cases[] = {
    Case("Cell Locate with UDP server", test_udp_server),
#if MBED_CONF_APP_RUN_CELL_LOCATE_TCP_SERVER_TEST
    Case("Cell Locate with TCP server", test_tcp_server),
#endif
    Case("Tidy up", test_tidy_up)
};

Specification specification(test_setup, cases);

// ----------------------------------------------------------------
// MAIN
// ----------------------------------------------------------------

int main() {

#ifdef FEATURE_COMMON_PAL
    mbed_trace_init();

    mbed_trace_mutex_wait_function_set(lock);
    mbed_trace_mutex_release_function_set(unlock);
#endif
    
    // Run tests
    return !Harness::run(specification);
}

// End Of File

