#include "OrganicMatLookup.h"

#include "StockpileUtils.h"

#include "modules/Materials.h"
#include "MiscUtils.h"

#include "df/world.h"
#include "df/world_data.h"

#include "df/creature_raw.h"
#include "df/caste_raw.h"
#include "df/material.h"

using namespace DFHack;
using namespace df::enums;
using df::global::world;

using std::endl;

/**
 * Helper class for mapping the various organic mats between their material indices
 * and their index in the stockpile_settings structures.
 */

void OrganicMatLookup::food_mat_by_idx ( std::ostream &out, organic_mat_category::organic_mat_category mat_category, std::vector<int16_t>::size_type food_idx, FoodMat & food_mat )
{
    out << "food_lookup: food_idx(" << food_idx << ") ";
    df::world_raws &raws = world->raws;
    df::special_mat_table table = raws.mat_table;
    int32_t main_idx = table.organic_indexes[mat_category][food_idx];
    int16_t type = table.organic_types[mat_category][food_idx];
    if ( mat_category == organic_mat_category::Fish ||
            mat_category == organic_mat_category::UnpreparedFish ||
            mat_category == organic_mat_category::Eggs )
    {
        food_mat.creature = raws.creatures.all[type];
        food_mat.caste = food_mat.creature->caste[main_idx];
        out << " special creature type(" << type << ") caste("<< main_idx <<")" <<endl;
    }
    else
    {
        food_mat.material.decode ( type, main_idx );
        out << " type(" << type << ") index("<< main_idx <<") token(" << food_mat.material.getToken() <<  ")" << endl;
    }
}
std::string OrganicMatLookup::food_token_by_idx ( std::ostream &out, organic_mat_category::organic_mat_category mat_category, std::vector<int16_t>::size_type idx )
{
    FoodMat food_mat;
    food_mat_by_idx ( out, mat_category, idx, food_mat );
    if ( food_mat.material.isValid() )
    {
        return food_mat.material.getToken();
    }
    else if ( food_mat.creature )
    {
        return food_mat.creature->creature_id + ":" + food_mat.caste->caste_id;
    }
    return std::string();
}

size_t OrganicMatLookup::food_max_size ( organic_mat_category::organic_mat_category mat_category )
{
    return world->raws.mat_table.organic_types[mat_category].size();
}

void OrganicMatLookup::food_build_map ( std::ostream &out )
{
    if ( index_built )
        return;
    df::world_raws &raws = world->raws;
    df::special_mat_table table = raws.mat_table;
    using df::enums::organic_mat_category::organic_mat_category;
    using traits = df::enum_traits<organic_mat_category>;
    for ( int32_t mat_category = traits::first_item_value; mat_category <= traits::last_item_value; ++mat_category )
    {
        for ( size_t i = 0; i < table.organic_indexes[mat_category].size(); ++i )
        {
            int16_t type = table.organic_types[mat_category].at ( i );
            int32_t index = table.organic_indexes[mat_category].at ( i );
            food_index[mat_category].insert ( std::make_pair ( std::make_pair ( type,index ), i ) ); // wtf.. only in c++
        }
    }
    index_built = true;
}

int16_t OrganicMatLookup::food_idx_by_token ( std::ostream &out, organic_mat_category::organic_mat_category mat_category, const std::string & token )
{
    int16_t food_idx = -1;
    df::world_raws &raws = world->raws;
    df::special_mat_table table = raws.mat_table;
    out << "food_idx_by_token: ";
    if ( mat_category == organic_mat_category::Fish ||
            mat_category == organic_mat_category::UnpreparedFish ||
            mat_category == organic_mat_category::Eggs )
    {
        std::vector<std::string> tokens;
        split_string ( &tokens, token, ":" );
        if ( tokens.size() != 2 )
        {
            out << "creature " << "invalid CREATURE:CASTE token: " << token << endl;
        }
        else
        {
            int16_t creature_idx = find_creature ( tokens[0] );
            if ( creature_idx < 0 )
            {
                out << " creature invalid token " << tokens[0];
            }
            else
            {
                food_idx = linear_index ( table.organic_types[mat_category], creature_idx );
                if ( tokens[1] ==  "MALE" )
                    food_idx +=  1;
                if ( table.organic_types[mat_category][food_idx] ==  creature_idx )
                    out << "creature " << token << " caste " <<  tokens[1] <<  " creature_idx(" << creature_idx << ") food_idx("<< food_idx << ")" << endl;
                else
                {
                    out << "ERROR creature caste not found: " << token << " caste " <<  tokens[1] <<  " creature_idx(" << creature_idx << ") food_idx("<< food_idx << ")" << endl;
                    food_idx = -1;
                }
            }
        }
    }
    else
    {
        if ( !index_built )
            food_build_map ( out );
        MaterialInfo mat_info = food_mat_by_token ( out, token );
        int16_t type = mat_info.type;
        int32_t index = mat_info.index;
        int16_t food_idx2 = -1;
        auto it = food_index[mat_category].find ( std::make_pair ( type, index ) );
        if ( it != food_index[mat_category].end() )
        {
            out << "matinfo: " << token << " type(" << mat_info.type << ") idx("  << mat_info.index << ") food_idx(" << it->second << ")" << endl;
            food_idx = it->second;
        }
        else
        {
            out << "matinfo: " << token << " type(" << mat_info.type << ") idx("  << mat_info.index << ") food_idx not found :(" <<  endl;
        }
    }
    return food_idx;
}

MaterialInfo OrganicMatLookup::food_mat_by_token ( std::ostream &out, const std::string & token )
{
    MaterialInfo mat_info;
    mat_info.find ( token );
    return mat_info;
}

bool OrganicMatLookup::index_built = false;
std::vector<OrganicMatLookup::FoodMatMap> OrganicMatLookup::food_index = std::vector<OrganicMatLookup::FoodMatMap> ( df::enum_traits< df::organic_mat_category >::last_item_value + 1 );