/*
 * Copyright (c) 2016 Cesanta Software Limited
 * All rights reserved
 */

#include "mjs_object.h"
#include "mjs_core.h"
#include "mjs_internal.h"
#include "mjs_primitive.h"
#include "mjs_string.h"
#include "mjs_util.h"
#include "furi.h"

#include "common/mg_str.h"

MJS_PRIVATE mjs_val_t mjs_object_to_value(struct mjs_object* o) {
    if(o == NULL) {
        return MJS_NULL;
    } else {
        return mjs_legit_pointer_to_value(o) | MJS_TAG_OBJECT;
    }
}

MJS_PRIVATE void mjs_obj_destructor(struct mjs* mjs, void* cell) {
    struct mjs_object* obj = cell;
    mjs_val_t obj_val = mjs_object_to_value(obj);

    struct mjs_property* destructor = mjs_get_own_property(
        mjs, obj_val, MJS_DESTRUCTOR_PROP_NAME, strlen(MJS_DESTRUCTOR_PROP_NAME));
    if(!destructor) return;
    if(!mjs_is_foreign(destructor->value)) return;

    mjs_custom_obj_destructor_t destructor_fn = mjs_get_ptr(mjs, destructor->value);
    if(destructor_fn) destructor_fn(mjs, obj_val);
}

MJS_PRIVATE struct mjs_object* get_object_struct(mjs_val_t v) {
    struct mjs_object* ret = NULL;
    if(mjs_is_null(v)) {
        ret = NULL;
    } else {
        assert(mjs_is_object_based(v));
        ret = (struct mjs_object*)get_ptr(v);
    }
    return ret;
}

mjs_val_t mjs_mk_object(struct mjs* mjs) {
    struct mjs_object* o = new_object(mjs);
    if(o == NULL) {
        return MJS_NULL;
    }
    (void)mjs;
    o->properties = NULL;
    return mjs_object_to_value(o);
}

int mjs_is_object(mjs_val_t v) {
    return (v & MJS_TAG_MASK) == MJS_TAG_OBJECT || (v & MJS_TAG_MASK) == MJS_TAG_ARRAY;
}

int mjs_is_object_based(mjs_val_t v) {
    return ((v & MJS_TAG_MASK) == MJS_TAG_OBJECT) || ((v & MJS_TAG_MASK) == MJS_TAG_ARRAY) ||
           ((v & MJS_TAG_MASK) == MJS_TAG_ARRAY_BUF_VIEW);
}

MJS_PRIVATE struct mjs_property*
    mjs_get_own_property(struct mjs* mjs, mjs_val_t obj, const char* name, size_t len) {
    struct mjs_property* p;
    struct mjs_object* o;

    if(!mjs_is_object_based(obj)) {
        return NULL;
    }

    o = get_object_struct(obj);

    if(len <= 5) {
        mjs_val_t ss = mjs_mk_string(mjs, name, len, 1);
        for(p = o->properties; p != NULL; p = p->next) {
            if(p->name == ss) return p;
        }
    } else {
        for(p = o->properties; p != NULL; p = p->next) {
            if(mjs_strcmp(mjs, &p->name, name, len) == 0) return p;
        }
        return p;
    }

    return NULL;
}

MJS_PRIVATE struct mjs_property*
    mjs_get_own_property_v(struct mjs* mjs, mjs_val_t obj, mjs_val_t key) {
    size_t n;
    char* s = NULL;
    int need_free = 0;
    struct mjs_property* p = NULL;
    mjs_err_t err = mjs_to_string(mjs, &key, &s, &n, &need_free);
    if(err == MJS_OK) {
        p = mjs_get_own_property(mjs, obj, s, n);
    }
    if(need_free) free(s);
    return p;
}

MJS_PRIVATE struct mjs_property*
    mjs_mk_property(struct mjs* mjs, mjs_val_t name, mjs_val_t value) {
    struct mjs_property* p = new_property(mjs);
    p->next = NULL;
    p->name = name;
    p->value = value;
    return p;
}

mjs_val_t mjs_get(struct mjs* mjs, mjs_val_t obj, const char* name, size_t name_len) {
    struct mjs_property* p;

    if(name_len == (size_t)~0) {
        name_len = strlen(name);
    }

    p = mjs_get_own_property(mjs, obj, name, name_len);
    if(p == NULL) {
        return MJS_UNDEFINED;
    } else {
        return p->value;
    }
}

mjs_val_t mjs_get_v(struct mjs* mjs, mjs_val_t obj, mjs_val_t name) {
    size_t n;
    char* s = NULL;
    int need_free = 0;
    mjs_val_t ret = MJS_UNDEFINED;

    mjs_err_t err = mjs_to_string(mjs, &name, &s, &n, &need_free);

    if(err == MJS_OK) {
        /* Successfully converted name value to string: get the property */
        ret = mjs_get(mjs, obj, s, n);
    }

    if(need_free) {
        free(s);
        s = NULL;
    }
    return ret;
}

mjs_val_t mjs_get_v_proto(struct mjs* mjs, mjs_val_t obj, mjs_val_t key) {
    struct mjs_property* p;
    mjs_val_t pn = mjs_mk_string(mjs, MJS_PROTO_PROP_NAME, ~0, 1);
    if((p = mjs_get_own_property_v(mjs, obj, key)) != NULL) return p->value;
    if((p = mjs_get_own_property_v(mjs, obj, pn)) == NULL) return MJS_UNDEFINED;
    return mjs_get_v_proto(mjs, p->value, key);
}

mjs_err_t
    mjs_set(struct mjs* mjs, mjs_val_t obj, const char* name, size_t name_len, mjs_val_t val) {
    return mjs_set_internal(mjs, obj, MJS_UNDEFINED, (char*)name, name_len, val);
}

mjs_err_t mjs_set_v(struct mjs* mjs, mjs_val_t obj, mjs_val_t name, mjs_val_t val) {
    return mjs_set_internal(mjs, obj, name, NULL, 0, val);
}

MJS_PRIVATE mjs_err_t mjs_set_internal(
    struct mjs* mjs,
    mjs_val_t obj,
    mjs_val_t name_v,
    char* name,
    size_t name_len,
    mjs_val_t val) {
    mjs_err_t rcode = MJS_OK;

    struct mjs_property* p;

    int need_free = 0;

    if(name == NULL) {
        /* Pointer was not provided, so obtain one from the name_v. */
        rcode = mjs_to_string(mjs, &name_v, &name, &name_len, &need_free);
        if(rcode != MJS_OK) {
            goto clean;
        }
    } else {
        /*
     * Pointer was provided, so we ignore name_v. Here we set it to undefined,
     * and the actual value will be calculated later if needed.
     */
        name_v = MJS_UNDEFINED;
    }

    p = mjs_get_own_property(mjs, obj, name, name_len);

    if(p == NULL) {
        struct mjs_object* o;
        if(!mjs_is_object_based(obj)) {
            return MJS_REFERENCE_ERROR;
        }

        /*
     * name_v might be not a string here. In this case, we need to create a new
     * `name_v`, which will be a string.
     */
        if(!mjs_is_string(name_v)) {
            name_v = mjs_mk_string(mjs, name, name_len, 1);
        }

        p = mjs_mk_property(mjs, name_v, val);

        o = get_object_struct(obj);
        p->next = o->properties;
        o->properties = p;
    }

    p->value = val;

clean:
    if(need_free) {
        free(name);
        name = NULL;
    }
    return rcode;
}

MJS_PRIVATE void mjs_destroy_property(struct mjs_property** p) {
    *p = NULL;
}

/*
 * See comments in `object_public.h`
 */
int mjs_del(struct mjs* mjs, mjs_val_t obj, const char* name, size_t len) {
    struct mjs_property *prop, *prev;

    if(!mjs_is_object_based(obj)) {
        return -1;
    }
    if(len == (size_t)~0) {
        len = strlen(name);
    }
    for(prev = NULL, prop = get_object_struct(obj)->properties; prop != NULL;
        prev = prop, prop = prop->next) {
        size_t n;
        const char* s = mjs_get_string(mjs, &prop->name, &n);
        if(n == len && strncmp(s, name, len) == 0) {
            if(prev) {
                prev->next = prop->next;
            } else {
                get_object_struct(obj)->properties = prop->next;
            }
            mjs_destroy_property(&prop);
            return 0;
        }
    }
    return -1;
}

mjs_val_t mjs_next(struct mjs* mjs, mjs_val_t obj, mjs_val_t* iterator) {
    struct mjs_property* p = NULL;
    mjs_val_t key = MJS_UNDEFINED;

    if(*iterator == MJS_UNDEFINED) {
        struct mjs_object* o = get_object_struct(obj);
        p = o->properties;
    } else {
        p = ((struct mjs_property*)get_ptr(*iterator))->next;
    }

    if(p == NULL) {
        *iterator = MJS_UNDEFINED;
    } else {
        key = p->name;
        *iterator = mjs_mk_foreign(mjs, p);
    }

    return key;
}

MJS_PRIVATE void mjs_op_create_object(struct mjs* mjs) {
    mjs_val_t ret = MJS_UNDEFINED;
    mjs_val_t proto_v = mjs_arg(mjs, 0);

    if(!mjs_check_arg(mjs, 0, "proto", MJS_TYPE_OBJECT_GENERIC, &proto_v)) {
        goto clean;
    }

    ret = mjs_mk_object(mjs);
    mjs_set(mjs, ret, MJS_PROTO_PROP_NAME, ~0, proto_v);

clean:
    mjs_return(mjs, ret);
}

MJS_PRIVATE void mjs_op_object_define_property(struct mjs* mjs) {
    // stub, do not use
    mjs_return(mjs, MJS_UNDEFINED);
}

mjs_val_t
    mjs_struct_to_obj(struct mjs* mjs, const void* base, const struct mjs_c_struct_member* defs) {
    mjs_val_t obj;
    const struct mjs_c_struct_member* def = defs;
    if(base == NULL || def == NULL) return MJS_UNDEFINED;
    obj = mjs_mk_object(mjs);
    /* Pin the object while it is being built */
    mjs_own(mjs, &obj);
    /*
   * Because mjs inserts new properties at the head of the list,
   * start from the end so the constructed object more closely resembles
   * the definition.
   */
    while(def->name != NULL)
        def++;
    for(def--; def >= defs; def--) {
        mjs_val_t v = MJS_UNDEFINED;
        const char* ptr = (const char*)base + def->offset;
        switch(def->type) {
        case MJS_STRUCT_FIELD_TYPE_STRUCT: {
            const void* sub_base = (const void*)ptr;
            const struct mjs_c_struct_member* sub_def =
                (const struct mjs_c_struct_member*)def->arg;
            v = mjs_struct_to_obj(mjs, sub_base, sub_def);
            break;
        }
        case MJS_STRUCT_FIELD_TYPE_STRUCT_PTR: {
            const void** sub_base = (const void**)ptr;
            const struct mjs_c_struct_member* sub_def =
                (const struct mjs_c_struct_member*)def->arg;
            if(*sub_base != NULL) {
                v = mjs_struct_to_obj(mjs, *sub_base, sub_def);
            } else {
                v = MJS_NULL;
            }
            break;
        }
        case MJS_STRUCT_FIELD_TYPE_INT: {
            double value = (double)(*(int*)ptr);
            v = mjs_mk_number(mjs, value);
            break;
        }
        case MJS_STRUCT_FIELD_TYPE_BOOL: {
            v = mjs_mk_boolean(mjs, *(bool*)ptr);
            break;
        }
        case MJS_STRUCT_FIELD_TYPE_DOUBLE: {
            v = mjs_mk_number(mjs, *(double*)ptr);
            break;
        }
        case MJS_STRUCT_FIELD_TYPE_FLOAT: {
            float value = *(float*)ptr;
            v = mjs_mk_number(mjs, value);
            break;
        }
        case MJS_STRUCT_FIELD_TYPE_CHAR_PTR: {
            const char* value = *(const char**)ptr;
            v = mjs_mk_string(mjs, value, ~0, 1);
            break;
        }
        case MJS_STRUCT_FIELD_TYPE_VOID_PTR: {
            v = mjs_mk_foreign(mjs, *(void**)ptr);
            break;
        }
        case MJS_STRUCT_FIELD_TYPE_MG_STR_PTR: {
            const struct mg_str* s = *(const struct mg_str**)ptr;
            if(s != NULL) {
                v = mjs_mk_string(mjs, s->p, s->len, 1);
            } else {
                v = MJS_NULL;
            }
            break;
        }
        case MJS_STRUCT_FIELD_TYPE_MG_STR: {
            const struct mg_str* s = (const struct mg_str*)ptr;
            v = mjs_mk_string(mjs, s->p, s->len, 1);
            break;
        }
        case MJS_STRUCT_FIELD_TYPE_DATA: {
            const char* dptr = (const char*)ptr;
            const intptr_t dlen = (intptr_t)def->arg;
            v = mjs_mk_string(mjs, dptr, dlen, 1);
            break;
        }
        case MJS_STRUCT_FIELD_TYPE_INT8: {
            double value = (double)(*(int8_t*)ptr);
            v = mjs_mk_number(mjs, value);
            break;
        }
        case MJS_STRUCT_FIELD_TYPE_INT16: {
            double value = (double)(*(int16_t*)ptr);
            v = mjs_mk_number(mjs, value);
            break;
        }
        case MJS_STRUCT_FIELD_TYPE_UINT8: {
            double value = (double)(*(uint8_t*)ptr);
            v = mjs_mk_number(mjs, value);
            break;
        }
        case MJS_STRUCT_FIELD_TYPE_UINT16: {
            double value = (double)(*(uint16_t*)ptr);
            v = mjs_mk_number(mjs, value);
            break;
        }
        case MJS_STRUCT_FIELD_TYPE_CUSTOM: {
            mjs_val_t (*fptr)(struct mjs*, const void*) =
                (mjs_val_t(*)(struct mjs*, const void*))def->arg;
            v = fptr(mjs, ptr);
        }
        default: {
            break;
        }
        }
        mjs_set(mjs, obj, def->name, ~0, v);
    }
    mjs_disown(mjs, &obj);
    return obj;
}
