/*

Show extended heap stats when EspClass::getHeapStats() is available

*/

#pragma once

#include <type_traits>

struct heap_stats_t {
    uint32_t available;
    uint16_t usable;
    uint8_t frag_pct;
};

namespace EspClass_has_getHeapStats {
    struct _detector {
        template<typename T, typename = decltype(
                std::declval<T>().getHeapStats(0,0,0))>
          static std::true_type detect(int);

        template<typename>
          static std::false_type detect(...);
    };

    template <typename T>
    struct detector : public _detector {
        using result = decltype(
                std::declval<detector>().template detect<T>(0));
    };

    template <typename T>
    struct typed_check : public detector<T>::result {
    };

    typed_check<EspClass> check{};
};

template <typename T>
void _getHeapStats(std::true_type&, T& instance, heap_stats_t& stats) {
    instance.getHeapStats(&stats.available, &stats.usable, &stats.frag_pct);
}

template <typename T>
void _getHeapStats(std::false_type&, T& instance, heap_stats_t& stats) {
    stats.available = instance.getFreeHeap();
    stats.usable = 0;
    stats.frag_pct = 0;
}

void getHeapStats(heap_stats_t& stats) {
    _getHeapStats(EspClass_has_getHeapStats::check, ESP, stats);
}

// WTF
// Calling ESP.getFreeHeap() is making the system crash on a specific
// AiLight bulb, but anywhere else it should work as expected
static bool _heap_value_wtf = false;

heap_stats_t getHeapStats() {
    heap_stats_t stats;
    if (_heap_value_wtf) {
        stats.available = 9999;
        stats.usable = 9999;
        stats.frag_pct = 0;
        return stats;
    }
    getHeapStats(stats);
    return stats;
}

void wtfHeap(bool value) {
    _heap_value_wtf = value;
}

unsigned int getFreeHeap() {
    return getHeapStats().available;
}

static unsigned int _initial_heap_value = 0;
void setInitialFreeHeap() {
    _initial_heap_value = getFreeHeap();
}

unsigned int getInitialFreeHeap() {
    if (0 == _initial_heap_value) {
        setInitialFreeHeap();
    }
    return _initial_heap_value;
}

void infoMemory(const char* name, const heap_stats_t& stats) {
    infoMemory(name, getInitialFreeHeap(), stats.available);
}

void infoHeapStats(const char* name, const heap_stats_t& stats) {
    DEBUG_MSG_P(
        PSTR("[MAIN] %-6s: %5u contiguous bytes available (%u%% fragmentation)\n"),
        name,
        stats.usable,
        stats.frag_pct
    );
}

void infoHeapStats(bool show_frag_stats = true) {
    const auto stats = getHeapStats();
    infoMemory("Heap", stats);
    if (show_frag_stats && EspClass_has_getHeapStats::check) {
        infoHeapStats("Heap", stats);
    }
}
