/*
 *  this file is part of Game Categories Lite
 *  Contain parts of 6.39 TN-A, XmbControl
 *
 *  Copyright (C) 2009, Bubbletune
 *  Copyright (C) 2011, Total_Noob
 *  Copyright (C) 2011, Codestation
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <psputils.h>
#include "categories_lite.h"
#include "psppaf.h"
#include "vshitem.h"
#include "utils.h"
#include "context.h"
#include "config.h"
#include "stub_funcs.h"
#include "language.h"
#include "logger.h"

#define OPTION_PAGE "page_psp_config_umd_cache"

char user_buffer[256];
static u32 backup[4] = { 0, 0, 0, 0 };
int context_mode = 0;
static SceSysconfItem *sysconf_item[] = { NULL, NULL, NULL, NULL };

extern int sysconf_plug;

extern int model;

#define GC_SYSCONF_MODE "gc0"
#define GC_SYSCONF_MODE_SUB "gcs0"
#define GC_SYSCONF_PREFIX "gc1"
#define GC_SYSCONF_PREFIX_SUB "gcs1"
#define GC_SYSCONF_SHOW "gc2"
#define GC_SYSCONF_SHOW_SUB "gcs2"
#define GC_SYSCONF_SORT "gc3"
#define GC_SYSCONF_SORT_SUB "gcs3"

static const char *sysconf_str[] = {
    GC_SYSCONF_MODE,
    GC_SYSCONF_PREFIX,
    GC_SYSCONF_SHOW,
    GC_SYSCONF_SORT
};

static const char *sysconf_sub[] = {
    GC_SYSCONF_MODE_SUB,
    GC_SYSCONF_PREFIX_SUB,
    GC_SYSCONF_SHOW_SUB,
    GC_SYSCONF_SORT_SUB
};

void (*AddSysconfItem)(u32 *option, SceSysconfItem **item);
SceSysconfItem *(*GetSysconfItem)(void *arg0, void *arg1);

int (*vshGetRegistryValue)(u32 *option, char *name, void *arg2, int size, int *value);
int (*vshSetRegistryValue)(u32 *option, char *name, int size,  int *value);

int (*ResolveRefWString)(void *resource, u32 *data, int *a2, char **string, int *t0);
int (*GetPageNodeByID)(void *resource, char *name, SceRcoEntry **child);

void AddSysconfItemPatched(u32 *option, SceSysconfItem **item) {
    AddSysconfItem(option, item);
    if(sysconf_plug) {
        for(u32 i = 0; i < ITEMSOF(sysconf_item); i++) {
            if(sysconf_plug || !sysconf_item[i]) {
                sysconf_item[i] = (SceSysconfItem *)sce_paf_private_malloc(sizeof(SceSysconfItem));
            }
            sce_paf_private_memcpy(sysconf_item[i], *item, sizeof(SceSysconfItem));
            sysconf_item[i]->id = 6;
            sysconf_item[i]->text = sysconf_str[i];
            sysconf_item[i]->regkey = sysconf_str[i];
            sysconf_item[i]->subtitle = sysconf_sub[i];
            sysconf_item[i]->page = OPTION_PAGE;
            option[2] = 1;
            AddSysconfItem(option, &sysconf_item[i]);
        }
    }
    sysconf_plug = 0;
    context_mode = 0;
    context_gamecats = 0;
    kprintf("called, option addr: %08X\n", option);
}

void HijackContext(SceRcoEntry *src, char **options, int n) {
    SceRcoEntry *plane = (SceRcoEntry *)((u32)src + src->first_child);
    SceRcoEntry *mlist = (SceRcoEntry *)((u32)plane + plane->first_child);
    u32 *mlist_param = (u32 *)((u32)mlist + mlist->param);
    kprintf("Called, n: %i, context_mode: %i\n", n, context_mode);
    /* Backup */
    if(backup[0] == 0 && backup[1] == 0 && backup[2] == 0 && backup[3] == 0) {
        kprintf("Making context backup\n");
        backup[0] = mlist->first_child;
        backup[1] = mlist->child_count;
        backup[2] = mlist_param[16];
        backup[3] = mlist_param[18];
    }

    if(context_mode) {
        SceRcoEntry *base = (SceRcoEntry *)((u32)mlist + mlist->first_child);

        SceRcoEntry *item = (SceRcoEntry *)sce_paf_private_malloc(base->next_entry * n);
        u32 *item_param = (u32 *)((u32)item + base->param);

        mlist->first_child = (u32)item - (u32)mlist;
        mlist->child_count = n;
        mlist_param[16] = 13;
        mlist_param[18] = 6;

        for(int i = 0; i < n; i++) {
            sce_paf_private_memcpy(item, base, base->next_entry);

            item_param[0] = 0xDEAD;
            item_param[1] = (u32)options[i];

            if(i != 0) {
                item->prev_entry = item->next_entry;
            }
            if(i == n - 1) {
                item->next_entry = 0;
            }

            item = (SceRcoEntry *)((u32)item + base->next_entry);
            item_param = (u32 *)((u32)item + base->param);
        }
    } else {
        /* Restore */
        mlist->first_child = backup[0];
        mlist->child_count = backup[1];
        mlist_param[16] = backup[2];
        mlist_param[18] = backup[3];
    }

    sceKernelDcacheWritebackAll();
}

SceSysconfItem *GetSysconfItemPatched(void *arg0, void *arg1) {
    SceSysconfItem *item = GetSysconfItem(arg0, arg1);
    kprintf("called, item->text: %s, id: %i\n", item->text, item->id);
    context_mode = 0;
    for(u32 i = 0; i < ITEMSOF(sysconf_str); i++) {
        if(sce_paf_private_strcmp(item->text, sysconf_str[i]) == 0) {
            context_mode = i + 1;
            kprintf("match for %s, using context_mode: %i\n", sysconf_str[i], context_mode);
        }
    }
    return item;
}

int vshGetRegistryValuePatched(u32 *option, char *name, void *arg2, int size, int *value) {
    if (name) {
        context_mode = 0;
        kprintf("name: %s\n", name);
        if (strcmp(name, "/CONFIG/SYSTEM/XMB/language") == 0) {
            lang_id = get_registry_value("/CONFIG/SYSTEM/XMB", "language");
            LoadLanguage(lang_id, model == 4 ? INTERNAL_STORAGE : MEMORY_STICK);
        }
        for(u32 i = 0; i < ITEMSOF(sysconf_str); i++) {
            if(sce_paf_private_strcmp(name, sysconf_str[i]) == 0) {
                context_mode = i + 1;
                kprintf("match for %s, using context_mode: %i\n", sysconf_str[i], context_mode);
                switch(i) {
                case 0:
                    *value = config.mode;
                    return 0;
                case 1:
                    *value = config.prefix;
                    return 0;
                case 2:
                    *value = config.uncategorized;
                    return 0;
                case 3:
                    *value = config.catsort;
                    return 0;
                default:
                    *value = 0;
                    return 0;
                }
            }
        }
    }
    return vshGetRegistryValue(option, name, arg2, size, value);
}

int vshSetRegistryValuePatched(u32 *option, char *name, int size,  int *value) {
    u32 *cfg;
    if (name) {
        kprintf("name: %s\n", name);
        for(u32 i = 0; i < ITEMSOF(sysconf_str); i++) {
            if(sce_paf_private_strcmp(name, sysconf_str[i]) == 0) {
                switch(i) {
                case 0:
                    cfg = &config.mode;
                    break;
                case 1:
                    cfg = &config.prefix;
                    break;
                case 2:
                    cfg = &config.uncategorized;
                    break;
                case 3:
                    cfg = &config.catsort;
                    break;
                default:
                    cfg = NULL;
                    break;
                }
                if(cfg) {
                    *cfg = *value;
                    save_config();
                    return 0;
                }
            }
        }
    }
    return vshSetRegistryValue(option, name, size, value);
}

int ResolveRefWStringPatched(void *resource, u32 *data, int *a2, char **string, int *t0) {
    kprintf("Processing\n");
    if (data[0] == 0xDEAD) {
        kprintf("data: %s\n", (char *)data[1]);
        gc_utf8_to_unicode((wchar_t *)user_buffer, (char *) data[1]);
        *(wchar_t **) string = (wchar_t *) user_buffer;
        return 0;
    }
    return ResolveRefWString(resource, data, a2, string, t0);
}

int GetPageNodeByIDPatched(void *resource, char *name, SceRcoEntry **child) {
    int res = GetPageNodeByID(resource, name, child);
    if(name) {
        //kprintf("name: %s, mode: %i\n", name, context_mode);
        if (sce_paf_private_strcmp(name, OPTION_PAGE) == 0) {
            kprintf("name: %s, mode: %i\n", name, context_mode);
            switch(context_mode) {
            case 0:
                HijackContext(*child, NULL, 0);
                break;
            case 1:
                HijackContext(*child, lang_container.mode, ITEMSOF(lang_container.mode));
                break;
            case 2:
                HijackContext(*child, lang_container.prefix, ITEMSOF(lang_container.prefix));
                break;
            case 3:
                HijackContext(*child, lang_container.show, ITEMSOF(lang_container.show));
                break;
            case 4:
                HijackContext(*child, lang_container.sort, ITEMSOF(lang_container.sort));
                break;
            }
        }
    }
    return res;
}

void PatchVshmainForSysconf(u32 text_addr) {
    vshGetRegistryValue = redir2stub(text_addr+patches.vshGetRegistryValueOffset[patch_index], get_registry_stub, vshGetRegistryValuePatched);
    vshSetRegistryValue = redir2stub(text_addr+patches.vshSetRegistryValueOffset[patch_index], set_registry_stub, vshSetRegistryValuePatched);
}

void PatchPafForSysconf(u32 text_addr) {
    GetPageNodeByID = redir2stub(text_addr+patches.GetPageNodeByIDOffset[patch_index], get_page_node_stub, GetPageNodeByIDPatched);
    ResolveRefWString = redir2stub(text_addr+patches.ResolveRefWStringOffset[patch_index], resolve_ref_wstring_stub, ResolveRefWStringPatched);
}

void PatchSysconf(u32 text_addr) {
    AddSysconfItem = redir_call(text_addr+patches.AddSysconfItem[patch_index], AddSysconfItemPatched);
    GetSysconfItem = redir_call(text_addr+patches.GetSysconfItem[patch_index], GetSysconfItemPatched);
}