/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2026 Olaf Wintermann. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   1. Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *
 *   2. Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "action.h"
#include "context.h"
#include "object.h"

#include <cx/string.h>

void uic_add_action(
        UiContext *ctx,
        const char *name,
        ui_callback callback,
        void *userdata,
        const char *accelerator,
        const char *accelerator_text)
{
    if(!name) {
        return;
    }
    
    UiAction action;
    action.name = ui_strdup(ctx, name);
    action.callback = callback;
    action.userdata = userdata;
    action.accelerator = accelerator ? ui_strdup(ctx, accelerator) : NULL;
    action.accelerator_text = accelerator_text ? ui_strdup(ctx, accelerator_text) : NULL;
    action.ctx = ctx;
    cxMapPut(ctx->actions, name, &action);
    cxMapRehash(ctx->actions);
}

void uic_bind_action(
        UiContext *ctx,
        const char *action,
        void *bind_obj,
        ui_enablefunc set_enabled)
{
    if(!action) {
        return;
    }
    
    UiActionBinding binding;
    binding.action = ui_strdup(ctx, action);
    binding.userdata = bind_obj;
    binding.set_enabled = set_enabled;
    cxListAdd(ctx->action_bindings, &binding);
}

UiAction* uic_resolve_action(UiContext *ctx, const char *action) {
    UiAction *a = NULL;
    if(ctx->actions) {
        a = cxMapGet(ctx->actions, action);
    }
    // check if any sub-document defines this action
    // sub-document actions have precedence, the most specific action will
    // be returned
    CxIterator i = cxListIterator(ctx->documents);
    cx_foreach(void *, doc, i) {
        UiContext *doc_ctx = ui_document_context(doc);
        UiAction *sub_action = uic_resolve_action(doc_ctx, action);
        if(sub_action) {
            a = sub_action;
            // if one sub-tree has an action, we don't care about other
            // subtrees
            break;
        }
    }
    
    if(!a && ctx->parent) {
        // check parents
        a = uic_resolve_action_from_parents(ctx, action);
    }
    
    return a;
}

UiAction* uic_resolve_action_from_parents(UiContext *ctx, const char *action) {
    UiContext *parent = ctx->parent;
    if(parent == NULL) {
        return NULL;
    }
    if(parent->actions) {
        UiAction *a = cxMapGet(parent->actions, action);
        if(a) {
            return a;
        }
    }
    return uic_resolve_action_from_parents(parent, action);
}



void ui_update_action_bindings(UiContext *ctx) {
    CxIterator i = cxListIterator(ctx->action_bindings);
    cx_foreach(UiActionBinding*, binding, i) {
        UiAction *action = uic_resolve_action(ctx, binding->action);
        if(binding->set_enabled) {
            binding->set_enabled(binding->userdata, action != NULL);
        }
    }
}

void uic_action_callback(UiEvent *event, const char *action_name) {
    UiContext *ctx = ui_global_context();
    if(event->obj) {
        ctx = event->obj->ctx;
    }
    
    UiAction *action = uic_resolve_action(ctx, action_name);
    if(action) {
        // override event document: for actions we know that the event is
        // for a specific document
        event->document = action->ctx->self_doc;
        if(action->callback) {
            action->callback(event, action->userdata);
        }
    }
}

void ui_call_action(UiContext *ctx, const char *action_name) {
    ui_call_action2(ctx, action_name, NULL, 0);
}

void ui_call_action2(UiContext *ctx, const char *action_name, void *eventdata, int intval) {
    UiAction *action = uic_resolve_action(ctx, action_name);
    if(action && action->callback) {
        UiEvent event;
        memset(&event, 0, sizeof(UiEvent));
        event.obj = ctx->obj;
        event.window = event.obj ? event.obj->window : NULL;
        event.document = ctx->self_doc ? ctx->self_doc : ctx->document;
        if(eventdata) {
            event.eventdata = eventdata;
            event.eventdatatype = UI_EVENT_DATA_POINTER;
        }
        event.intval = intval;
        action->callback(&event, action->userdata);
    }
}

void ui_broadcast_action(const char *action_name) {
    ui_broadcast_action2(action_name, NULL, 0);
}

void ui_broadcast_action2(const char *action_name, void *eventdata, int intval) {
    CxList *objects = uic_object_list();
    CxIterator i = cxListIterator(objects);
    cx_foreach(UiObject*, obj, i) {
        ui_call_action2(obj->ctx, action_name, eventdata, intval);
    }
}

typedef struct UiActionBroadcast {
    char *action;
    void *eventdata;
    int intval;
} UiActionBroadcast;

void ui_mainthread_broadcast(const char *action_name) {
    ui_mainthread_broadcast2(action_name, NULL, 0);
}

static int mainthread_action_broadcast(void *data) {
    UiActionBroadcast *broadcast = data;
    ui_broadcast_action2(broadcast->action, broadcast->eventdata, broadcast->intval);
    free(broadcast->action);
    free(broadcast);
    return 0;
}

void ui_mainthread_broadcast2(const char *action_name, void *eventdata, int intval) {
    UiActionBroadcast *broadcast = malloc(sizeof(UiActionBroadcast));
    broadcast->action = strdup(action_name);
    broadcast->eventdata = eventdata;
    broadcast->intval = intval;
    ui_call_mainthread(mainthread_action_broadcast, broadcast);
}
