//#ifdef ESP_PLATFORM

#include <Tactility/app/AppManifest.h>
#include <Tactility/lvgl/Toolbar.h>
#include <Tactility/Assets.h>
#include <Tactility/StringUtils.h>
#include <Tactility/hal/radio/RadioDevice.h>
#include <Tactility/hal/radio/ParameterSet.h>

#include "Tactility/lvgl/LvglSync.h"

#include <cstdio>
#include <cstring>
#include <vector>
#include <iomanip>
#include <ctime>
#include <sstream>
#include <algorithm>
#include <cctype>
#include <iomanip>
#include <sstream>
#include <vector>
#include <cstdint>
#include <lvgl.h>

extern const lv_obj_class_t lv_label_class;

namespace tt::app::chirp {

constexpr const char* TAG = "ChirpChatterApp";

enum CCViews
{
    CCView_Msgs,
    CCView_LoraSettings,
    CCView_ProtoSettings
};

class ChirpChatterApp;

template<CCViews view>
static void changeViewHandler(lv_event_t* e) {
    auto* self = static_cast<ChirpChatterApp*>(lv_event_get_user_data(e));
    TT_LOG_I(TAG, "Clicked %d", view);
    self->changeView(view);
}

static void buttonRecolorFocus(lv_event_t *event) {
    lv_obj_t *image = (lv_obj_t *)lv_event_get_user_data(event);
    if (image != NULL) {
        lv_obj_set_style_image_recolor(image, lv_palette_main(LV_PALETTE_YELLOW), LV_STATE_DEFAULT);
    }
}

static void buttonRecolorDefocus(lv_event_t *event) {
    lv_obj_t *image = (lv_obj_t *)lv_event_get_user_data(event);
    if (image != NULL) {
        lv_obj_set_style_image_recolor(image, lv_theme_get_color_primary(image), LV_STATE_DEFAULT);
    }
}

static void debugFocus(lv_event_t *event) {
    lv_obj_t *target = (lv_obj_t *)lv_event_get_current_target(event);
    if (target != NULL) {
        lv_obj_set_style_bg_color(target, lv_color_hex(0xFF0000), 0);
    }
}

static void debugDefocus(lv_event_t *event) {
    lv_obj_t *target = (lv_obj_t *)lv_event_get_current_target(event);
    if (target != NULL) {
        lv_obj_set_style_bg_color(target, lv_color_hex(0x00FF00), 0);
    }
}

bool isPrintableData(const std::vector<uint8_t>& data) {
    return std::all_of(data.begin(), data.end(),
                       [](uint8_t byte) {
                           char c = static_cast<char>(byte);
                           return std::isprint(static_cast<unsigned char>(c));
                       });
}

std::string hexdump(const std::vector<uint8_t>& data) {
    std::ostringstream oss;
    for (size_t i = 0; i < data.size(); ++i) {
        oss << std::hex << std::uppercase << std::setw(2) << std::setfill('0')
            << static_cast<unsigned>(data[i]);
        if (i + 1 != data.size()) oss << ' ';
    }
    return oss.str();
}

bool isValidHexString(const std::string& hex) {
    if (hex.empty() || (hex.size() % 2) != 0) return false;
    for (char c : hex)
        if (!std::isxdigit(static_cast<unsigned char>(c)))
            return false;
    return true;
}

bool parseHexString(const std::string& hex, std::vector<uint8_t>& out) {
    if (!isValidHexString(hex)) return false;
    out.clear();
    out.reserve(hex.size() / 2);
    for (size_t i = 0; i < hex.size(); i += 2) {
        uint8_t high = std::isdigit(hex[i]) ? (hex[i] - '0')
        : (std::tolower(hex[i]) - 'a' + 10);
        uint8_t low  = std::isdigit(hex[i+1]) ? (hex[i+1] - '0')
        : (std::tolower(hex[i+1]) - 'a' + 10);
        out.push_back(static_cast<uint8_t>((high << 4) | low));
    }
    return true;
}



class LoraView {
public:
    using DeviceActivationCallback = std::function<void(std::shared_ptr<tt::hal::radio::RadioDevice>)>;
private:
    //using LoraParameters = tt::hal::lora::LoraParameters;

    std::vector<std::string> loraDevNames;
    std::vector<std::shared_ptr<tt::hal::radio::RadioDevice>> loraDevs;
    std::shared_ptr<tt::hal::radio::RadioDevice> loraDevice;

    DeviceActivationCallback cbDevActive;
    DeviceActivationCallback cbDevInactive;

    void queryLoraDevs() {
        auto radios = tt::hal::findDevices<tt::hal::radio::RadioDevice>(tt::hal::Device::Type::Radio);
        loraDevNames.clear();
        loraDevs.clear();

        for (const auto& radio: radios) {
            if (radio->canTransmit(tt::hal::radio::RadioDevice::Modulation::LoRa) &&
                radio->canReceive(tt::hal::radio::RadioDevice::Modulation::LoRa)) {
                loraDevNames.push_back(radio->getName());
                loraDevs.push_back(radio);
            }
        }
    }

    lv_obj_t* initDropdownInput(int row, const char* const label, const char* const items) {
        lv_obj_t* label_obj = lv_label_create(container);
        lv_label_set_text(label_obj, label);
        lv_obj_set_grid_cell(label_obj,
                             LV_GRID_ALIGN_STRETCH, 0, 1,
                             LV_GRID_ALIGN_CENTER, row, 1);
        lv_obj_set_size(label_obj, lv_pct(100), LV_SIZE_CONTENT);

        lv_obj_t* input = lv_dropdown_create(container);
        lv_obj_set_grid_cell(input,
                             LV_GRID_ALIGN_STRETCH, 1, 1,
                             LV_GRID_ALIGN_CENTER, row, 1);
        lv_obj_set_size(input, lv_pct(100), LV_SIZE_CONTENT);
        lv_dropdown_set_options(input, items);

        return input;
    }

    lv_obj_t* initFormInput(int row, const char* const label, const char* const defval, const char* const unit) {
        lv_obj_t* label_obj = lv_label_create(container);
        lv_label_set_text(label_obj, label);
        lv_obj_set_grid_cell(label_obj,
                             LV_GRID_ALIGN_STRETCH, 0, 1,
                             LV_GRID_ALIGN_CENTER, row, 1);
        lv_obj_set_size(label_obj, lv_pct(100), LV_SIZE_CONTENT);

        lv_obj_t* input = lv_textarea_create(container);
        lv_obj_set_grid_cell(input,
                             LV_GRID_ALIGN_STRETCH, 1, 1,
                             LV_GRID_ALIGN_CENTER, row, 1);
        lv_obj_set_size(input, lv_pct(100), LV_SIZE_CONTENT);
        lv_textarea_set_text(input, defval);
        lv_textarea_set_one_line(input, true);

        lv_obj_t* unit_obj = lv_label_create(container);
        lv_label_set_text(unit_obj, unit);
        lv_obj_set_grid_cell(unit_obj,
                             LV_GRID_ALIGN_STRETCH, 2, 1,
                             LV_GRID_ALIGN_CENTER, row, 1);
        lv_obj_set_size(unit_obj, lv_pct(100), LV_SIZE_CONTENT);
        lv_obj_set_style_text_align(unit_obj , LV_TEXT_ALIGN_CENTER, 0);

        return input;
    }

    lv_obj_t* initSliderInput(int row, const char* const label, int min, int max, int defval) {
        lv_obj_t* label_obj = lv_label_create(container);
        lv_label_set_text(label_obj, label);
        lv_obj_set_grid_cell(label_obj,
                             LV_GRID_ALIGN_STRETCH, 0, 1,
                             LV_GRID_ALIGN_CENTER, row, 1);
        lv_obj_set_size(label_obj, lv_pct(100), LV_SIZE_CONTENT);

        lv_obj_t* input = lv_slider_create(container);
        lv_obj_set_grid_cell(input,
                             LV_GRID_ALIGN_STRETCH, 1, 1,
                             LV_GRID_ALIGN_CENTER, row, 1);
        lv_obj_set_size(input, lv_pct(100), 10);
        lv_slider_set_range(input, min, max);

        lv_obj_t* number_obj = lv_label_create(container);
        //lv_label_set_text(number_obj, unit);
        lv_obj_set_grid_cell(number_obj,
                             LV_GRID_ALIGN_STRETCH, 2, 1,
                             LV_GRID_ALIGN_CENTER, row, 1);
        lv_obj_set_size(number_obj, lv_pct(100), LV_SIZE_CONTENT);
        lv_obj_set_style_text_align(number_obj , LV_TEXT_ALIGN_CENTER, 0);
        char buf[8] = {0};
        lv_snprintf(buf, sizeof(buf), "%d", defval);
        lv_label_set_text(number_obj, buf);

        lv_obj_add_event_cb(input, [](lv_event_t * e) {
            lv_obj_t* slider = lv_event_get_target_obj(e);
            lv_obj_t* label = (lv_obj_t*)lv_event_get_user_data(e);
            char buf[8] = {0};
            lv_snprintf(buf, sizeof(buf), "%d", (int)lv_slider_get_value(slider));
            lv_label_set_text(label, buf);
        }, LV_EVENT_VALUE_CHANGED, number_obj);

        lv_slider_set_value(input, defval, LV_ANIM_OFF);

        return input;
    }

    void initUi(lv_obj_t *parent) {
        container = lv_obj_create(parent);
        lv_obj_set_size(container, lv_pct(100), lv_pct(100));
        lv_obj_set_style_pad_all(container, 0, 0);

        int grid_row_size = 40;
        static lv_coord_t lora_col_dsc[] = {LV_GRID_FR(3), LV_GRID_FR(2), 45, LV_GRID_TEMPLATE_LAST};
        static lv_coord_t lora_row_dsc[] = {
            grid_row_size,
            grid_row_size,
            grid_row_size,
            grid_row_size,
            grid_row_size,
            grid_row_size,
            grid_row_size,
            grid_row_size,
            LV_GRID_TEMPLATE_LAST};
        lv_obj_set_grid_dsc_array(container, lora_col_dsc, lora_row_dsc);

        std::string dropdown_items = string::join(loraDevNames, "\n");
        loraDeviceInput = initDropdownInput(0, "LoRa Device", dropdown_items.c_str());
        loraDeviceOn = lv_switch_create(container);
        lv_obj_set_grid_cell(loraDeviceOn,
                             LV_GRID_ALIGN_STRETCH, 2, 1,
                             LV_GRID_ALIGN_CENTER, 0, 1);
        lv_obj_set_size(loraDeviceOn, lv_pct(100), 20);

        frequencyInput = initFormInput(1, "Frequency", "869.525", "MHz");
        bandwidthInput = initFormInput(2, "Bandwidth", "250", "kHz");
        syncwordInput = initFormInput(3, "Sync Word", "2B", "hex");
        deBitsInput = initSliderInput(4, "Coding Rate", 4, 8, 5);
        sfInput = initSliderInput(5, "Spread Factor", 7, 12, 11);
        preambleChirpsInput = initSliderInput(6, "Preamble Chirps", 4, 32, 16);
        txPowInput = initFormInput(7, "TX Power", "27",  "dBm");
        /*
        lv_obj_add_event_cb(frequencyInput, [](lv_event_t * e) {
            lv_obj_t* input = lv_event_get_target_obj(e);
            LoraParameters* params = (LoraParameters*)lv_event_get_user_data(e);
            std::string buf(lv_textarea_get_text(input));
            if (!buf.empty()) {
                params->frequency = std::stof(buf);
            }
        }, LV_EVENT_VALUE_CHANGED, &loraParams);

        lv_obj_add_event_cb(bandwidthInput, [](lv_event_t * e) {
            lv_obj_t* input = lv_event_get_target_obj(e);
            LoraParameters* params = (LoraParameters*)lv_event_get_user_data(e);
            std::string buf(lv_textarea_get_text(input));
            if (!buf.empty()) {
                params->bandwidth = std::stof(buf);
            }
        }, LV_EVENT_VALUE_CHANGED, &loraParams);

        lv_obj_add_event_cb(syncwordInput, [](lv_event_t * e) {
            lv_obj_t* input = lv_event_get_target_obj(e);
            LoraParameters* params = (LoraParameters*)lv_event_get_user_data(e);
            std::string buf(lv_textarea_get_text(input));
            if (!buf.empty()) {
                std::stringstream ss;
                ss << std::hex << buf;
                ss >> params->syncWord;
            }
        }, LV_EVENT_VALUE_CHANGED, &loraParams);

        lv_obj_add_event_cb(preambleChirpsInput, [](lv_event_t * e) {
            lv_obj_t* input = lv_event_get_target_obj(e);
            LoraParameters* params = (LoraParameters*)lv_event_get_user_data(e);
            params->preambleLength = lv_slider_get_value(input);
        }, LV_EVENT_VALUE_CHANGED, &loraParams);

        lv_obj_add_event_cb(deBitsInput, [](lv_event_t * e) {
            lv_obj_t* input = lv_event_get_target_obj(e);
            LoraParameters* params = (LoraParameters*)lv_event_get_user_data(e);
            params->deBits = lv_slider_get_value(input);
        }, LV_EVENT_VALUE_CHANGED, &loraParams);

        lv_obj_add_event_cb(sfInput, [](lv_event_t * e) {
            lv_obj_t* input = lv_event_get_target_obj(e);
            LoraParameters* params = (LoraParameters*)lv_event_get_user_data(e);
            params->spreadFactor = lv_slider_get_value(input);
        }, LV_EVENT_VALUE_CHANGED, &loraParams);

        lv_obj_add_event_cb(txPowInput, [](lv_event_t * e) {
            lv_obj_t* input = lv_event_get_target_obj(e);
            LoraParameters* params = (LoraParameters*)lv_event_get_user_data(e);
            std::string buf(lv_textarea_get_text(input));
            if (!buf.empty()) {
                params->power = std::stoi(buf);
            }
        }, LV_EVENT_VALUE_CHANGED, &loraParams);
        */
        /*
        if (loraDevNames.size() > 0) {
            loraDevice = loraService->getDevice(loraDevNames[0]);
            if (loraDevice)
            {
                using State = hal::lora::LoraDevice::State;
                switch (loraDevice->getState()) {
                    case State::PendingOn:
                    case State::On:
                        setParameters(loraDevice->getParameters());
                        disableForm();
                        lv_obj_add_state(loraDeviceOn, LV_STATE_CHECKED);
                        break;
                    case State::Error:
                    case State::PendingOff:
                    case State::Off:
                    default:
                        break;
                }
            } else {
                TT_LOG_E(TAG, "Attempted to load device \"%s\", what is happening!?", loraDevNames[0].c_str());
            }
        }*/

        lv_obj_add_event_cb(loraDeviceOn, [](lv_event_t * e) {
            lv_obj_t* input = lv_event_get_target_obj(e);
            LoraView* self = (LoraView*)lv_event_get_user_data(e);
            if (lv_obj_has_state(input, LV_STATE_CHECKED)) {
                self->enableDevice();
            } else {
                self->disableDevice();
            }
        }, LV_EVENT_VALUE_CHANGED, this);

    }

    void disableForm()
    {
        lv_obj_add_state(loraDeviceInput, LV_STATE_DISABLED);
        lv_obj_add_state(frequencyInput, LV_STATE_DISABLED);
        lv_obj_add_state(bandwidthInput, LV_STATE_DISABLED);
        lv_obj_add_state(syncwordInput, LV_STATE_DISABLED);
        lv_obj_add_state(deBitsInput, LV_STATE_DISABLED);
        lv_obj_add_state(preambleChirpsInput, LV_STATE_DISABLED);
        lv_obj_add_state(txPowInput, LV_STATE_DISABLED);
    }

    void enableForm()
    {
        lv_obj_clear_state(loraDeviceInput, LV_STATE_DISABLED);
        lv_obj_clear_state(frequencyInput, LV_STATE_DISABLED);
        lv_obj_clear_state(bandwidthInput, LV_STATE_DISABLED);
        lv_obj_clear_state(syncwordInput, LV_STATE_DISABLED);
        lv_obj_clear_state(deBitsInput, LV_STATE_DISABLED);
        lv_obj_clear_state(preambleChirpsInput, LV_STATE_DISABLED);
        lv_obj_clear_state(txPowInput, LV_STATE_DISABLED);
    }

public:
    //std::shared_ptr<hal::lora::LoraDevice> loraDevice;
    lv_obj_t* container;
    lv_obj_t* loraDeviceOn;
    lv_obj_t* loraDeviceInput;
    lv_obj_t* frequencyInput;
    lv_obj_t* bandwidthInput;
    lv_obj_t* syncwordInput;
    lv_obj_t* deBitsInput;
    lv_obj_t* sfInput;
    lv_obj_t* preambleChirpsInput;
    lv_obj_t* txPowInput;

    LoraView(lv_obj_t *parent) {
        queryLoraDevs();
        initUi(parent);
        setParameters(
            869.525,
            250.0,
            0x2B,
            16,
            5,
            11,
            22
        );
    }

    void onDeviceActivation(DeviceActivationCallback cb) {
        cbDevActive = cb;
    }

    void onDeviceDeactivation(DeviceActivationCallback cb) {
        cbDevInactive = cb;
    }

    void setParameters(float frequency, float bandwidth, uint8_t syncWord, uint16_t preambleLength, uint8_t codingRate, uint8_t spreadFactor, int8_t power) {
        std::string buf;

        buf = std::format("{:.6f}", frequency);
        lv_textarea_set_text(frequencyInput, buf.c_str());
        buf = std::format("{:.2f}", bandwidth);
        lv_textarea_set_text(bandwidthInput, buf.c_str());
        buf = std::format("{:X}", syncWord);
        lv_textarea_set_text(syncwordInput, buf.c_str());
        lv_slider_set_value(preambleChirpsInput, preambleLength, LV_ANIM_OFF);
        lv_slider_set_value(deBitsInput, codingRate, LV_ANIM_OFF);
        lv_slider_set_value(sfInput, spreadFactor, LV_ANIM_OFF);
        buf = std::format("{:d}", power);
        lv_textarea_set_text(txPowInput, buf.c_str());
    }

    void configureFromForm() {
        using enum tt::hal::radio::RadioDevice::Parameter;
        using enum tt::hal::radio::RadioDevice::ParameterStatus;

        std::string buffer;
        int value = 0;
        bool configured = true;
        buffer = lv_textarea_get_text(frequencyInput);
        if (!buffer.empty()) {
            configured &= (loraDevice->setParameter(Frequency, std::stof(buffer)) == Success);
        }
        buffer = lv_textarea_get_text(bandwidthInput);
        if (!buffer.empty()) {
            configured &= (loraDevice->setParameter(Bandwidth, std::stof(buffer)) == Success);
        }
        buffer = lv_textarea_get_text(syncwordInput);
        if (!buffer.empty()) {
            uint8_t syncWord = 0;
            std::stringstream ss(buffer);
            ss >> std::hex >> syncWord;

            configured &= (loraDevice->setParameter(SyncWord, std::stoi(buffer, nullptr, 16)) == Success);
        }
        value = lv_slider_get_value(deBitsInput);
        configured &= (loraDevice->setParameter(CodingRate, value) == Success);

        value = lv_slider_get_value(sfInput);
        configured &= (loraDevice->setParameter(SpreadFactor, value) == Success);

        value = lv_slider_get_value(preambleChirpsInput);
        configured &= (loraDevice->setParameter(PreambleLength, value) == Success);

        buffer = lv_textarea_get_text(txPowInput);
        if (!buffer.empty()) {
            configured &= (loraDevice->setParameter(Power, std::stof(buffer)) == Success);
        }
    }

    void enableDevice()
    {
        loraDevice = loraDevs[lv_dropdown_get_selected(loraDeviceInput)];
        if (loraDevice) {
            disableForm();
            loraDevice->setModulation(tt::hal::radio::RadioDevice::Modulation::LoRa);
            configureFromForm();
            loraDevice->start();
            vTaskDelay(pdMS_TO_TICKS(500));
            if (loraDevice->getState() != tt::hal::radio::RadioDevice::State::On) {
                lv_obj_clear_state(loraDeviceOn, LV_STATE_CHECKED);
                enableForm();
            } else {
                cbDevActive(loraDevice);
            }
        } else {
            lv_obj_clear_state(loraDeviceOn, LV_STATE_CHECKED);
        }
    }

    void disableDevice()
    {
        if (loraDevice) {
            loraDevice->stop();
            cbDevInactive(loraDevice);
        }
        enableForm();
    }
};

class ChirpChatterApp : public App {

    lv_obj_t* sidebar = nullptr;
    lv_obj_t* mainView = nullptr;
    lv_obj_t* progressBar = nullptr;
    lv_obj_t* progressText = nullptr;

    lv_obj_t* messageList = nullptr;
    lv_obj_t* inputField = nullptr;

    lv_obj_t* messageView = nullptr;
    LoraView* loraView = nullptr;

    hal::radio::RadioDevice::RxSubscriptionId rxSubId;
    std::shared_ptr<tt::hal::radio::RadioDevice> loraDevice;

    template<CCViews T>
    lv_obj_t* createSidebarButton(lv_obj_t* parent, const char* image_file) {
        auto* sidebar_button = lv_button_create(parent);

        lv_obj_set_size(sidebar_button, 32, 32);
        lv_obj_align(sidebar_button, LV_ALIGN_TOP_MID, 0, 0);
        lv_obj_set_style_pad_all(sidebar_button, 0, 0);
        //lv_obj_set_style_pad_top(sidebar_button, 36, 0);
        lv_obj_set_style_shadow_width(sidebar_button, 0, 0);
        lv_obj_set_style_border_width(sidebar_button, 0, 0);
        lv_obj_set_style_bg_opa(sidebar_button, 0, LV_PART_MAIN);

        auto* button_image = lv_image_create(sidebar_button);
        lv_image_set_src(button_image, image_file);
        lv_obj_set_style_image_recolor(button_image, lv_theme_get_color_primary(parent), LV_STATE_DEFAULT);
        lv_obj_set_style_image_recolor_opa(button_image, LV_OPA_COVER, LV_STATE_DEFAULT);
        // Ensure buttons are still tappable when asset fails to load
        lv_obj_set_size(button_image, 32, 32);

        static lv_style_t style_focus;
        lv_style_init(&style_focus);
        lv_style_set_outline_width(&style_focus, 0);
        lv_obj_add_style(sidebar_button, &style_focus, LV_PART_MAIN | LV_STATE_FOCUSED | LV_STATE_FOCUS_KEY);
        lv_obj_add_style(button_image, &style_focus, LV_PART_MAIN | LV_STATE_FOCUSED | LV_STATE_FOCUS_KEY);

        lv_obj_add_event_cb(button_image, changeViewHandler<T>, LV_EVENT_SHORT_CLICKED, (void*)this);
        lv_obj_add_event_cb(sidebar_button, changeViewHandler<T>, LV_EVENT_SHORT_CLICKED, (void*)this);

        lv_obj_add_event_cb(sidebar_button, buttonRecolorFocus, LV_EVENT_FOCUSED, button_image);
        lv_obj_add_event_cb(sidebar_button, buttonRecolorDefocus, LV_EVENT_DEFOCUSED, button_image);

        return sidebar_button;
    }

    void addDummyMessage(const char* const message) {
        auto* msg_container = lv_obj_create(messageList);
        lv_obj_set_flex_flow(msg_container, LV_FLEX_FLOW_COLUMN);
        lv_obj_set_flex_grow(msg_container, 0);
        lv_obj_set_style_pad_all(msg_container, 1, 0);
        lv_obj_add_flag(msg_container, LV_OBJ_FLAG_CLICKABLE);

        lv_obj_t* msg_label = lv_label_create(msg_container);
        lv_label_set_text(msg_label, message);
        lv_obj_set_width(msg_label, lv_pct(100));
        lv_label_set_long_mode(msg_label, LV_LABEL_LONG_WRAP);
        lv_obj_set_style_text_align(msg_label, LV_TEXT_ALIGN_LEFT, 0);
        lv_obj_set_style_pad_all(msg_label, 0, 0);

        lv_obj_t* msg_info = lv_label_create(msg_container);
        lv_label_set_text(msg_info, "RX/2024-07-06+15:04");
        lv_obj_set_width(msg_info, lv_pct(100));
        lv_label_set_long_mode(msg_info, LV_LABEL_LONG_WRAP);
        lv_obj_set_style_text_align(msg_info, LV_TEXT_ALIGN_LEFT, 0);
        lv_obj_set_style_pad_all(msg_info, 0, 0);
        lv_obj_set_style_text_font(msg_info, &lv_font_montserrat_10, 0);


        lv_obj_set_width(msg_container, lv_pct(100));
        lv_obj_set_height(msg_container,  LV_SIZE_CONTENT);

        lv_obj_scroll_to_y(messageList, lv_obj_get_scroll_y(messageList) + 1000, LV_ANIM_ON);

        /*auto* group = lv_group_get_default();
        lv_group_add_obj(group, msg_container);*/
    }

    void addPacketMessage(const hal::radio::RxPacket& packet) {
        auto* msg_container = lv_obj_create(messageList);
        lv_obj_set_flex_flow(msg_container, LV_FLEX_FLOW_COLUMN);
        lv_obj_set_flex_grow(msg_container, 0);
        lv_obj_set_style_pad_all(msg_container, 1, 0);
        lv_obj_add_flag(msg_container, LV_OBJ_FLAG_CLICKABLE);

        lv_obj_t* msg_label = lv_label_create(msg_container);

        std::string messageBuf = "";
        if (isPrintableData(packet.data)) {
            messageBuf = std::string(packet.data.begin(), packet.data.end());
        } else {
            messageBuf = hexdump(packet.data);
        }
        lv_label_set_text(msg_label, messageBuf.c_str());
        lv_obj_set_width(msg_label, lv_pct(100));
        lv_label_set_long_mode(msg_label, LV_LABEL_LONG_WRAP);
        lv_obj_set_style_text_align(msg_label, LV_TEXT_ALIGN_LEFT, 0);
        lv_obj_set_style_pad_all(msg_label, 0, 0);

        lv_obj_t* msg_info = lv_label_create(msg_container);

        auto t = std::time(nullptr);
        auto tm = *std::localtime(&t);
        std::stringstream ss;
        ss << "RX/RAW ";
        ss << std::put_time(&tm, "%Y-%m-%d %H:%M:%S");
        ss << " ";
        ss << "RSSI:" << packet.rssi;
        ss << " ";
        ss << "SNR:" << packet.snr;

        lv_label_set_text(msg_info, ss.str().c_str());
        lv_obj_set_width(msg_info, lv_pct(100));
        lv_label_set_long_mode(msg_info, LV_LABEL_LONG_WRAP);
        lv_obj_set_style_text_align(msg_info, LV_TEXT_ALIGN_LEFT, 0);
        lv_obj_set_style_pad_all(msg_info, 0, 0);
        lv_obj_set_style_text_font(msg_info, &lv_font_montserrat_10, 0);


        lv_obj_set_width(msg_container, lv_pct(100));
        lv_obj_set_height(msg_container,  LV_SIZE_CONTENT);

        lv_obj_scroll_to_y(messageList, lv_obj_get_scroll_y(messageList) + 1000, LV_ANIM_ON);

        /*auto* group = lv_group_get_default();
         *   lv_group_add_obj(group, msg_container);*/
    }

public:

    void onCreate(AppContext& appContext) override {
#ifdef ESP_PLATFORM
        esp_log_level_set("*", ESP_LOG_DEBUG);
#endif
    }

    void onDestroy(AppContext& appContext) override {

    }

    void onShow(AppContext& context, lv_obj_t* parent) override {

        static lv_coord_t grid_col_dsc[] = {36, LV_GRID_FR(1), LV_GRID_TEMPLATE_LAST};
        static lv_coord_t grid_row_dsc[] = {40, LV_GRID_FR(1), LV_GRID_TEMPLATE_LAST};

        lv_obj_t * grid = lv_obj_create(parent);
        lv_obj_set_grid_dsc_array(grid, grid_col_dsc, grid_row_dsc);
        lv_obj_set_size(grid, lv_pct(100), lv_pct(100));

        static lv_style_t style_grid;
        lv_style_init(&style_grid);
        lv_style_set_pad_row(&style_grid, 0);
        lv_style_set_pad_column(&style_grid, 0);
        lv_style_set_pad_all(&style_grid, 0);

        lv_obj_add_style(grid, &style_grid, LV_PART_MAIN);

        // Create toolbar
        auto* toolbar = tt::lvgl::toolbar_create(grid, "Welcome to ChirpChatter!");
        lv_obj_set_size(toolbar, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
        lv_obj_set_grid_cell(toolbar, LV_GRID_ALIGN_STRETCH, 0, 2,
                             LV_GRID_ALIGN_STRETCH, 0, 1);

        progressText =  lv_obj_get_child_by_type(toolbar, 0, &lv_label_class);
        lv_obj_align(progressText, LV_ALIGN_TOP_LEFT, 0, 0);
        lv_obj_set_style_text_font(progressText, &lv_font_montserrat_12, 0);
        lv_obj_set_size(progressText, lv_pct(75), LV_SIZE_CONTENT);

        // Create sidebar
        sidebar = lv_obj_create(grid);
        lv_obj_set_size(sidebar, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
        lv_obj_set_grid_cell(sidebar, LV_GRID_ALIGN_START, 0, 1,
                             LV_GRID_ALIGN_START, 1, 1);

        lv_obj_set_scrollbar_mode(sidebar, LV_SCROLLBAR_MODE_OFF);
        lv_obj_set_style_pad_all(sidebar, 2, 0);
        lv_obj_set_style_border_width(sidebar, 0, 0);
        lv_obj_set_flex_flow(sidebar, LV_FLEX_FLOW_COLUMN);

        // Create progress bar
        progressBar = lv_bar_create(toolbar);
        //lv_obj_set_flex_flow(toolbar, LV_FLEX_FLOW_COLUMN);
        lv_obj_align(progressBar, LV_ALIGN_RIGHT_MID, 0, 0);
        lv_obj_set_size(progressBar, lv_pct(25), 36);
        lv_obj_set_style_radius(progressBar, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
        lv_obj_set_style_radius(progressBar, 0, LV_PART_INDICATOR | LV_STATE_DEFAULT);
        lv_bar_set_range(progressBar, 0, 100);
        lv_bar_set_value(progressBar, 100, LV_ANIM_OFF);

        auto paths = context.getPaths();
        auto icon_msgs_path = paths->getSystemPathLvgl("icon_msgs.png");
        createSidebarButton<CCView_Msgs>(sidebar, icon_msgs_path.c_str());
        auto icon_lora_path = paths->getSystemPathLvgl("icon_lora.png");
        createSidebarButton<CCView_LoraSettings>(sidebar, icon_lora_path.c_str());
        auto icon_proto_path = paths->getSystemPathLvgl("icon_proto.png");
        createSidebarButton<CCView_ProtoSettings>(sidebar, icon_proto_path.c_str());


        // Main view
        /*mainView = lv_obj_create(grid);
        lv_obj_set_size(mainView, lv_pct(100), lv_pct(100));
        lv_obj_set_grid_cell(mainView, LV_GRID_ALIGN_STRETCH, 1, 1,
                             LV_GRID_ALIGN_STRETCH, 1, 1);
        //lv_obj_set_flex_flow(mainView, LV_FLEX_FLOW_COLUMN);
        lv_obj_set_style_bg_color(mainView, lv_color_hex(0x00FF00), 0);
        lv_obj_set_style_border_width(mainView, 0, 0);
        lv_obj_set_style_pad_all(mainView, 0, 0);
        */
        // Message view
        messageView = lv_obj_create(grid);
        //lv_obj_set_size(messageView, lv_disp_get_hor_res(NULL) - 40, lv_disp_get_ver_res(NULL) - toolbar_height*2);
        lv_obj_set_size(messageView, lv_pct(100), lv_pct(100));
        //lv_obj_set_flex_flow(messageView, LV_FLEX_FLOW_COLUMN);
        //lv_obj_set_flex_grow(messageView, 1);
        lv_obj_set_size(messageView, lv_pct(100), lv_pct(100));
        lv_obj_set_grid_cell(messageView, LV_GRID_ALIGN_STRETCH, 1, 1,
                             LV_GRID_ALIGN_STRETCH, 1, 1);

        lv_obj_set_style_pad_all(messageView, 0, 0);


        messageList = lv_obj_create(messageView);
        lv_obj_set_size(messageList, lv_pct(100), lv_pct(80));
        lv_obj_set_flex_flow(messageList, LV_FLEX_FLOW_COLUMN);
        lv_obj_align(messageList, LV_ALIGN_TOP_MID, 0, 0);
        ///lv_obj_set_style_bg_color(mainView, lv_color_hex(0xFF0000), 0);
        lv_obj_set_style_border_width(messageList, 0, 0);
        lv_obj_set_style_pad_all(messageList, 0, 0);
        lv_obj_add_flag(messageList, (lv_obj_flag_t)(LV_OBJ_FLAG_CLICKABLE | LV_OBJ_FLAG_SCROLL_ON_FOCUS));

        auto* group = lv_group_get_default();
        lv_group_add_obj(group, messageList);

        lv_obj_add_event_cb(messageList, buttonRecolorFocus, LV_EVENT_FOCUSED, nullptr);
        lv_obj_add_event_cb(messageList, buttonRecolorDefocus, LV_EVENT_DEFOCUSED, nullptr);

        /*
        messageList = lv_page_create(messageView, nullptr);
        lv_obj_set_size(messageList, lv_pct(100), lv_pct(80));
        lv_obj_set_style_border_width(messageList, 0, 0);
        lv_obj_set_style_pad_all(messageList, 0, 0);
        */
        // Input panel
        auto* input_panel = lv_obj_create(messageView);
        lv_obj_set_flex_flow(input_panel, LV_FLEX_FLOW_ROW);
        lv_obj_set_size(input_panel, lv_pct(100), LV_SIZE_CONTENT);
        lv_obj_align(input_panel, LV_ALIGN_BOTTOM_MID, 0, 0);
        lv_obj_set_flex_align(input_panel, LV_FLEX_ALIGN_SPACE_BETWEEN, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
        lv_obj_set_style_pad_all(input_panel, 5, 0);

        // Input field
        inputField = lv_textarea_create(input_panel);
        lv_obj_set_flex_grow(inputField, 1);
        lv_obj_set_height(inputField, LV_PCT(100));
        lv_textarea_set_placeholder_text(inputField, "Type a message...");
        lv_textarea_set_one_line(inputField, true);

        // Send button
        auto* send_btn = lv_btn_create(input_panel);
        lv_obj_set_size(send_btn, 50, LV_SIZE_CONTENT);
        //lv_obj_add_event_cb(send_btn, onSendClicked, LV_EVENT_CLICKED, this);

        auto* btn_label = lv_label_create(send_btn);
        lv_label_set_text(btn_label, "SEND");
        lv_obj_center(btn_label);

        lv_obj_set_flex_grow(messageList, 1);
        lv_obj_set_flex_grow(input_panel, 0);

        //lv_obj_set_style_bg_color(messageList, lv_color_hex(0xFF0000), 0);
        //lv_obj_set_style_bg_color(input_panel, lv_color_hex(0x00FF00), 0);

        //addDummyMessage("HELLO CHIRPCHAT!");
        //addDummyMessage("How's biz?");
        //addDummyMessage("Test");
        //addDummyMessage("Test empfangen in Linz");

        // LoRa settings view
        loraView = new LoraView(grid);
        lv_obj_set_grid_cell(loraView->container, LV_GRID_ALIGN_STRETCH, 1, 1,
                             LV_GRID_ALIGN_STRETCH, 1, 1);

        //loraView->onDeviceActivation(std::bind(&ChirpChatterApp::onDeviceActivation, this));
        //loraView->onDeviceDeactivation(std::bind(&ChirpChatterApp::onDeviceDeactivation, this));

        loraView->onDeviceActivation([this](std::shared_ptr<tt::hal::radio::RadioDevice> dev) { this->onDeviceActivation(dev); });
        loraView->onDeviceDeactivation([this](std::shared_ptr<tt::hal::radio::RadioDevice> dev) { this->onDeviceDeactivation(dev); });

        lv_obj_add_event_cb(send_btn, [](lv_event_t * e) {
            lv_obj_t* input = lv_event_get_target_obj(e);
            ChirpChatterApp* self = (ChirpChatterApp*)lv_event_get_user_data(e);
            self->sendMessage();
        }, LV_EVENT_SHORT_CLICKED, (void*)this);

        changeView(CCView_Msgs);
    }

    void onRxPacket(hal::Device::Id id, const hal::radio::RxPacket& packet) {
        addPacketMessage(packet);
    }

    void onDeviceActivation(std::shared_ptr<tt::hal::radio::RadioDevice> dev) {
        rxSubId = dev->subscribeRx([this](hal::Device::Id id, const hal::radio::RxPacket& packet) {
            this->onRxPacket(id, packet);
        });
        loraDevice = dev;
        std::ostringstream oss;
        oss << "Device \"" << dev->getName() << "\" online";

        lv_label_set_text(progressText, oss.str().c_str());
    }

    void onDeviceDeactivation(std::shared_ptr<tt::hal::radio::RadioDevice> dev) {
        dev->unsubscribeRx(rxSubId);
        loraDevice = nullptr;

        lv_label_set_text(progressText, "Offline");
    }

    void sendMessage() {
        std::string message = lv_textarea_get_text(inputField);
        std::vector<uint8_t> data;
        if (message == "!test1") {
            parseHexString("ffffffff1147fec0c60940e5810800009671ad09dd2a0e7841ce266a3d759e967dc32a16bf4d5eecafde28d82b690f22eccf968a", data);
        } else if (message == "!ack") {
            parseHexString("ffffffff1147fec0f1dee9ab81080000922bf53364151a15", data);
        } else if (message == "!gdn8") {
            parseHexString("ffffffff1147fec025ffdd7a81080000768023f848619a3782ddadb5f686dc", data);
        } else if (message == "!gm") {
            parseHexString("ffffffff1147fec08a27ff4b810800003185053566e837fb0b88ade5fc84d9a13e", data);
        } else if (message == "!ptest1") {
            parseHexString("ffffffff1147fec0ef0e5bd48108000035f4be1f4bf703b3cce235423dd218a0c9ec745032a1f04be19c", data);
        } else {
            data = std::vector<uint8_t>(message.begin(), message.end());
        }

        loraDevice->transmit(tt::hal::radio::TxPacket{.data = data}, [this](hal::radio::RadioDevice::TxId id, hal::radio::RadioDevice::TransmissionState state) {
           this->onTxStatus(id, state);
        });
    }

    void onTxStatus(hal::radio::RadioDevice::TxId id, hal::radio::RadioDevice::TransmissionState state) {
        using enum hal::radio::RadioDevice::TransmissionState;

        switch (state) {
            case Queued:
                lv_label_set_text(progressText, "Message queued...");
                lv_bar_set_value(progressBar, 25, LV_ANIM_ON);
                break;
            case PendingTransmit:
                lv_label_set_text(progressText, "Message transmitting...");
                lv_bar_set_value(progressBar, 50, LV_ANIM_ON);
                break;
            case Transmitted:
                lv_label_set_text(progressText, "Message transmitted!\nReturn to receive.");
                lv_bar_set_value(progressBar, 100, LV_ANIM_ON);
                break;
            case Timeout:
                lv_label_set_text(progressText, "Message transmit timed out!");
                lv_bar_set_value(progressBar, 100, LV_ANIM_ON);
                break;
            case Error:
                lv_label_set_text(progressText, "Error transmitting message!");
                lv_bar_set_value(progressBar, 100, LV_ANIM_ON);
                break;
        }
    }

    void changeView(const CCViews view) {
        lv_obj_add_flag(messageView, LV_OBJ_FLAG_HIDDEN);
        lv_obj_add_flag(loraView->container, LV_OBJ_FLAG_HIDDEN);

        switch (view) {
            case CCView_Msgs:
                lv_obj_clear_flag(messageView, LV_OBJ_FLAG_HIDDEN);
                break;
            case CCView_LoraSettings:
                lv_obj_clear_flag(loraView->container, LV_OBJ_FLAG_HIDDEN);
                break;
            default:
                break;

        }
    }

    ~ChirpChatterApp() override = default;
};

extern const AppManifest manifest = {
    .id = "ChirpChatter",
    .name = "ChirpChatter",
    .icon = TT_ASSETS_APP_ICON_CHAT,
    .createApp = create<ChirpChatterApp>
};

}

//#endif
