#include "inttypes.h"
#include "unity.h"
#include "esp_attr.h"
#include "esp_log.h"
#include "soc/soc.h"
#include "esp_system.h"
#if CONFIG_IDF_TARGET_ESP32
#include "spiram.h"
#endif

static __NOINIT_ATTR uint32_t s_noinit;
static RTC_NOINIT_ATTR uint32_t s_rtc_noinit;
static RTC_DATA_ATTR uint32_t s_rtc_data;
static RTC_RODATA_ATTR uint32_t s_rtc_rodata;
static RTC_FAST_ATTR uint32_t s_rtc_force_fast;
static RTC_SLOW_ATTR uint32_t s_rtc_force_slow;
#if CONFIG_SPIRAM_ALLOW_NOINIT_SEG_EXTERNAL_MEMORY
static EXT_RAM_NOINIT_ATTR uint32_t s_noinit_ext;
#endif

extern int _rtc_noinit_start;
extern int _rtc_noinit_end;
extern int _rtc_data_start;
extern int _rtc_data_end;
extern int _noinit_start;
extern int _noinit_end;
extern int _rtc_force_fast_start;
extern int _rtc_force_fast_end;
extern int _rtc_force_slow_start;
extern int _rtc_force_slow_end;
extern int _ext_ram_noinit_start;
extern int _ext_ram_noinit_end;
extern int _ext_ram_bss_start;
extern int _ext_ram_bss_end;


static bool data_in_segment(void *ptr, int *seg_start, int *seg_end)
{
    return ((intptr_t)ptr < (intptr_t)seg_end) && \
           ((intptr_t)ptr >= (intptr_t)seg_start);
}

TEST_CASE("Attributes place variables into correct sections", "[ld]")
{
    TEST_ASSERT(data_in_segment(&s_noinit, &_noinit_start, &_noinit_end));
    TEST_ASSERT(data_in_segment(&s_rtc_noinit, &_rtc_noinit_start, &_rtc_noinit_end));
    TEST_ASSERT(data_in_segment(&s_rtc_data, &_rtc_data_start, &_rtc_data_end));
    TEST_ASSERT(data_in_segment(&s_rtc_rodata, &_rtc_data_start, &_rtc_data_end));
    TEST_ASSERT(data_in_segment(&s_rtc_force_fast, &_rtc_force_fast_start, &_rtc_force_fast_end));
    TEST_ASSERT(data_in_segment(&s_rtc_force_slow, &_rtc_force_slow_start, &_rtc_force_slow_end));

#if CONFIG_ESP32_RTCDATA_IN_FAST_MEM   || \
    CONFIG_ESP32S2_RTCDATA_IN_FAST_MEM || \
    CONFIG_ESP32S3_RTCDATA_IN_FAST_MEM || \
    CONFIG_ESP32C3_RTCDATA_IN_FAST_MEM
    TEST_ASSERT(data_in_segment(&s_rtc_data, (int*) SOC_RTC_DRAM_LOW, (int*) SOC_RTC_DRAM_HIGH));
    TEST_ASSERT(data_in_segment(&s_rtc_rodata, (int*) SOC_RTC_DRAM_LOW, (int*) SOC_RTC_DRAM_HIGH));
    TEST_ASSERT(data_in_segment(&s_rtc_noinit, (int*) SOC_RTC_DRAM_LOW, (int*) SOC_RTC_DRAM_HIGH));
#else
    TEST_ASSERT(data_in_segment(&s_rtc_data, (int*) SOC_RTC_DATA_LOW, (int*) SOC_RTC_DATA_HIGH));
    TEST_ASSERT(data_in_segment(&s_rtc_rodata, (int*) SOC_RTC_DATA_LOW, (int*) SOC_RTC_DATA_HIGH));
    TEST_ASSERT(data_in_segment(&s_rtc_noinit, (int*) SOC_RTC_DATA_LOW, (int*) SOC_RTC_DATA_HIGH));
#endif

    TEST_ASSERT(data_in_segment(&s_rtc_force_fast, (int*) SOC_RTC_DRAM_LOW, (int*) SOC_RTC_DRAM_HIGH));
    TEST_ASSERT(data_in_segment(&s_rtc_force_slow, (int*) SOC_RTC_DATA_LOW, (int*) SOC_RTC_DATA_HIGH));

#if CONFIG_SPIRAM_ALLOW_NOINIT_SEG_EXTERNAL_MEMORY
    TEST_ASSERT(data_in_segment(&s_noinit_ext, &_ext_ram_noinit_start, &_ext_ram_noinit_end));
#endif
}


#if CONFIG_SPIRAM_ALLOW_NOINIT_SEG_EXTERNAL_MEMORY

#define TEST_BUFFER_SIZE (16*1024/4)
static EXT_RAM_NOINIT_ATTR uint32_t s_noinit_buffer[TEST_BUFFER_SIZE];

static void write_spiram_and_reset(void)
{
    // Fill the noinit buffer
    printf("Filling buffer\n");
    for (uint32_t i = 0; i < TEST_BUFFER_SIZE; i++) {
        s_noinit_buffer[i] = i ^ 0x55555555U;
    }
    printf("Flushing cache\n");
    // Flush the cache out to SPIRAM before resetting.
    esp_spiram_writeback_cache();

    printf("Restarting\n");
    // Reset to test that noinit memory is left intact.
    esp_restart();
}

static void check_spiram_contents(void)
{
    // Confirm that the memory contents are still what we expect
    uint32_t error_count = 0;
    for (uint32_t i = 0; i < TEST_BUFFER_SIZE; i++) {
        if (s_noinit_buffer[i] != (i ^ 0x55555555U)) {
            error_count++;
        }
    }
    printf("Found %" PRIu32 " memory errors\n", error_count);
    TEST_ASSERT(error_count == 0);
}

TEST_CASE_MULTIPLE_STAGES("Spiram test noinit memory", "[spiram]", write_spiram_and_reset, check_spiram_contents);

#endif // CONFIG_SPIRAM_ALLOW_NOINIT_SEG_EXTERNAL_MEMORY


#if CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY
#define TEST_BSS_NUM    256
static EXT_RAM_ATTR uint32_t s_bss_buffer[TEST_BSS_NUM];

TEST_CASE("Test variables placed in external .bss segment", "[ld]")
{
    for (int i = 0; i < TEST_BSS_NUM; i++) {
        TEST_ASSERT(data_in_segment(&s_bss_buffer[i], &_ext_ram_bss_start, &_ext_ram_bss_end));
        TEST_ASSERT_EQUAL(0, s_bss_buffer[i]);
    }
}
#endif  //#if CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY
