void recipe_dictionary::finalize()
{
    if( !DynamicDataLoader::get_instance().load_deferred( deferred ) ) {
        debugmsg( "JSON contains circular dependency: discarded %i recipes", deferred.size() );
    }

    finalize_internal( recipe_dict.recipes );
    finalize_internal( recipe_dict.uncraft );

    for( auto &e : recipe_dict.recipes ) {
        auto &r = e.second;

        for( const auto &bk : r.booksets ) {
            islot_book::recipe_with_description_t desc{ &r, bk.second, item::nname( r.result ), false };
            item::find_type( bk.first )->book->recipes.insert( desc );
        }

        if( r.contained && r.container == "null" ) {
            r.container = item::find_type( r.result )->default_container;
        }

        if( r.autolearn ) {
            r.autolearn_requirements = r.required_skills;
            if( r.skill_used ) {
                r.autolearn_requirements[ r.skill_used ] = r.difficulty;
            }
        }

        // add recipe to category and component caches
        recipe_dict.category[r.category].insert( &r );

        for( const auto &opts : r.requirements().get_components() ) {
            for( const item_comp &comp : opts ) {
                recipe_dict.component[comp.type].insert( &r );
            }
        }

        // if reversible and no specific uncraft recipe exists use this recipe
        if( r.reversible && !recipe_dict.uncraft.count( r.result ) ) {
            recipe_dict.uncraft[ r.result ] = r;
        }
    }

    // add pseudo uncrafting recipes
    for( const auto &e : item_controller->get_all_itypes() ) {

        // books that don't alreay have an uncrafting recipe
        if( e.second->book && !recipe_dict.uncraft.count( e.first ) && e.second->volume > 0 ) {
            int pages = e.second->volume / units::from_milliliter( 12.5 );
            auto &bk = recipe_dict.uncraft[ e.first ];
            bk.ident_ = e.first;
            bk.result = e.first;
            bk.reversible = true;
            bk.requirements_ = *requirement_id( "uncraft_book" ) * pages;
            bk.time = pages * 10; // @todo allow specifying time in requirement_data
        }
    }
}
void recipe_dictionary::finalize()
{
    DynamicDataLoader::get_instance().load_deferred( deferred );

    // remove abstract recipes
    delete_if( []( const recipe & element ) {
        return element.abstract;
    } );

    finalize_internal( recipe_dict.recipes );
    finalize_internal( recipe_dict.uncraft );

    for( auto &e : recipe_dict.recipes ) {
        auto &r = e.second;

        for( const auto &bk : r.booksets ) {
            const itype *booktype = item::find_type( bk.first );
            int req = bk.second > 0 ? bk.second : std::max( booktype->book->req, r.difficulty );
            islot_book::recipe_with_description_t desc{ &r, req, r.result_name(), false };
            const_cast<islot_book &>( *booktype->book ).recipes.insert( desc );
        }

        // if reversible and no specific uncraft recipe exists use this recipe
        if( r.reversible && !recipe_dict.uncraft.count( recipe_id( r.result() ) ) ) {
            recipe_dict.uncraft[ recipe_id( r.result() ) ] = r;
        }
    }

    // add pseudo uncrafting recipes
    for( const itype *e : item_controller->all() ) {
        const itype_id id = e->get_id();
        const recipe_id rid = recipe_id( id );

        // books that don't already have an uncrafting recipe
        if( e->book && !recipe_dict.uncraft.count( rid ) && e->volume > 0 ) {
            int pages = e->volume / units::from_milliliter( 12.5 );
            auto &bk = recipe_dict.uncraft[rid];
            bk.ident_ = rid;
            bk.result_ = id;
            bk.reversible = true;
            bk.requirements_ = *requirement_id( "uncraft_book" ) * pages;
            bk.time = pages * 10; // @todo: allow specifying time in requirement_data
        }
    }

    // Cache auto-learn recipes
    for( const auto &e : recipe_dict.recipes ) {
        if( e.second.autolearn ) {
            recipe_dict.autolearn.insert( &e.second );
        }
    }
}