#include "SubsurfaceTree.hpp"
#include "../events/Events.hpp"

void addSurfaceGlobalOffset(SSurfaceTreeNode* node, int* lx, int* ly) {
    *lx += node->pSurface->sx;
    *ly += node->pSurface->sy;

    if (node->offsetfn) {
        // This is the root node
        RASSERT(!node->pSubsurface, "Node had no subsurface!");
        node->offsetfn(node->globalOffsetData, lx, ly);
    } else {
        RASSERT(node->pSubsurface, "Node had no subsurface!");

        *lx += node->pSubsurface->pSubsurface->current.x;
        *ly += node->pSubsurface->pSubsurface->current.y;

        addSurfaceGlobalOffset(node->pParent, lx, ly);
    }
}

SSurfaceTreeNode* createTree(wlr_surface* pSurface) {
    SubsurfaceTree::surfaceTreeNodes.push_back(SSurfaceTreeNode());

    const auto PNODE = &SubsurfaceTree::surfaceTreeNodes.back();

    PNODE->pSurface = pSurface;

    PNODE->listen_newSubsurface.notify = Events::listener_newSubsurfaceNode;
    PNODE->listen_commit.notify = Events::listener_commitSubsurface;
    PNODE->listen_destroy.notify = Events::listener_destroySubsurfaceNode;

    wl_signal_add(&pSurface->events.commit, &PNODE->listen_commit);
    wl_signal_add(&pSurface->events.destroy, &PNODE->listen_destroy);
    wl_signal_add(&pSurface->events.new_subsurface, &PNODE->listen_newSubsurface);

    return PNODE;
}

SSurfaceTreeNode* createSubsurfaceNode(SSurfaceTreeNode* pParent, SSubsurface* pSubsurface, wlr_surface* surface) {
    const auto PNODE = createTree(surface);
    PNODE->pParent = pParent;
    PNODE->pSubsurface = pSubsurface;

    return PNODE;
}

SSurfaceTreeNode* SubsurfaceTree::createTreeRoot(wlr_surface* pSurface, applyGlobalOffsetFn fn, void* data) {
    const auto PNODE = createTree(pSurface);
    PNODE->offsetfn = fn;
    PNODE->globalOffsetData = data;

    return PNODE;
}

void destroySubsurface(SSubsurface* pSubsurface);

void SubsurfaceTree::destroySurfaceTree(SSurfaceTreeNode* pNode) {
    bool exists = false;
    for (auto& n : surfaceTreeNodes) {
        if (&n == pNode) {
            exists = true;
            break;
        }
    }

    RASSERT(exists, "Tried to delete a surfaceTreeNode that doesn't exist!");

    for (auto& c : pNode->childSubsurfaces)
        destroySubsurface(&c);

    pNode->childSubsurfaces.clear();

    wl_list_remove(&pNode->listen_newSubsurface.link);
    wl_list_remove(&pNode->listen_commit.link);
    wl_list_remove(&pNode->listen_destroy.link);

    Debug::log(LOG, "SurfaceTree Node removed");
}

void destroySubsurface(SSubsurface* pSubsurface) {
    if (pSubsurface->pChild) {
        SubsurfaceTree::destroySurfaceTree(pSubsurface->pChild);
        pSubsurface->pChild = nullptr;
    }

    wl_list_remove(&pSubsurface->listen_map.link);
    wl_list_remove(&pSubsurface->listen_unmap.link);
    wl_list_remove(&pSubsurface->listen_destroy.link);
}

//
// Subsurface listeners
//

void Events::listener_newSubsurfaceNode(wl_listener* listener, void* data) {
    SSurfaceTreeNode* pNode = wl_container_of(listener, pNode, listen_newSubsurface);

    const auto PSUBSURFACE = (wlr_subsurface*)data;

    pNode->childSubsurfaces.push_back(SSubsurface());
    const auto PNEWSUBSURFACE = &pNode->childSubsurfaces.back();

    Debug::log(LOG, "Added a new subsurface %x", PSUBSURFACE);

    PNEWSUBSURFACE->pSubsurface = PSUBSURFACE;
    PNEWSUBSURFACE->pParent = pNode;

    PNEWSUBSURFACE->listen_map.notify = Events::listener_mapSubsurface;
    PNEWSUBSURFACE->listen_unmap.notify = Events::listener_unmapSubsurface;
    PNEWSUBSURFACE->listen_destroy.notify = Events::listener_destroySubsurface;

    wl_signal_add(&PSUBSURFACE->events.map, &PNEWSUBSURFACE->listen_map);
    wl_signal_add(&PSUBSURFACE->events.unmap, &PNEWSUBSURFACE->listen_unmap);
    wl_signal_add(&PSUBSURFACE->events.destroy, &PNEWSUBSURFACE->listen_destroy);
}

void Events::listener_mapSubsurface(wl_listener* listener, void* data) {
    SSubsurface* subsurface = wl_container_of(listener, subsurface, listen_map);

    Debug::log(LOG, "Subsurface %x mapped", subsurface->pSubsurface);

    subsurface->pChild = createSubsurfaceNode(subsurface->pParent, subsurface, subsurface->pSubsurface->surface);
}

void Events::listener_unmapSubsurface(wl_listener* listener, void* data) {
    SSubsurface* subsurface = wl_container_of(listener, subsurface, listen_unmap);

    Debug::log(LOG, "Subsurface %x unmapped", subsurface);

    if (subsurface->pChild) {
        const auto PNODE = subsurface->pChild;

        int lx = 0, ly = 0;
        addSurfaceGlobalOffset(PNODE, &lx, &ly);

        wlr_box extents = {0};
        wlr_surface_get_extends(PNODE->pSurface, &extents);

        extents.x += lx;
        extents.y += ly;

        SubsurfaceTree::destroySurfaceTree(subsurface->pChild);
        subsurface->pChild = nullptr;
    }
}

void Events::listener_commitSubsurface(wl_listener* listener, void* data) {
    SSurfaceTreeNode* pNode = wl_container_of(listener, pNode, listen_commit);
}

void Events::listener_destroySubsurface(wl_listener* listener, void* data) {
    SSubsurface* subsurface = wl_container_of(listener, subsurface, listen_destroy);

    Debug::log(LOG, "Subsurface %x destroyed", subsurface);

    subsurface->pParent->childSubsurfaces.remove(*subsurface);
}

void Events::listener_destroySubsurfaceNode(wl_listener* listener, void* data) {
    SSurfaceTreeNode* pNode = wl_container_of(listener, pNode, listen_destroy);

    Debug::log(LOG, "Subsurface Node %x destroyed", pNode);

    for (auto& c : pNode->childSubsurfaces)
        destroySubsurface(&c);

    wl_list_remove(&pNode->listen_newSubsurface.link);
    wl_list_remove(&pNode->listen_commit.link);
    wl_list_remove(&pNode->listen_destroy.link);

    SubsurfaceTree::surfaceTreeNodes.remove(*pNode);
}