#include "sfall_lists.h"

#include <unordered_map>
#include <vector>

#include "object.h"
#include "scripts.h"

namespace fallout {

// Due to bad design of |ListType| it's |LIST_ITEMS| and |LIST_CRITTERS| do not
// match |OBJ_TYPE_CRITTER| and |OBJ_TYPE_ITEM|.
static constexpr int kObjectTypeToListType[] = {
    /*    OBJ_TYPE_ITEM */ LIST_ITEMS,
    /* OBJ_TYPE_CRITTER */ LIST_CRITTERS,
    /* OBJ_TYPE_SCENERY */ LIST_SCENERY,
    /*    OBJ_TYPE_WALL */ LIST_WALLS,
    /*    OBJ_TYPE_TILE */ LIST_TILES,
    /*    OBJ_TYPE_MISC */ LIST_MISC,
};

static constexpr int kObjectTypeToListTypeSize = sizeof(kObjectTypeToListType) / sizeof(kObjectTypeToListType[0]);

// As in Sfall.
static constexpr int kInitialListId = 0xCCCCCC;

// Loosely based on [sList] from Sfall.
struct List {
    std::vector<Object*> objects;
    size_t pos = 0;
};

struct SfallListsState {
    std::unordered_map<int, List> lists;
    int nextListId = kInitialListId;
};

static SfallListsState* _state = nullptr;

bool sfallListsInit()
{
    _state = new (std::nothrow) SfallListsState();
    if (_state == nullptr) {
        return false;
    }

    return true;
}

void sfallListsReset()
{
    _state->nextListId = kInitialListId;
    _state->lists.clear();
}

void sfallListsExit()
{
    if (_state != nullptr) {
        delete _state;
        _state = nullptr;
    }
}

int sfallListsCreate(int listType)
{
    int listId = _state->nextListId++;
    List& list = _state->lists[listId];

    if (listType == LIST_TILES) {
        // For unknown reason this list type is not implemented in Sfall.
    } else if (listType == LIST_SPATIAL) {
        for (int elevation = 0; elevation < ELEVATION_COUNT; elevation++) {
            Script* script = scriptGetFirstSpatialScript(elevation);
            while (script != nullptr) {
                Object* obj = script->owner;
                if (obj == nullptr) {
                    obj = scriptGetSelf(script->program);
                }
                list.objects.push_back(obj);
                script = scriptGetNextSpatialScript();
            }
        }
    } else {
        // CE: Implementation is slightly different. Sfall manually loops thru
        // elevations (3) and hexes (40000) and use |objectFindFirstAtLocation|
        // (originally |obj_find_first_at_tile|) to obtain next object. This
        // functionality is already implemented in |objectFindFirst| and
        // |objectFindNext|.
        //
        // As a small optimization |LIST_ALL| is handled separately since there
        // is no need to check object type.
        if (listType == LIST_ALL) {
            Object* obj = objectFindFirst();
            while (obj != nullptr) {
                list.objects.push_back(obj);
                obj = objectFindNext();
            }
        } else {
            Object* obj = objectFindFirst();
            while (obj != nullptr) {
                int objectType = PID_TYPE(obj->pid);
                if (objectType < kObjectTypeToListTypeSize && kObjectTypeToListType[objectType] == listType) {
                    list.objects.push_back(obj);
                }
                obj = objectFindNext();
            }
        }
    }

    return listId;
}

Object* sfallListsGetNext(int listId)
{
    auto it = _state->lists.find(listId);
    if (it != _state->lists.end()) {
        List& list = it->second;
        if (list.pos < list.objects.size()) {
            return list.objects[list.pos++];
        }
    }

    return nullptr;
}

void sfallListsDestroy(int listId)
{
    auto it = _state->lists.find(listId);
    if (it != _state->lists.end()) {
        _state->lists.erase(it);
    }
}

} // namespace fallout
