#include "assetsAsm.h"

#include <iomanip>
#include <sstream>
#include <vector>

#include "helpers/assetsHelper.h"
#include "helpers/debugHelper.h"
#include "helpers/stringHelper.h"

#include "helpers/c/cEnumsHelper.h"

const std::string ASSETS_START_LABEL = "ASSETS_START";
const std::string ASSETS_END_LABEL = "ASSETS_END";
const std::string ASSETS_SIZE_LABEL = ASSETS_END_LABEL + " - " + ASSETS_START_LABEL;
    
const std::string sectionsPtr = "/assets/sections";

AssetsAsm::AssetsAsm(DkrAssetsSettings &settings) : _settings(settings) {
    _preload_c_context();
    
    _outFilepath = _settings.pathToAsm / "assets/assets.s";
    
    _outPrefix = _settings.pathToBuild / "assets/";
    
    _write();
}

AssetsAsm::~AssetsAsm() {
    
}

void AssetsAsm::_write() {
    _asm.write_comment("This file is automatically generated. Any changes you make to this file will get overwritten.");
    
    // Includes
    _asm.write_newline();
    _asm.write_include("macro.inc");
    _asm.write_newline();
    _asm.write_section("data");
    _asm.write_newline();
    
    _write_asset_sections();
    
    _asm.save(_outFilepath);
}

void AssetsAsm::_write_asset_sections() {
    JsonFile *mainJson = AssetsHelper::get_main_json(_settings);
    
    std::vector<std::string> mainOrder;
    mainJson->get_array<std::string>("/assets/order", mainOrder);
    
    _write_main_table(mainOrder);
    
    _asm.write_header_comment("Asset Sections Table");
    _asm.write_global_label(ASSETS_START_LABEL, true);
    _asm.write_newline();
    
    for(std::string &sectionId : mainOrder) {
        _asm.write_header_comment(sectionId.c_str());
        _asm.write_global_label(sectionId, true);
        
        _write_asset_section(mainJson, sectionId);
        
        _asm.write_global_label(sectionId + "_END");
        _asm.write_newline();
    }
    
    _asm.write_global_label(ASSETS_END_LABEL, true);
}

void AssetsAsm::_write_asset_section(JsonFile *mainJson, std::string &sectionId) {
    std::string sectionPtr = sectionsPtr + "/" + sectionId;
        
    bool isDeferred = mainJson->get_bool(sectionPtr + "/deferred");
    std::string type = mainJson->get_string(sectionPtr + "/type");
    
    DebugHelper::assert(!type.empty(), "(AssetsAsm::_write_asset_sections) [", sectionPtr, 
        "] does not have a type in the main json file!");
    
    if(isDeferred) {
        _write_deferred_asset(mainJson, sectionPtr);
        return;
    }
    
    if(type == "Empty") {
        // TODO
        return;
    }
    
    // Tables are handeled differently.
    if(type == "Table") {
        std::string forSectionId = mainJson->get_string(sectionPtr + "/for");
        DebugHelper::assert(!forSectionId.empty(), "(AssetsAsm::_write_asset_sections) [", sectionPtr, 
            "] table does not have a \"for\" property!");
        std::string forSectionPtr = sectionsPtr + "/" + forSectionId;
        bool isForDeferred = mainJson->get_bool(forSectionPtr + "/deferred");
        if(isForDeferred) {
            _write_deferred_asset_table(mainJson, forSectionPtr, forSectionId);
        } else {
            _write_asset_table(forSectionId);
        }
        std::string forType = mainJson->get_string(forSectionPtr + "/type");
        
        if(forType == "TTGhost") {
            _asm.write_two_words("0xFFFF0000", forSectionId + "_END - " + forSectionId);
            _asm.write_two_words("0xFFFF0000", "0xFFFFFFFF");
        } else {
            if(forType == "Miscellaneous") {
                _asm.write_word("(" + forSectionId + "_END - " + forSectionId + ") / 4");
            } else {
                _asm.write_word(forSectionId + "_END - " + forSectionId);
            }
            _asm.write_word("0xFFFFFFFF");
        }
        return;
    }
    
    // Not deferred, so write them out normally.
    
    std::string filename = mainJson->get_string(sectionPtr + "/filename");
    
    DebugHelper::assert(!filename.empty(), "(AssetsAsm::_write_asset_sections) [", sectionPtr, 
        "] does not have a filename set in the main json file!");
    
    JsonFile *assetSectionJson = AssetsHelper::get_asset_section_json(_settings, sectionId);
    
    DebugHelper::assert(assetSectionJson != nullptr, 
        "(AssetsAsm::_write_asset) [", sectionPtr, "] Could not get section json!");
        
    _write_asset(assetSectionJson);
}

void AssetsAsm::_write_main_table(std::vector<std::string> &mainOrder) {
    _asm.write_header_comment("Asset Sections Table");
    
    std::string numEntries = std::to_string(mainOrder.size());
    _asm.write_word_with_comment(numEntries, "Number of entries in the table");
    
    for(std::string &sectionId : mainOrder) {
        std::string entry = sectionId + " - " + ASSETS_START_LABEL;
        _asm.write_word(entry);
    }
    
    _asm.write_word(ASSETS_SIZE_LABEL);
    
    _asm.write_newline();
}

void AssetsAsm::_write_deferred_asset(JsonFile *mainJson, const std::string &sectionPtr) {
    std::string deferInfoPtr = sectionPtr + "/defer-info";
    
    std::string fromSectionId = mainJson->get_string(deferInfoPtr + "/from-section");
    std::string idPostfix = mainJson->get_string(deferInfoPtr + "/id-postfix");
    fs::path outputLocalPath = mainJson->get_path(deferInfoPtr + "/output-path");
    
    JsonFile *fromSectionJson = AssetsHelper::get_asset_section_json(_settings, fromSectionId);
    
    std::vector<std::string> sectionOrder;
    fromSectionJson->get_array<std::string>("/files/order", sectionOrder);
    
    fs::path fromSectionFolder = fromSectionJson->get_path("/folder");
    
    for(std::string &assetBuildId : sectionOrder) {
        std::string fileSectionPtr = "/files/sections/"+assetBuildId;
        fs::path filename = fromSectionJson->get_string(fileSectionPtr+"/filename");
        filename.replace_extension(".bin");
        
        // TODO: This only works for LevelNames. 
        //       Will need to be modified later since Object Animations aren't one-to-one with Object Models.
        _asm.write_global_label(assetBuildId + idPostfix);
        _asm.write_binary_include(_outPrefix / fromSectionFolder / outputLocalPath / filename);
    }
}

void AssetsAsm::_write_asset(JsonFile *assetSectionJson) {
    fs::path folder = assetSectionJson->get_path("/folder");
    
    if(!assetSectionJson->has("/files")) {
        // Single file asset.
        if(assetSectionJson->has("/filename")) {
            std::string filename = assetSectionJson->get_string("/filename");
            fs::path localFilepath = folder / filename;
            localFilepath.replace_extension(".bin");
            _asm.write_binary_include(_outPrefix / localFilepath);
        }
        return;
    }
    
    std::vector<std::string> sectionOrder;
    assetSectionJson->get_array<std::string>("/files/order", sectionOrder);
    
    for(std::string &assetBuildId : sectionOrder) {
        std::string fileSectionPtr = "/files/sections/"+assetBuildId;
        std::string filename = assetSectionJson->get_string(fileSectionPtr+"/filename");
        DebugHelper::assert(!filename.empty(), "(AssetsAsm::_write_asset) [",
            fileSectionPtr,"] does not define a filename!");
        fs::path localFolder = assetSectionJson->get_string(fileSectionPtr+"/folder");
        fs::path localFilepath = folder;
        if(!localFolder.empty()) {
            localFilepath /= localFolder;
        }
        localFilepath /= filename;
        
        localFilepath.replace_extension(".bin");
        
        _asm.write_global_label(assetBuildId);
        _asm.write_binary_include(_outPrefix / localFilepath);
    }
}

void AssetsAsm::_write_asset_table(const std::string &assetSectionId) {
    JsonFile *targetSectionJson = AssetsHelper::get_asset_section_json(_settings, assetSectionId);
    
    if(targetSectionJson == nullptr) {
        return;
    }
    
    if(!targetSectionJson->has("/files")) {
        return;
    }
    
    std::string targetType = targetSectionJson->get_string("/type");
    
    if(targetType == "MenuText") {
        // Write the number of entries in each language.
        JsonFile *menuTextSection = AssetsHelper::get_asset_section_json(_settings, "ASSET_MENU_TEXT");
        size_t numberOfTextEntries = menuTextSection->length_of_array("/menu-text-build-ids");
        _asm.write_word(std::to_string(numberOfTextEntries));
    }
    
    bool first = true;
    
    std::vector<std::string> sectionOrder;
    targetSectionJson->get_array<std::string>("/files/order", sectionOrder);
    for(std::string &assetBuildId : sectionOrder) {
        
        if(first && targetType == "Audio") {
            _asm.write_comment("For whatever reason, the first table entry in the audio table is not set.");
            first = false;
            continue;
        }
        
        std::string tableEntry = assetBuildId + " - " + assetSectionId;
        if(targetType == "Miscellaneous") {
            tableEntry = "(" + tableEntry + ") / 4";
        } else if (targetType == "GameText") {
            JsonFile *gameTextEntry = AssetsHelper::get_asset_json(_settings, assetSectionId, assetBuildId);
            bool isDialog = gameTextEntry->get_string("/text-type") == "Dialog";
            if(isDialog) {
                // Upper-most bit of the entry signifies that it is a dialog instead of a textbox.
                tableEntry = "(" + tableEntry + ") | 0x80000000";
            }
        }
        
        if(targetType == "TTGhost") {
            JsonFile *ghostJson = AssetsHelper::get_asset_json(_settings, assetSectionId, assetBuildId);
            std::string ghostInfo = _get_ghost_level_and_vehicle(ghostJson);
            _asm.write_two_words(ghostInfo, tableEntry);
            continue;
        }
        
        _asm.write_word(tableEntry);
    }
}

std::string AssetsAsm::_get_ghost_level_and_vehicle(JsonFile *ghostJson) {
    std::string levelId = ghostJson->get_string("/header/level");
    std::string vehicleId = ghostJson->get_string("/header/vehicle");
    
    uint32_t levelIndex = (uint32_t)AssetsHelper::get_asset_index(_settings, "ASSET_LEVEL_HEADERS", levelId);
    uint32_t vehicleIndex = (uint32_t)_c_context.get_int_value_of_symbol(vehicleId);
    
    // First byte is the level index, second is the vehicle index. Bytes 3 & 4 are always zero.
    uint32_t word = ((levelIndex << 24) | (vehicleIndex << 16)) & 0xFFFF0000;
    
    std::stringstream ss;
    
    // Set formatting
    ss << "0x" << std::hex << std::uppercase << std::setfill('0') << std::setw(8);
    
    ss << word;
    
    return ss.str();
}


void AssetsAsm::_write_deferred_asset_table(JsonFile *mainJson, const std::string &sectionPtr, const std::string &assetSectionId) {
    std::string deferInfoPtr = sectionPtr + "/defer-info";
    
    std::string fromSectionId = mainJson->get_string(deferInfoPtr + "/from-section");
    std::string idPostfix = mainJson->get_string(deferInfoPtr + "/id-postfix");
    //fs::path outputLocalPath = mainJson->get_path(deferInfoPtr + "/output-path");
    
    JsonFile *fromSectionJson = AssetsHelper::get_asset_section_json(_settings, fromSectionId);
    
    std::vector<std::string> sectionOrder;
    fromSectionJson->get_array<std::string>("/files/order", sectionOrder);
    for(std::string &assetBuildId : sectionOrder) {
        // TODO: This only works for LevelNames. 
        //       Will need to be modified later since Object Animations aren't one-to-one with Object Models.
        _asm.write_word(assetBuildId + idPostfix + " - " + assetSectionId);
    }
}

void AssetsAsm::_preload_c_context() {
    fs::path includeFolder = _settings.pathToRepo / "include/";
    
    // No structs are needed.
    
    // Only need enums.h
    fs::path enumPaths[] = {
        includeFolder / "enums.h"
    };
    
    for(fs::path enumPath : enumPaths) {
        CEnumsHelper::load_enums_from_file(&_c_context, enumPath);
    }
}
