#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\""
//        }
//}

// 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

// The time to wait for a HTTP command to complete
#ifndef MBED_CONF_APP_HTTP_TIMEOUT
#define HTTP_TIMEOUT  10000
#else
#define HTTP_TIMEOUT  MBED_CONF_APP_HTTP_TIMEOUT
#endif

// The HTTP echo server, as described in the
// first answer here:
// http://stackoverflow.com/questions/5725430/http-test-server-that-accepts-get-post-calls
// !!! IMPORTANT: this test relies on that server behaving in the same way forever !!!
#define HTTP_ECHO_SERVER "httpbin.org"

// The size of the test file
#define TEST_FILE_SIZE 100

// The maximum number of HTTP profiles
#define MAX_PROFILES 4

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

#ifdef FEATURE_COMMON_PAL
// Lock for debug prints
static Mutex mtx;
#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);
// A few buffers for general use
static char buf[2048];
static char buf1[sizeof(buf)];

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

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

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

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

// Test HTTP commands
void test_http_cmd() {
    int profile;
    char * pData;
#ifdef TARGET_UBLOX_C030_R41XM
    int mno_profile;
    if (pDriver->init(MBED_CONF_APP_DEFAULT_PIN) == false) //init can return false if profile set is SW_DEFAULT
    {
        TEST_ASSERT(pDriver->get_mno_profile(&mno_profile));
        if (mno_profile == UbloxATCellularInterface::SW_DEFAULT) {
            TEST_ASSERT(pDriver->set_mno_profile(UbloxATCellularInterface::STANDARD_EU));
            TEST_ASSERT(pDriver->reboot_modem());
            tr_debug("Reboot successful\n");
            wait_ms(5000);
        }
    }
    TEST_ASSERT(pDriver->init(MBED_CONF_APP_DEFAULT_PIN));
    TEST_ASSERT(pDriver->disable_psm());
#endif
    TEST_ASSERT(pDriver->connect(MBED_CONF_APP_DEFAULT_PIN, MBED_CONF_APP_APN,
                                 MBED_CONF_APP_USERNAME, MBED_CONF_APP_PASSWORD) == 0);

    profile = pDriver->httpAllocProfile();
    TEST_ASSERT(profile >= 0);

    pDriver->httpSetTimeout(profile, HTTP_TIMEOUT);

    // Set up the server to talk to
    TEST_ASSERT(pDriver->httpSetPar(profile, UbloxATCellularInterfaceExt::HTTP_SERVER_NAME, HTTP_ECHO_SERVER));

    // Check HTTP head request
    memset(buf, 0, sizeof (buf));
    TEST_ASSERT(pDriver->httpCommand(profile, UbloxATCellularInterfaceExt::HTTP_HEAD,
                                     "/headers",
                                     NULL, NULL, 0, NULL,
                                     buf, sizeof (buf)) == NULL);
    tr_debug("Received: %s", buf);
    TEST_ASSERT(strstr(buf, "Content-Length:") != NULL);

    // Check HTTP get request
    memset(buf, 0, sizeof (buf));
    TEST_ASSERT(pDriver->httpCommand(profile, UbloxATCellularInterfaceExt::HTTP_GET,
                                     "/get",
                                     NULL, NULL, 0, NULL,
                                     buf, sizeof (buf)) == NULL);
    tr_debug("Received: %s", buf);
    TEST_ASSERT(strstr(buf, "HTTP/1.1 200 OK") != NULL);

    // Check HTTP delete request
    memset(buf, 0, sizeof (buf));
    TEST_ASSERT(pDriver->httpCommand(profile, UbloxATCellularInterfaceExt::HTTP_DELETE,
                                     "/delete",
                                     NULL, NULL, 0, NULL,
                                     buf, sizeof (buf)) == NULL);
    tr_debug("Received: %s", buf);
    TEST_ASSERT(strstr(buf, "HTTP/1.1 200 OK") != NULL);

    // Check HTTP put request (this will fail as the echo server doesn't support it)
    memset(buf, 0, sizeof (buf));
    TEST_ASSERT(pDriver->httpCommand(profile, UbloxATCellularInterfaceExt::HTTP_PUT,
                                     "/put",
                                     NULL, NULL, 0, NULL,
                                     buf, sizeof (buf)) != NULL);

    // Check HTTP post request with data
    memset(buf, 0, sizeof (buf));
    TEST_ASSERT(pDriver->httpCommand(profile, UbloxATCellularInterfaceExt::HTTP_POST_DATA,
                                     "/post",
                                     NULL, "formData=0123456789", 0, NULL,
                                     buf, sizeof (buf)) == NULL);
    tr_debug("Received: %s", buf);
    TEST_ASSERT(strstr(buf, "HTTP/1.1 200 OK") != NULL);

    // Check HTTP post request with a file, also checking that writing the response
    // to a named file works
    for (int x = 0; x < TEST_FILE_SIZE; x++) {
        buf[x] = (x % 10) + 0x30;
    }
    pDriver->delFile("post_test.txt");
    TEST_ASSERT(pDriver->writeFile("post_test.txt", buf, TEST_FILE_SIZE) == TEST_FILE_SIZE);

    // This may fail if rsp.txt doesn't happen to be sitting around from a previous run
    // so don't check the return value
    pDriver->delFile("rsp.txt");

    memset(buf, 0, sizeof (buf));
    TEST_ASSERT(pDriver->httpCommand(profile, UbloxATCellularInterfaceExt::HTTP_POST_FILE,
                                     "/post",
                                     "rsp.txt", "post_test.txt",
                                     UbloxATCellularInterfaceExt::HTTP_CONTENT_TEXT, NULL,
                                     buf, sizeof (buf)) == NULL);
    tr_debug("Received: %s", buf);
    // Find the data in the response and check it
    pData = strstr(buf, "\"data\": \"");
    TEST_ASSERT(pData != NULL);
    pData += 9;
    for (int x = 0; x < TEST_FILE_SIZE; x++) {
        TEST_ASSERT(*(pData + x) == (x % 10) + 0x30);
    }

    // Also check that rsp.txt exists and is the same as buf
    pDriver->readFile("rsp.txt", buf1, sizeof (buf1));
    memcmp(buf1, buf, sizeof (buf1));
    TEST_ASSERT(pDriver->delFile("rsp.txt"));
    TEST_ASSERT(!pDriver->delFile("rsp.txt")); // Should fail

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

// Test HTTP with TLS
void test_http_tls() {
    int profile;
    SocketAddress address;

    TEST_ASSERT(pDriver->connect(MBED_CONF_APP_DEFAULT_PIN, MBED_CONF_APP_APN,
                                 MBED_CONF_APP_USERNAME, MBED_CONF_APP_PASSWORD) == 0);

    profile = pDriver->httpAllocProfile();
    TEST_ASSERT(profile >= 0);

    pDriver->httpSetTimeout(profile, HTTP_TIMEOUT);

    // Set up the server to talk to and TLS, using the IP address this time just for variety
    TEST_ASSERT(pDriver->gethostbyname("www.amazon.com", &address) == 0);
    TEST_ASSERT(pDriver->httpSetPar(profile, UbloxATCellularInterfaceExt::HTTP_IP_ADDRESS, address.get_ip_address()));
    TEST_ASSERT(pDriver->httpSetPar(profile, UbloxATCellularInterfaceExt::HTTP_SECURE, "1"));
    TEST_ASSERT(pDriver->httpSetPar(profile, UbloxATCellularInterfaceExt::HTTP_REQ_HEADER, "0:Host:www.amazon.com"));

    // Check HTTP get request
    memset(buf, 0, sizeof (buf));
    TEST_ASSERT(pDriver->httpCommand(profile, UbloxATCellularInterfaceExt::HTTP_GET,
                                     "/",
                                     NULL, NULL, 0, NULL,
                                     buf, sizeof (buf)) == NULL);
    tr_debug("Received: %s", buf);
    // This is what amazon.com returns if TLS is set
    TEST_ASSERT(strstr(buf, "HTTP/1.1 200 OK") != NULL);

    // Reset the profile and check that this now fails
    TEST_ASSERT(pDriver->httpResetProfile(profile));
    TEST_ASSERT(pDriver->httpSetPar(profile, UbloxATCellularInterfaceExt::HTTP_IP_ADDRESS, address.get_ip_address()));
    memset(buf, 0, sizeof (buf));
    TEST_ASSERT(pDriver->httpCommand(profile, UbloxATCellularInterfaceExt::HTTP_GET,
                                     "/",
                                     NULL, NULL, 0, NULL,
                                     buf, sizeof (buf)) == NULL);
    tr_debug("Received: %s", buf);
    // This is what amazon.com returns if TLS is NOT set
    TEST_ASSERT(strstr(buf, "HTTP/1.1 403 Forbidden") != NULL);

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

// Allocate max profiles
void test_alloc_profiles() {
    int profiles[MAX_PROFILES];

    TEST_ASSERT(pDriver->connect(MBED_CONF_APP_DEFAULT_PIN, MBED_CONF_APP_APN,
                                 MBED_CONF_APP_USERNAME, MBED_CONF_APP_PASSWORD) == 0);

    // Allocate first profile and use it
    profiles[0] = pDriver->httpAllocProfile();
    TEST_ASSERT(profiles[0] >= 0);
    TEST_ASSERT(pDriver->httpSetPar(profiles[0], UbloxATCellularInterfaceExt::HTTP_SERVER_NAME, "raw.githubusercontent.com"));
    TEST_ASSERT(pDriver->httpSetPar(profiles[0], UbloxATCellularInterfaceExt::HTTP_SECURE, "1"));

    // Check HTTP get request
    memset(buf, 0, sizeof (buf));
    TEST_ASSERT(pDriver->httpCommand(profiles[0], UbloxATCellularInterfaceExt::HTTP_GET,
                                     "/u-blox/mbed-os/master/features/cellular/mbed_lib.json",
                                     NULL, NULL, 0, NULL,
                                     buf, sizeof (buf)) == NULL);
    tr_debug("Received: %s", buf);
    TEST_ASSERT(strstr(buf, "Radio access technology to use. Value in integer: GSM=0, GSM_COMPACT=1, UTRAN=2, EGPRS=3, HSDPA=4, HSUPA=5, HSDPA_HSUPA=6, E_UTRAN=7, CATM1=8 ,NB1=9") != NULL);

    // Check that we stop being able to get profiles at the max number
    for (int x = 1; x < sizeof (profiles) / sizeof (profiles[0]); x++) {
        profiles[x] = pDriver->httpAllocProfile();
        TEST_ASSERT(profiles[0] >= 0);
    }
    TEST_ASSERT(pDriver->httpAllocProfile() < 0);

    // Now use the last one and check that it doesn't affect the first one
    TEST_ASSERT(pDriver->httpSetPar(profiles[sizeof (profiles) / sizeof (profiles[0]) - 1],
                                    UbloxATCellularInterfaceExt::HTTP_SERVER_NAME, HTTP_ECHO_SERVER));

    // Check HTTP head request on last profile
    memset(buf, 0, sizeof (buf));
    TEST_ASSERT(pDriver->httpCommand(profiles[sizeof (profiles) / sizeof (profiles[0]) - 1],
                                     UbloxATCellularInterfaceExt::HTTP_HEAD,
                                     "/headers",
                                     NULL, NULL, 0, NULL,
                                     buf, sizeof (buf)) == NULL);
    tr_debug("Received: %s", buf);
    TEST_ASSERT(strstr(buf, "Content-Length:") != NULL);

    // Check HTTP get request on first profile once more
    memset(buf, 0, sizeof (buf));
    TEST_ASSERT(pDriver->httpCommand(profiles[0], UbloxATCellularInterfaceExt::HTTP_GET,
                                     "/u-blox/mbed-os/master/features/cellular/mbed_lib.json",
                                     NULL, NULL, 0, NULL,
                                     buf, sizeof (buf)) == NULL);
    tr_debug("Received: %s", buf);
    TEST_ASSERT(strstr(buf, "Radio access technology to use. Value in integer: GSM=0, GSM_COMPACT=1, UTRAN=2, EGPRS=3, HSDPA=4, HSUPA=5, HSDPA_HSUPA=6, E_UTRAN=7, CATM1=8 ,NB1=9") != NULL);

    // Free the profiles again
    for (int x = 0; x < sizeof (profiles) / sizeof (profiles[0]); x++) {
        TEST_ASSERT(pDriver->httpFreeProfile(profiles[x]));
    }

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

// ----------------------------------------------------------------
// 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("HTTP commands", test_http_cmd),
#ifndef TARGET_UBLOX_C027
    // C027 doesn't support TLS
    Case("HTTP with TLS", test_http_tls),
#endif
    Case("Alloc max profiles", test_alloc_profiles)
};

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

