#include <string.h>

#include "imports/avs.h"

#include "launcher/ea3-config.h"
#include "launcher/module.h"

#include "util/defs.h"
#include "util/hex.h"
#include "util/log.h"
#include "util/str.h"

PSMAP_BEGIN(ea3_ident_psmap)
PSMAP_OPTIONAL(PSMAP_TYPE_STR, struct ea3_ident, softid, "/ea3/id/softid", "")
PSMAP_OPTIONAL(PSMAP_TYPE_STR, struct ea3_ident, hardid, "/ea3/id/hardid", "")
PSMAP_OPTIONAL(PSMAP_TYPE_STR, struct ea3_ident, pcbid, "/ea3/id/pcbid", "")
PSMAP_REQUIRED(PSMAP_TYPE_STR, struct ea3_ident, model, "/ea3/soft/model")
PSMAP_REQUIRED(PSMAP_TYPE_STR, struct ea3_ident, dest, "/ea3/soft/dest")
PSMAP_REQUIRED(PSMAP_TYPE_STR, struct ea3_ident, spec, "/ea3/soft/spec")
PSMAP_REQUIRED(PSMAP_TYPE_STR, struct ea3_ident, rev, "/ea3/soft/rev")
PSMAP_REQUIRED(PSMAP_TYPE_STR, struct ea3_ident, ext, "/ea3/soft/ext")
PSMAP_END

void ea3_ident_init(struct ea3_ident *ident)
{
    memset(ident, 0, sizeof(*ident));
}

bool ea3_ident_from_property(
    struct ea3_ident *ident, struct property *ea3_config)
{
    return property_psmap_import(ea3_config, NULL, ident, ea3_ident_psmap);
}

void ea3_ident_hardid_from_ethernet(struct ea3_ident *ident)
{
    struct avs_net_interface netif;
    int result;

    result = avs_net_ctrl(1, &netif, sizeof(netif));

    if (result < 0) {
        log_fatal(
            "avs_net_ctrl call to get MAC address returned error: %d", result);
    }

    ident->hardid[0] = '0';
    ident->hardid[1] = '1';
    ident->hardid[2] = '0';
    ident->hardid[3] = '0';

    hex_encode_uc(
        netif.mac_addr,
        sizeof(netif.mac_addr),
        ident->hardid + 4,
        sizeof(ident->hardid) - 4);
}

bool ea3_ident_invoke_module_init(
    struct ea3_ident *ident,
    const struct module_context *module,
    struct property_node *app_config)
{
    char sidcode_short[17];
    char sidcode_long[21];
    char security_code[9];
    bool ok;

    /* Set up security env vars */

    str_format(
        security_code,
        lengthof(security_code),
        "G*%s%s%s%s",
        ident->model,
        ident->dest,
        ident->spec,
        ident->rev);

    std_setenv("/env/boot/version", "0.0.0");
    std_setenv("/env/profile/security_code", security_code);
    std_setenv("/env/profile/system_id", ident->pcbid);
    std_setenv("/env/profile/account_id", ident->pcbid);
    std_setenv("/env/profile/license_id", ident->softid);
    std_setenv("/env/profile/software_id", ident->softid);
    std_setenv("/env/profile/hardware_id", ident->hardid);

    /* Set up the short sidcode string, let dll_entry_init mangle it */

    str_format(
        sidcode_short,
        lengthof(sidcode_short),
        "%s%s%s%s%s",
        ident->model,
        ident->dest,
        ident->spec,
        ident->rev,
        ident->ext);

    /* Set up long-form sidcode env var */

    str_format(
        sidcode_long,
        lengthof(sidcode_long),
        "%s:%s:%s:%s:%s",
        ident->model,
        ident->dest,
        ident->spec,
        ident->rev,
        ident->ext);

    /* Set this up beforehand, as certain games require it in dll_entry_init */

    std_setenv("/env/profile/soft_id_code", sidcode_long);

    ok = module_context_invoke_init(module, sidcode_short, app_config);

    if (!ok) {
        return false;
    }

    /* Back-propagate sidcode, as some games modify it during init */

    memcpy(ident->model, sidcode_short + 0, sizeof(ident->model) - 1);
    ident->dest[0] = sidcode_short[3];
    ident->spec[0] = sidcode_short[4];
    ident->rev[0] = sidcode_short[5];
    memcpy(ident->ext, sidcode_short + 6, sizeof(ident->ext));

    /* Set up long-form sidcode env var again */

    str_format(
        sidcode_long,
        lengthof(sidcode_long),
        "%s:%s:%s:%s:%s",
        ident->model,
        ident->dest,
        ident->spec,
        ident->rev,
        ident->ext);

    std_setenv("/env/profile/soft_id_code", sidcode_long);

    return true;
}

void ea3_ident_to_property(
    const struct ea3_ident *ident, struct property *ea3_config)
{
    struct property_node *node;
    int i;

    for (i = 0; ea3_ident_psmap[i].type != 0xFF; i++) {
        node = property_search(ea3_config, 0, ea3_ident_psmap[i].path);

        if (node != NULL) {
            property_node_remove(node);
        }
    }

    property_psmap_export(ea3_config, NULL, ident, ea3_ident_psmap);
}

void ea3_ident_replace_property_bool(
    struct property_node *node, const char *name, uint8_t val)
{
    struct property_node *tmp;

    tmp = property_search(NULL, node, name);

    if (tmp) {
        property_node_remove(tmp);
    }

    property_node_create(NULL, node, PROPERTY_TYPE_BOOL, name, val);
}

void ea3_ident_replace_property_str(
    struct property_node *node, const char *name, const char *val)
{
    struct property_node *tmp;

    tmp = property_search(NULL, node, name);

    if (tmp) {
        property_node_remove(tmp);
    }

    tmp = property_node_create(NULL, node, PROPERTY_TYPE_STR, name, val);
}
