/*
 * SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
 *
 * SPDX-License-Identifier: MIT
 */
/*!
  @file M5UnitUnified.cpp
  @brief class UnitUnified
*/
#include "M5UnitUnified.hpp"
#include <M5Utility.hpp>

namespace m5 {
namespace unit {

uint32_t UnitUnified::_registerCount{0};

bool UnitUnified::add(Component& u, m5::hal::bus::Bus* bus)
{
    if (u.isRegistered()) {
        M5_LIB_LOGW("Already added");
        return false;
    }
    if (!bus) {
        M5_LIB_LOGE("Bus null");
        return false;
    }

    M5_LIB_LOGD("Add [%s]:0x%02x", u.deviceName(), u.address());

    u._manager = this;
    if (u.assign(bus)) {
        u._order = ++_registerCount;
        _units.emplace_back(&u);
        return add_children(u);
    }
    M5_LIB_LOGE("Failed to assign %s:%u", u.deviceName(), u.canAccessI2C());
    return false;
}

bool UnitUnified::add(Component& u, TwoWire& wire)
{
    if (u.isRegistered()) {
        M5_LIB_LOGW("Already added");
        return false;
    }

    M5_LIB_LOGD("Add [%s] addr:%02x children:%zu", u.deviceName(), u.address(), u.childrenSize());

    u._manager = this;
    if (u.assign(wire)) {
        u._order = ++_registerCount;
        _units.emplace_back(&u);
        return add_children(u);
    }
    M5_LIB_LOGE("Failed to assign %s:%u", u.deviceName(), u.canAccessI2C());
    return false;
}

bool UnitUnified::add(Component& u, const int8_t rx_pin, const int8_t tx_pin)
{
    if (u.isRegistered()) {
        M5_LIB_LOGW("Already added");
        return false;
    }

    M5_LIB_LOGD("Add [%s] rx:%d tx:%d %zu", u.deviceName(), rx_pin, tx_pin, u.childrenSize());

    u._manager = this;
    if (u.assign(rx_pin, tx_pin)) {
        u._order = ++_registerCount;
        _units.emplace_back(&u);
        return add_children(u);
    }
    M5_LIB_LOGE("Failed to assign %s:%u", u.deviceName(), u.canAccessGPIO());
    return false;
}

// Add children if exists
bool UnitUnified::add_children(Component& u)
{
    auto it = u.childBegin();
    while (it != u.childEnd()) {
        auto ch = it->channel();

        M5_LIB_LOGV("%s child:%s channel:%u", u.deviceName(), it->deviceName(), ch);
        if (it->isRegistered()) {
            M5_LIB_LOGE("Already registered %s", it->deviceName());
            return false;
        }
        it->_manager = this;
        it->_adapter = u.ensure_adapter(ch);
        M5_LIB_LOGD("  Shared:%u %u", u._adapter.use_count(), it->_adapter.use_count());
        it->_order = ++_registerCount;
        _units.emplace_back(&*it);

        if (!add_children(*it)) {
            return false;
        }
        ++it;
    }
    return true;
}

bool UnitUnified::begin()
{
    return !std::any_of(_units.begin(), _units.end(), [](Component* c) {
        M5_LIB_LOGV("Try begin:%s", c->deviceName());
        bool ret = c->_begun = c->begin();
        if (!ret) {
            M5_LIB_LOGE("Failed to begin: %s", c->debugInfo().c_str());
        }
        return !ret;
    });
}

void UnitUnified::update(const bool force)
{
    // Order of registration
    for (auto&& u : _units) {
        if (!u->_component_cfg.self_update && u->_begun) {
            u->update(force);
        }
    }
}

std::string UnitUnified::debugInfo() const
{
    std::string s = m5::utility::formatString("\nM5UnitUnified: %zu units\n", _units.size());
    for (auto&& u : _units) {
        if (!u->hasParent()) {
            s += make_unit_info(u, 0);
        }
    }
    return m5::utility::trim(s);
}

std::string UnitUnified::make_unit_info(const Component* u, const uint8_t indent) const
{
    std::string s = m5::utility::formatString("%*c%s\n", indent * 4, ' ', u->debugInfo().c_str());

    if (u->hasChildren()) {
        s += make_unit_info(u->_child, indent + 1);
    }
    u = u->_next;
    return u ? s += make_unit_info(u, indent) : s;
}

}  // namespace unit
}  // namespace m5
