/** Copyright 2011-2013 Thorsten Wißmann. All rights reserved.
 *
 * This software is licensed under the "Simplified BSD License".
 * See LICENSE for details */

#include "object.h"
#include "command.h"
#include "utils.h"
#include "assert.h"
#include "globals.h"
#include "ipc-protocol.h"

#include <string.h>
#include <stdlib.h>
#include <stdio.h>

typedef struct {
    char*       name;
    HSObject*   child;
} HSObjectChild;

static void hsobjectchild_destroy(HSObjectChild* oc);
static HSObjectChild* hsobjectchild_create(const char* name, HSObject* obj);
static void hsattribute_free(HSAttribute* attr);

static HSObject g_root_object;
static HSObject* g_tmp_object;

void object_tree_init() {
    hsobject_init(&g_root_object);
    g_tmp_object = hsobject_create_and_link(&g_root_object, TMP_OBJECT_PATH);

}

void object_tree_destroy() {
    hsobject_unlink_and_destroy(&g_root_object, g_tmp_object);
    hsobject_free(&g_root_object);
}

HSObject* hsobject_root() {
    return &g_root_object;
}

bool hsobject_init(HSObject* obj) {
    obj->attributes = NULL;
    obj->attribute_count = 0;
    obj->children = NULL;
    return true;
}

void hsobject_free(HSObject* obj) {
    for (int i = 0; i < obj->attribute_count; i++) {
        hsattribute_free(obj->attributes + i);
    }
    g_free(obj->attributes);
    g_list_free_full(obj->children, (GDestroyNotify)hsobjectchild_destroy);
}

static void hsattribute_free(HSAttribute* attr) {
    if (attr->user_data) {
        g_free(attr->name);
        if (attr->type == HSATTR_TYPE_STRING) {
            g_string_free(attr->user_data->str, true);
        }
        g_free(attr->user_data);
    }
    if (attr->unparsed_value) {
        g_string_free(attr->unparsed_value, true);
    }
}

HSObject* hsobject_create() {
    HSObject* obj = g_new(HSObject, 1);
    hsobject_init(obj);
    return obj;
}

void hsobject_destroy(HSObject* obj) {
    if (!obj) return;
    hsobject_free(obj);
    g_free(obj);
}

HSObject* hsobject_create_and_link(HSObject* parent, const char* name) {
    HSObject* obj = hsobject_create();
    hsobject_link(parent, obj, name);
    return obj;
}

void hsobject_unlink_and_destroy(HSObject* parent, HSObject* child) {
    hsobject_unlink(parent, child);
    hsobject_destroy(child);
}

static HSObjectChild* hsobjectchild_create(const char* name, HSObject* obj) {
    HSObjectChild* oc = g_new(HSObjectChild, 1);
    oc->name = g_strdup(name);
    oc->child = obj;
    return oc;
}

static void hsobjectchild_destroy(HSObjectChild* oc) {
    if (!oc) return;
    g_free(oc->name);
    g_free(oc);
}

struct HSObjectComplChild {
    char*    needle;
    char*    prefix;
    GString* curname;
    GString* output;
};

static void completion_helper(HSObjectChild* child, struct HSObjectComplChild* data) {
    g_string_assign(data->curname, child->name);
    g_string_append_c(data->curname, OBJECT_PATH_SEPARATOR);
    try_complete_prefix_partial(data->needle, data->curname->str, data->prefix, data->output);
}

void hsobject_complete_children(HSObject* obj, char* needle, char* prefix, GString* output) {
    struct HSObjectComplChild data = {
        needle,
        prefix,
        g_string_new(""),
        output
    };
    g_list_foreach(obj->children, (GFunc) completion_helper, &data);
    g_string_free(data.curname, true);
}

void hsobject_complete_attributes(HSObject* obj, bool user_only, char* needle,
                                  char* prefix, GString* output) {
    for (int i = 0; i < obj->attribute_count; i++) {
        HSAttribute* attr = obj->attributes + i;
        if (user_only && !attr->user_attribute) {
            // do not complete default attributes if user_only is set
            continue;
        }
        try_complete_prefix(needle, attr->name, prefix, output);
    }
}

static int child_check_name(HSObjectChild* child, char* name) {
    return strcmp(child->name, name);
}

void hsobject_link(HSObject* parent, HSObject* child, const char* name) {
    GList* elem = g_list_find_custom(parent->children, name,
                                     (GCompareFunc)child_check_name);
    if (!elem) {
        // create a new child node
        HSObjectChild* oc = hsobjectchild_create(name, child);
        parent->children = g_list_append(parent->children, oc);
    } else {
        // replace it
        HSObjectChild* oc = (HSObjectChild*) elem->data;
        oc->child = child;
    }
}

static int child_check_object(HSObjectChild* child, HSObject* obj) {
    // return 0 if they are identical
    return (child->child == obj) ? 0 : 1;
}

static void hsobject_unlink_helper(HSObject* parent, GCompareFunc f, void* data) {
    GList* elem = parent->children;
    while (elem) {
        elem = g_list_find_custom(elem, data, f);
        if (elem) {
            GList* next = elem->next;
            hsobjectchild_destroy((HSObjectChild*)elem->data);
            parent->children = g_list_delete_link(parent->children, elem);
            elem = next;
        }
    }
}

void hsobject_unlink(HSObject* parent, HSObject* child) {
    hsobject_unlink_helper(parent,
                           (GCompareFunc)child_check_object,
                           child);
}

void hsobject_unlink_by_name(HSObject* parent, char* name) {
    hsobject_unlink_helper(parent,
                           (GCompareFunc)child_check_name,
                           name);
}

void hsobject_link_rename(HSObject* parent, char* oldname, char* newname) {
    if (!strcmp(oldname, newname)) {
        return;
    }
    // remove object with target name
    hsobject_unlink_by_name(parent, newname);
    GList* elem = g_list_find_custom(parent->children,
                                     oldname,
                                     (GCompareFunc)child_check_name);
    HSObjectChild* child = (HSObjectChild*)elem->data;
    g_free(child->name);
    child->name = g_strdup(newname);
}

void hsobject_link_rename_object(HSObject* parent, HSObject* child, char* newname) {
    // remove occurrences of that object
    hsobject_unlink(parent, child);
    // link it again (replacing any object with newname)
    hsobject_link(parent, child, newname);
}

HSObject* hsobject_find_child(HSObject* obj, const char* name) {
    GList* elem = g_list_find_custom(obj->children, name,
                                     (GCompareFunc)child_check_name);
    if (elem) {
        return ((HSObjectChild*)(elem->data))->child;
    } else {
        return NULL;
    }
}

HSAttribute* hsobject_find_attribute(HSObject* obj, const char* name) {
    for (int i = 0; i < obj->attribute_count; i++) {
        if (!strcmp(name, obj->attributes[i].name)) {
            return obj->attributes + i;
        }
    }
    return NULL;
}

void hsobject_set_attributes_always_callback(HSObject* obj) {
    for (int i = 0; i < obj->attribute_count; i++) {
        obj->attributes[i].always_callback = true;
    }
}

static void print_child_name(HSObjectChild* child, GString* output) {
    g_string_append_printf(output, "  %s%c\n", child->name, OBJECT_PATH_SEPARATOR);
}

void hsattribute_append_to_string(HSAttribute* attribute, GString* output) {
    switch (attribute->type) {
        case HSATTR_TYPE_BOOL:
            if (*(attribute->value.b)) {
                g_string_append_printf(output, "true");
            } else {
                g_string_append_printf(output, "false");
            }
            break;
        case HSATTR_TYPE_INT:
            g_string_append_printf(output, "%d", *attribute->value.i);
            break;
        case HSATTR_TYPE_UINT:
            g_string_append_printf(output, "%u", *attribute->value.u);
            break;
        case HSATTR_TYPE_STRING:
            g_string_append_printf(output, "%s", (*attribute->value.str)->str);
            break;
        case HSATTR_TYPE_COLOR:
            g_string_append_printf(output, "%s", attribute->unparsed_value->str);
            break;
        case HSATTR_TYPE_CUSTOM:
            attribute->value.custom(attribute->data ? attribute->data
                                                    : attribute->object->data, output);
            break;
        case HSATTR_TYPE_CUSTOM_INT:
            g_string_append_printf(output, "%d",
                attribute->value.custom_int(attribute->data ? attribute->data
                                                            : attribute->object->data));
            break;
    }
}

GString* hsattribute_to_string(HSAttribute* attribute) {
    GString* str = g_string_new("");
    hsattribute_append_to_string(attribute, str);
    return str;
}

int attr_command(int argc, char* argv[], GString* output) {
    char* path = (argc < 2) ? "" : argv[1];
    char* unparsable;
    GString* errormsg = g_string_new("");
    HSObject* obj = hsobject_parse_path_verbose(path, &unparsable, errormsg);
    HSAttribute* attribute = NULL;
    if (strcmp("", unparsable)) {
        // if object could not be parsed, try attribute
        attribute = hsattribute_parse_path_verbose(path, errormsg);
        obj = NULL;
    }
    if (!obj && !attribute) {
        // if nothing was found
        g_string_append(output, errormsg->str);
        g_string_free(errormsg, true);
        return HERBST_INVALID_ARGUMENT;
    } else {
        g_string_free(errormsg, true);
    }
    char* new_value = (argc >= 3) ? argv[2] : NULL;
    if (obj && new_value) {
        g_string_append_printf(output,
            "%s: Can not assign value \"%s\" to object \"%s\",",
            argv[0], new_value, path);
    } else if (obj && !new_value) {
        // list children
        int childcount = g_list_length(obj->children);
        g_string_append_printf(output, "%d children%c\n", childcount,
                               childcount ? ':' : '.');
        g_list_foreach(obj->children, (GFunc) print_child_name, output);
        if (childcount > 0) {
            g_string_append_printf(output, "\n");
        }
        // list attributes
        g_string_append_printf(output, "%zu attributes", obj->attribute_count);
        if (obj->attribute_count > 0) {
            g_string_append_printf(output, ":\n");
            g_string_append_printf(output, " .---- type\n");
            g_string_append_printf(output, " | .-- writeable\n");
            g_string_append_printf(output, " V V\n");
        } else {
            g_string_append_printf(output, ".\n");
        }
        for (int i = 0; i < obj->attribute_count; i++) {
            HSAttribute* a = obj->attributes + i;
            char write = hsattribute_is_read_only(a) ? '-' : 'w';
            char t = hsattribute_type_indicator(a->type);
            g_string_append_printf(output, " %c %c %-20s = ", t, write, a->name);
            if (a->type == HSATTR_TYPE_STRING) {
                g_string_append_c(output, '\"');
            }
            hsattribute_append_to_string(a, output);
            if (a->type == HSATTR_TYPE_STRING) {
                g_string_append_c(output, '\"');
            }
            g_string_append_c(output, '\n');
        }
    } else if (new_value) { // && (attribute)
        return hsattribute_assign(attribute, new_value, output);
    } else { // !new_value && (attribute)
        hsattribute_append_to_string(attribute, output);
    }
    return 0;
}

static void object_append_caption(HSTree tree, GString* output) {
    HSObjectChild* oc = (HSObjectChild*) tree;
    g_string_append(output, oc->name);
}

static size_t object_child_count(HSTree tree) {
    HSObjectChild* oc = (HSObjectChild*) tree;
    return g_list_length(oc->child->children);
}

static HSTreeInterface object_nth_child(HSTree tree, size_t idx) {
    HSObjectChild* oc = (HSObjectChild*) tree;
    assert(oc->child);
    HSTreeInterface intf = {
        .nth_child  = object_nth_child,
        .data       = (HSTree) g_list_nth_data(oc->child->children, idx),
        .destructor = NULL,
        .child_count    = object_child_count,
        .append_caption = object_append_caption,
    };
    return intf;
}

HSObject* hsobject_by_path(char* path) {
    HSObject* obj = hsobject_parse_path(path, &path);
    if (!strcmp("", path)) {
        return obj;
    } else {
        // an invalid path was given if it was not parsed entirely
        return NULL;
    }
}

HSObject* hsobject_parse_path_verbose(char* path, char** unparsable,
                                      GString* output) {
    char* origpath = path;
    char* pathdup = strdup(path);
    char* curname = pathdup;
    char* lastname = "root";
    char seps[] = { OBJECT_PATH_SEPARATOR, '\0' };
    // replace all separators by null bytes
    g_strdelimit(curname, seps, '\0');
    HSObject* obj = hsobject_root();
    HSObject* child;
    // skip separator characters
    while (*path == OBJECT_PATH_SEPARATOR) {
        path++;
        curname++;
    }
    while (strcmp("", path)) {
        child = hsobject_find_child(obj, curname);
        if (!child) {
            if (output) {
                g_string_append_printf(output, "Invalid path \"%s\": ", origpath);
                g_string_append_printf(output, "No child \"%s\" in object %s\n",
                                       curname, lastname);
            }
            break;
        }
        lastname = curname;
        obj = child;
        // skip the name
        path    += strlen(curname);
        curname += strlen(curname);
        // skip separator characters
        while (*path == OBJECT_PATH_SEPARATOR) {
            path++;
            curname++;
        }
    }
    *unparsable = path;
    free(pathdup);
    return obj;
}

HSObject* hsobject_parse_path(char* path, char** unparsable) {
    return hsobject_parse_path_verbose(path, unparsable, NULL);
}

HSAttribute* hsattribute_parse_path_verbose(char* path, GString* output) {
    GString* object_error = g_string_new("");
    HSAttribute* attr;
    char* unparsable;
    HSObject* obj = hsobject_parse_path_verbose(path, &unparsable, object_error);
    if (obj == NULL || strchr(unparsable, OBJECT_PATH_SEPARATOR) != NULL) {
        // if there is still another path separator
        // then unparsable is more than just the attribute name.
        g_string_append(output, object_error->str);
        attr = NULL;
    } else {
        // if there is no path remaining separator, then unparsable contains
        // the attribute name
        attr = hsobject_find_attribute(obj, unparsable);
        if (!attr) {
            GString* obj_path = g_string_new(path);
            g_string_truncate(obj_path, unparsable - path);
            g_string_append_printf(output,
                "Unknown attribute \"%s\" in object \"%s\".\n",
                unparsable, obj_path->str);
            g_string_free(obj_path, true);
        }
    }
    g_string_free(object_error, true);
    return attr;
}

HSAttribute* hsattribute_parse_path(char* path) {
    GString* out = g_string_new("");
    HSAttribute* attr = hsattribute_parse_path_verbose(path, out);
    if (!attr) {
        HSError("Cannot parse %s: %s", path, out->str);
    }
    g_string_free(out, true);
    return attr;
}

int print_object_tree_command(int argc, char* argv[], GString* output) {
    char* unparsable;
    char* path = (argc < 2) ? "" : argv[1];
    HSObjectChild oc = {
        .name = path,
        .child = hsobject_parse_path_verbose(path, &unparsable, output),
    };
    if (strcmp("", unparsable)) {
        return HERBST_INVALID_ARGUMENT;
    }
    HSTreeInterface intf = {
        .nth_child  = object_nth_child,
        .data       = &oc,
        .destructor = NULL,
        .child_count    = object_child_count,
        .append_caption = object_append_caption,
    };
    tree_print_to(&intf, output);
    return 0;
}

void hsobject_set_attributes(HSObject* obj, HSAttribute* attributes) {
    // calculate new size
    size_t count;
    for (count = 0; attributes[count].name != NULL; count++)
        ;
    obj->attributes = g_renew(HSAttribute, obj->attributes, count);
    obj->attribute_count = count;
    memcpy(obj->attributes, attributes, count * sizeof(HSAttribute));
    for (int i = 0; i < count; i++) {
        obj->attributes[i].object = obj;
    }
}

int hsattribute_get_command(int argc, char* argv[], GString* output) {
    if (argc < 2) {
        return HERBST_NEED_MORE_ARGS;
    }
    HSAttribute* attr = hsattribute_parse_path_verbose(argv[1], output);
    if (!attr) {
        return HERBST_INVALID_ARGUMENT;
    }
    hsattribute_append_to_string(attr, output);
    return 0;
}

int hsattribute_set_command(int argc, char* argv[], GString* output) {
    if (argc < 3) {
        return HERBST_NEED_MORE_ARGS;
    }
    HSAttribute* attr = hsattribute_parse_path_verbose(argv[1], output);
    if (!attr) {
        return HERBST_INVALID_ARGUMENT;
    }
    return hsattribute_assign(attr, argv[2], output);
}

bool hsattribute_is_read_only(HSAttribute* attr) {
    bool custom = attr->type == HSATTR_TYPE_CUSTOM
                || attr->type == HSATTR_TYPE_CUSTOM_INT;
    assert(!(custom && attr->on_change));
    if (custom) return attr->change_custom == NULL;
    else return attr->on_change == NULL;
}

int hsattribute_assign(HSAttribute* attr, const char* new_value_str, GString* output) {
    if (hsattribute_is_read_only(attr)) {
        g_string_append_printf(output,
            "Can not write read-only attribute \"%s\"\n",
            attr->name);
        return HERBST_FORBIDDEN;
    }

    bool error = false;
    union {
        bool        b;
        int         i;
        unsigned int u;
        GString*    str;
        HSColor     color;
    } new_value, old_value;
    bool nothing_to_do = false;

#define ATTR_DO_ASSIGN_COMPARE(NAME,MEM) \
        do { \
            if (error) { \
                g_string_append_printf(output, \
                                       "Can not parse "NAME" from \"%s\"", \
                                       new_value_str); \
            } else { \
                old_value.MEM = *attr->value.MEM; \
                if (old_value.MEM == new_value.MEM) { \
                    nothing_to_do = true; \
                } else { \
                    *attr->value.MEM = new_value.MEM; \
                } \
            } \
        } while (0);
    // change the value and backup the old value
    switch (attr->type) {
        case HSATTR_TYPE_BOOL:
            new_value.b = string_to_bool_error(new_value_str,
                                             *attr->value.b,
                                             &error);
            ATTR_DO_ASSIGN_COMPARE("boolean", b);
            break;

        case HSATTR_TYPE_INT:
            error = (1 != sscanf(new_value_str, "%d", &new_value.i));
            ATTR_DO_ASSIGN_COMPARE("integer", i);
            break;

        case HSATTR_TYPE_UINT:
            error = (1 != sscanf(new_value_str, "%u", &new_value.u));
            ATTR_DO_ASSIGN_COMPARE("unsigned integer", u);
            break;

        case HSATTR_TYPE_STRING:
            if (!strcmp(new_value_str, (*attr->value.str)->str)) {
                nothing_to_do = true;
            } else {
                old_value.str = g_string_new((*attr->value.str)->str);
                g_string_assign(*attr->value.str, new_value_str);
            }
            break;

        case HSATTR_TYPE_COLOR:
            error = !getcolor_error(new_value_str, &new_value.color);
            if (error) {
                g_string_append_printf(output,
                    "\"%s\" is not a valid color.", new_value_str);
                break;
            }
            if (!strcmp(new_value_str, (attr->unparsed_value)->str)) {
                nothing_to_do = true;
            } else {
                old_value.color = *attr->value.color;
                *attr->value.color = new_value.color;
            }
            break;

        case HSATTR_TYPE_CUSTOM: break;
        case HSATTR_TYPE_CUSTOM_INT: break;
    }
    if (error) {
        return HERBST_INVALID_ARGUMENT;
    }
    if (attr->always_callback) {
        nothing_to_do = false; // pretend that there was a change
    }
    if (nothing_to_do) {
        return 0;
    }

    GString* old_unparsed_value = attr->unparsed_value;
    if (attr->unparsed_value) attr->unparsed_value = g_string_new(new_value_str);

    // ask the attribute about the change
    GString* errormsg = NULL;
    if (attr->on_change) errormsg = attr->on_change(attr);
    else errormsg = attr->change_custom(attr, new_value_str);
    int exit_status = 0;
    if (errormsg && errormsg->len > 0) {
        exit_status = HERBST_INVALID_ARGUMENT;
        // print the message
        if (errormsg->str[errormsg->len - 1] == '\n') {
            g_string_truncate(errormsg, errormsg->len - 1);
        }
        g_string_append_printf(output,
            "Can not write attribute \"%s\": %s\n",
            attr->name,
            errormsg->str);
        g_string_free(errormsg, true);
        // restore old value
        if (old_unparsed_value) {
            g_string_free(attr->unparsed_value, true);
            attr->unparsed_value = old_unparsed_value;
        }
        switch (attr->type) {
            case HSATTR_TYPE_BOOL: *attr->value.b = old_value.b; break;
            case HSATTR_TYPE_INT:  *attr->value.i = old_value.i; break;
            case HSATTR_TYPE_UINT: *attr->value.u = old_value.u; break;
            case HSATTR_TYPE_STRING:
                g_string_assign(*attr->value.str, old_value.str->str);
                break;
            case HSATTR_TYPE_COLOR:
                *attr->value.color = old_value.color; break;
                break;
            case HSATTR_TYPE_CUSTOM: break;
            case HSATTR_TYPE_CUSTOM_INT: break;
        }
    }
    // free old_value
    switch (attr->type) {
        case HSATTR_TYPE_BOOL: break;
        case HSATTR_TYPE_INT:  break;
        case HSATTR_TYPE_UINT:  break;
        case HSATTR_TYPE_STRING:
            g_string_free(old_value.str, true);
            break;
        case HSATTR_TYPE_COLOR:
            g_string_assign(attr->unparsed_value, new_value_str);
            break;
        case HSATTR_TYPE_CUSTOM: break;
        case HSATTR_TYPE_CUSTOM_INT: break;
    }
    if (old_unparsed_value) {
        g_string_free(old_unparsed_value, true);
    }
    return exit_status;
}


int substitute_command(int argc, char* argv[], GString* output) {
    // usage: substitute identifier attribute command [args ...]
    //            0         1           2       3
    if (argc < 4) {
        return HERBST_NEED_MORE_ARGS;
    }
    char* identifier = argv[1];
    HSAttribute* attribute = hsattribute_parse_path_verbose(argv[2], output);
    if (!attribute) {
        return HERBST_INVALID_ARGUMENT;
    }
    GString* attribute_string = hsattribute_to_string(attribute);
    char* repl = attribute_string->str;

    (void) SHIFT(argc, argv); // remove command name
    (void) SHIFT(argc, argv); // remove identifier
    (void) SHIFT(argc, argv); // remove attribute

    int status = call_command_substitute(identifier, repl, argc, argv, output);
    g_string_free(attribute_string, true);
    return status;
}

GString* ATTR_ACCEPT_ALL(HSAttribute* attr) {
    (void) attr;
    return NULL;
}

int compare_command(int argc, char* argv[], GString* output) {
    // usage: compare attribute operator constant
    if (argc < 4) {
        return HERBST_NEED_MORE_ARGS;
    }
    HSAttribute* attr = hsattribute_parse_path_verbose(argv[1], output);
    if (!attr) {
        return HERBST_INVALID_ARGUMENT;
    }
    char* op = argv[2];
    char* rvalue = argv[3];
    if (attr->type == HSATTR_TYPE_INT
        || attr->type == HSATTR_TYPE_UINT
        || attr->type == HSATTR_TYPE_CUSTOM_INT)
    {
        long long l;
        long long r;
        if (1 != sscanf(rvalue, "%lld", &r)) {
            g_string_append_printf(output,
                                   "Can not parse integer from \"%s\"\n",
                                   rvalue);
            return HERBST_INVALID_ARGUMENT;
        }
        switch (attr->type) {
            case HSATTR_TYPE_INT:  l = *attr->value.i; break;
            case HSATTR_TYPE_UINT: l = *attr->value.u; break;
            case HSATTR_TYPE_CUSTOM_INT:
                l = attr->value.custom_int(attr->data ? attr->data : attr->object->data);
                break;
            default: return HERBST_UNKNOWN_ERROR; break;
        }
        struct {
            char* name;
            bool  result;
        } eval[] = {
            { "=",  l == r  },
            { "!=", l != r  },
            { "le", l <= r  },
            { "lt", l <  r  },
            { "ge", l >= r  },
            { "gt", l >  r  },
        };
        int result = -1;
        for (int i = 0; i < LENGTH(eval); i++) {
            if (!strcmp(eval[i].name, op)) {
                result = !eval[i].result; // make false -> 1, true -> 0
            }
        }
        if (result == -1) {
            g_string_append_printf(output, "Invalid operator \"%s\"", op);
            result = HERBST_INVALID_ARGUMENT;
        }
        return result;
    } else if (attr->type == HSATTR_TYPE_BOOL) {
        bool l = *attr->value.b;
        bool error = false;
        bool r = string_to_bool_error(rvalue, l, &error);
        if (error) {
            g_string_append_printf(output, "Can not parse boolean from \"%s\"\n", rvalue);
            return HERBST_INVALID_ARGUMENT;
        }
        if (!strcmp("=", op)) return !(l == r);
        if (!strcmp("!=", op)) return !(l != r);
        g_string_append_printf(output, "Invalid boolean operator \"%s\"", op);
        return HERBST_INVALID_ARGUMENT;
    } else if (attr->type == HSATTR_TYPE_COLOR) {
        HSColor l = *attr->value.color;
        HSColor r = getcolor(rvalue);
        if (!strcmp("=", op)) return !(l == r);
        if (!strcmp("!=", op)) return !(l != r);
        g_string_append_printf(output, "Invalid color operator \"%s\"", op);
        return HERBST_INVALID_ARGUMENT;
    } else { // STRING or CUSTOM
        GString* l;
        bool free_l = false;
        if (attr->type == HSATTR_TYPE_STRING) {
            l = *attr->value.str;
        } else { // TYPE == CUSTOM
            l = g_string_new("");
            attr->value.custom(attr->data ? attr->data : attr->object->data, l);
            free_l = true;
        }
        bool equals = !strcmp(l->str, rvalue);
        int status;
        if (!strcmp("=", op)) status = !equals;
        else if (!strcmp("!=", op)) status = equals;
        else status = -1;
        if (free_l) {
            g_string_free(l, true);
        }
        if (status == -1) {
            g_string_append_printf(output, "Invalid string operator \"%s\"", op);
            return HERBST_INVALID_ARGUMENT;
        }
        return status;
    }
}

char hsattribute_type_indicator(int type) {
    switch (type) {
        case HSATTR_TYPE_BOOL:      return 'b';
        case HSATTR_TYPE_UINT:      return 'u';
        case HSATTR_TYPE_INT:       return 'i';
        case HSATTR_TYPE_STRING:    return 's';
        case HSATTR_TYPE_CUSTOM:    return 's';
        case HSATTR_TYPE_CUSTOM_INT:return 'i';
        case HSATTR_TYPE_COLOR:     return 'c';
    }
    return '?';
}

int userattribute_command(int argc, char* argv[], GString* output) {
    if (argc < 3) {
        return HERBST_NEED_MORE_ARGS;
    }
    char* type_str = argv[1];
    char* path = argv[2];
    char* unparsable;
    GString* errormsg = g_string_new("");
    HSObject* obj = hsobject_parse_path_verbose(path, &unparsable, errormsg);
    if (obj == NULL || strchr(unparsable, OBJECT_PATH_SEPARATOR) != NULL) {
        g_string_append(output, errormsg->str);
        g_string_free(errormsg, true);
        return HERBST_INVALID_ARGUMENT;
    } else {
        g_string_free(errormsg, true);
    }
    // check for an already existing attribute
    if (hsobject_find_attribute(obj, unparsable)) {
        g_string_append_printf(output, "Error: an attribute called \"%s\" already exists\n",
                               unparsable);
        return HERBST_FORBIDDEN;
    }
    // do not check for children with that name, because they must not start
    // with the USER_ATTRIBUTE_PREFIX.
    // now create a new attribute named unparsable at obj
    const char* prefix = USER_ATTRIBUTE_PREFIX;
    if (strncmp(unparsable, prefix, strlen(prefix))) {
        g_string_append(output, "Error: the name of user attributes has to ");
        g_string_append_printf(output, "start with \"%s\" but yours is \"%s\"\n",
                                       prefix, unparsable);
        return HERBST_INVALID_ARGUMENT;
    }
    HSAttribute* attr = hsattribute_create(obj, unparsable, type_str, output);
    if (!attr) {
        return HERBST_INVALID_ARGUMENT;
    }
    attr->user_attribute = true;
    return 0;
}

HSAttribute* hsattribute_create(HSObject* obj, char* name, char* type_str,
                                GString* output)
{
    struct {
        char* name;
        int   type;
    } types[] = {
        { "bool",   HSATTR_TYPE_BOOL    },
        { "uint",   HSATTR_TYPE_UINT    },
        { "int",    HSATTR_TYPE_INT     },
        { "string", HSATTR_TYPE_STRING  },
        { "color",  HSATTR_TYPE_COLOR   },
    };
    int type = -1;
    for (int i = 0; i < LENGTH(types); i++) {
        if (!strcmp(type_str, types[i].name)) {
            type = types[i].type;
            break;
        }
    }
    if (type < 0) {
        g_string_append_printf(output, "Unknown attribute type \"%s\"\n",
                               type_str);
        return NULL;
    }
    size_t count = obj->attribute_count + 1;
    obj->attributes = g_renew(HSAttribute, obj->attributes, count);
    obj->attribute_count = count;
    // initialize object
    HSAttribute* attr = obj->attributes + count - 1;
    memset(attr, 0, sizeof(*attr));
    attr->object = obj;
    attr->type = type;
    attr->name = g_strdup(name);
    attr->on_change = ATTR_ACCEPT_ALL;
    attr->user_attribute = false;
    attr->user_data = g_new(HSAttributeValue, 1);
    switch (type) {
        case HSATTR_TYPE_BOOL:
            attr->user_data->b = false;
            attr->value.b = &attr->user_data->b;
            break;
        case HSATTR_TYPE_INT:
            attr->user_data->i = 0;
            attr->value.i = &attr->user_data->i;
            break;
        case HSATTR_TYPE_UINT:
            attr->user_data->u = 0;
            attr->value.u = &attr->user_data->u;
            break;
        case HSATTR_TYPE_STRING:
            attr->user_data->str = g_string_new("");
            attr->value.str = &attr->user_data->str;
            break;
        case HSATTR_TYPE_COLOR:
            attr->user_data->color = getcolor("#000000");
            attr->unparsed_value = g_string_new("#000000");
            attr->value.color = &attr->user_data->color;
        default:
            break;
    }
    return attr;
}

int userattribute_remove_command(int argc, char* argv[], GString* output) {
    if (argc < 2) {
        return HERBST_NEED_MORE_ARGS;
    }
    char* path = argv[1];
    HSAttribute* attr = hsattribute_parse_path_verbose(path, output);
    if (!attr) {
        return HERBST_INVALID_ARGUMENT;
    }
    if (!attr->user_attribute) {
        g_string_append_printf(output, "Can only user-defined attributes, "
                                       "but \"%s\" is not user-defined.\n",
                               path);
        return HERBST_FORBIDDEN;
    }
    return userattribute_remove(attr) ? 0 : HERBST_UNKNOWN_ERROR;
}

bool userattribute_remove(HSAttribute* attr) {
    HSObject* obj = attr->object;
    int idx = attr - obj->attributes;
    if (idx < 0 || idx >= obj->attribute_count) {
        fprintf(stderr, "Assertion 0 <= idx < count failed.\n");
        return false;
    }
    hsattribute_free(attr);
    // remove it from buf
    size_t count = obj->attribute_count - 1;
    size_t bytes = (count - idx) * sizeof(HSAttribute);
    memmove(obj->attributes + idx, obj->attributes + idx + 1, bytes);
    obj->attributes = g_renew(HSAttribute, obj->attributes, count);
    obj->attribute_count = count;
    return 0;
}

#define FORMAT_CHAR '%'

int sprintf_command(int argc, char* argv[], GString* output) {
    // usage: sprintf IDENTIFIER FORMAT [Params...] COMMAND [ARGS ...]
    if (argc < 4) {
        return HERBST_NEED_MORE_ARGS;
    }
    char* identifier = argv[1];
    char* format = argv[2];
    (void) SHIFT(argc, argv);
    (void) SHIFT(argc, argv);
    (void) SHIFT(argc, argv);
    GString* repl = g_string_new("");
    int nextarg = 0; // next argument to consider
    for (int i = 0; format[i] != '\0'; i++) {
        if (format[i] == FORMAT_CHAR) {
            // FORMAT_CHAR is our format character
            // '%' is the printf format character
            switch (format[i+1]) {
                case FORMAT_CHAR:
                    g_string_append(repl, "%%");
                    break;

                case 's':
                    if (nextarg >= (argc - 1)) {
                        g_string_append_printf(output,
                            "Error: Too few parameters. A %dth parameter missing. "
                            "(treating \"%s\" as the command to execute)\n",
                            nextarg, argv[argc - 1]);
                        g_string_free(repl, true);
                        return HERBST_INVALID_ARGUMENT;
                    }
                    HSAttribute* attr;
                    attr = hsattribute_parse_path_verbose(argv[nextarg], output);
                    if (!attr) {
                        g_string_free(repl, true);
                        return HERBST_INVALID_ARGUMENT;
                    }
                    GString* gs = hsattribute_to_string(attr);
                    g_string_append(repl, gs->str);
                    g_string_free(gs, true);
                    nextarg++;
                    break;

                default:
                    g_string_append_printf(output,
                        "Error: unknown format specifier \'%c\' in format "
                        "\"%s\" at position %d\n",
                        format[i+1] ? format[i+1] : '?', format, i);
                    g_string_free(repl, true);
                    return HERBST_INVALID_ARGUMENT;
                    break;
            }
            i++;
        } else {
            g_string_append_c(repl, format[i]);
        }
    }
    int cmdc = argc - nextarg;
    char** cmdv = argv + nextarg;
    int status;
    status = call_command_substitute(identifier, repl->str, cmdc, cmdv, output);
    g_string_free(repl, true);
    return status;
}

int tmpattribute_command(int argc, char* argv[], GString* output) {
    // usage: tmp type IDENTIFIER COMMAND [ARGS...]
    if (argc < 4) {
        return HERBST_NEED_MORE_ARGS;
    }
    static int tmpcount = 0;
    tmpcount++;
    char* name = g_strdup_printf("%stmp%d", USER_ATTRIBUTE_PREFIX, tmpcount);
    // attr may change, so only remember the object
    HSAttribute* attr = hsattribute_create(g_tmp_object, name, argv[1], output);
    if (!attr) {
        tmpcount--;
        g_free(name);
        return HERBST_INVALID_ARGUMENT;
    }
    HSObject* obj = attr->object;
    char* path = g_strdup_printf("%s%c%s", TMP_OBJECT_PATH,
                                 OBJECT_PATH_SEPARATOR, name);
    int status = call_command_substitute(argv[2], path, argc - 3, argv + 3, output);
    userattribute_remove(hsobject_find_attribute(obj, name));
    g_free(name);
    g_free(path);
    tmpcount--;
    return status;
}

