#include "CCArmatureDisplay.h"
#include "CCSlot.h"

DRAGONBONES_NAMESPACE_BEGIN

CCArmatureDisplay* CCArmatureDisplay::create()
{
    CCArmatureDisplay* displayContainer = new CCArmatureDisplay();
    if (displayContainer->init())
    {
        displayContainer->autorelease();
    }
    else
    {
        AX_SAFE_DELETE(displayContainer);
    }

    return displayContainer;
}

void CCArmatureDisplay::dbInit(Armature* armature)
{
    _armature = armature;
}

void CCArmatureDisplay::dbClear()
{
    setEventDispatcher(ax::Director::getInstance()->getEventDispatcher());

    _armature = nullptr;
    AX_SAFE_RELEASE(_dispatcher);
    release();
}

void CCArmatureDisplay::dispose(bool disposeProxy)
{
    if (_armature != nullptr)
    {
        _armature->dispose();
        _armature = nullptr;
    }
}

void CCArmatureDisplay::dbUpdate()
{
    const auto drawed = DragonBones::debugDraw;
    if (drawed || _debugDraw)
    {
        _debugDraw = drawed;
        if (_debugDraw)
        {}
        else
        {
            // TODO
        }
    }
}

void CCArmatureDisplay::addDBEventListener(std::string_view type, const std::function<void(EventObject*)>& callback)
{
    auto lambda = [callback](ax::EventCustom* event) -> void {
        callback(static_cast<EventObject*>(event->getUserData()));
    };
    _dispatcher->addCustomEventListener(type, lambda);
}

void CCArmatureDisplay::dispatchDBEvent(std::string_view type, EventObject* value)
{
    _dispatcher->dispatchCustomEvent(type, value);
}

void CCArmatureDisplay::removeDBEventListener(std::string_view type, const std::function<void(EventObject*)>& callback)
{
    // TODO
    _dispatcher->removeCustomEventListeners(type);
}

ax::Rect CCArmatureDisplay::getBoundingBox() const
{
    auto isFirst = true;
    float minX   = 0.0f;
    float minY   = 0.0f;
    float maxX   = 0.0f;
    float maxY   = 0.0f;

    for (const auto slot : _armature->getSlots())
    {
        if (!slot->getVisible() || !slot->getDisplay())
        {
            continue;
        }

        const auto display = static_cast<CCSlot*>(slot)->getCCDisplay();
        const auto bounds  = display->getBoundingBox();
        if (isFirst)
        {
            isFirst = false;
            minX    = bounds.getMinX();
            minY    = bounds.getMinY();
            maxX    = bounds.getMaxX();
            maxY    = bounds.getMaxY();
        }
        else
        {
            minX = std::min(minX, bounds.getMinX());
            minY = std::min(minY, bounds.getMinY());
            maxX = std::max(maxX, bounds.getMaxX());
            maxY = std::max(maxY, bounds.getMaxY());
        }
    }

    ax::Rect rect(minX, minY, maxX - minX, maxY - minY);

    return ax::RectApplyTransform(rect, getNodeToParentTransform());
}

DBCCSprite* DBCCSprite::create()
{
    DBCCSprite* sprite = new DBCCSprite();

    if (sprite->init())
    {
        sprite->autorelease();
    }
    else
    {
        AX_SAFE_DELETE(sprite);
    }

    return sprite;
}

bool DBCCSprite::_checkVisibility(const ax::Mat4& transform, const ax::Size& size, const ax::Rect& rect)
{
    auto scene = ax::Director::getInstance()->getRunningScene();

    // If draw to Rendertexture, return true directly.
    //  only cull the default camera. The culling algorithm is valid for default camera.
    if (!scene || (scene && scene->getDefaultCamera() != ax::Camera::getVisitingCamera()))
        return true;

    auto director = ax::Director::getInstance();
    ax::Rect visiableRect(director->getVisibleOrigin(), director->getVisibleSize());

    // transform center point to screen space
    float hSizeX = size.width / 2;
    float hSizeY = size.height / 2;

    ax::Vec3 v3p(hSizeX, hSizeY, 0);

    transform.transformPoint(&v3p);
    ax::Vec2 v2p = ax::Camera::getVisitingCamera()->projectGL(v3p);

    // convert content size to world coordinates
    float wshw = std::max(fabsf(hSizeX * transform.m[0] + hSizeY * transform.m[4]),
                          fabsf(hSizeX * transform.m[0] - hSizeY * transform.m[4]));
    float wshh = std::max(fabsf(hSizeX * transform.m[1] + hSizeY * transform.m[5]),
                          fabsf(hSizeX * transform.m[1] - hSizeY * transform.m[5]));

    // enlarge visible rect half size in screen coord
    visiableRect.origin.x -= wshw;
    visiableRect.origin.y -= wshh;
    visiableRect.size.width += wshw * 2;
    visiableRect.size.height += wshh * 2;
    bool ret = visiableRect.containsPoint(v2p);
    return ret;
}

void DBCCSprite::draw(ax::Renderer* renderer, const ax::Mat4& transform, uint32_t flags)
{
#if AX_USE_CULLING
#    if COCOS2D_VERSION >= 0x00031400
    const auto& rect = _polyInfo.getRect();
#    else
    const auto& rect = _polyInfo.rect;
#    endif

    // Don't do calculate the culling if the transform was not updated
    auto visitingCamera = ax::Camera::getVisitingCamera();
    auto defaultCamera  = ax::Camera::getDefaultCamera();
    if (visitingCamera == defaultCamera)
    {
        _insideBounds = ((flags & FLAGS_TRANSFORM_DIRTY) || visitingCamera->isViewProjectionUpdated())
                            ? _checkVisibility(transform, _contentSize, rect)
                            : _insideBounds;
    }
    else
    {
        _insideBounds = _checkVisibility(transform, _contentSize, rect);
    }

    if (_insideBounds)
#endif
    {
#if COCOS2D_VERSION >= 0x00040000
        _trianglesCommand.init(_globalZOrder, _texture, _blendFunc, _polyInfo.triangles, transform, flags);
#else
        _trianglesCommand.init(_globalZOrder, _texture->getName(), getGLProgramState(), _blendFunc, _polyInfo.triangles,
                               transform, flags);
#endif
        renderer->addCommand(&_trianglesCommand);

#if AX_SPRITE_DEBUG_DRAW
        _debugDrawNode->clear();
        auto count   = _polyInfo.triangles.indexCount / 3;
        auto indices = _polyInfo.triangles.indices;
        auto verts   = _polyInfo.triangles.verts;
        for (ssize_t i = 0; i < count; i++)
        {
            // draw 3 lines
            auto from = verts[indices[i * 3]].vertices;
            auto to   = verts[indices[i * 3 + 1]].vertices;
            _debugDrawNode->drawLine(ax::Vec2(from.x, from.y), ax::Vec2(to.x, to.y), ax::Color4F::WHITE);

            from = verts[indices[i * 3 + 1]].vertices;
            to   = verts[indices[i * 3 + 2]].vertices;
            _debugDrawNode->drawLine(ax::Vec2(from.x, from.y), ax::Vec2(to.x, to.y), ax::Color4F::WHITE);

            from = verts[indices[i * 3 + 2]].vertices;
            to   = verts[indices[i * 3]].vertices;
            _debugDrawNode->drawLine(ax::Vec2(from.x, from.y), ax::Vec2(to.x, to.y), ax::Color4F::WHITE);
        }
#endif  // AX_SPRITE_DEBUG_DRAW
    }
}

ax::PolygonInfo& DBCCSprite::getPolygonInfoModify()
{
    return _polyInfo;
}

DRAGONBONES_NAMESPACE_END