#include "EncyclopediaDetailPanel.h"

#include "CUIControls.h"
#include "../universe/Condition.h"
#include "../universe/Universe.h"
#include "../universe/Tech.h"
#include "../universe/ShipDesign.h"
#include "../universe/Building.h"
#include "../universe/Planet.h"
#include "../universe/System.h"
#include "../universe/Ship.h"
#include "../universe/Fleet.h"
#include "../universe/Special.h"
#include "../universe/Species.h"
#include "../universe/Field.h"
#include "../universe/Effect.h"
#include "../Empire/Empire.h"
#include "../Empire/EmpireManager.h"
#include "../util/i18n.h"
#include "../util/Logger.h"
#include "../util/OptionsDB.h"
#include "../util/Directories.h"
#include "../client/human/HumanClientApp.h"
#include "../combat/CombatLogManager.h"
#include "DesignWnd.h"
#include "Encyclopedia.h"
#include "../parse/Parse.h"

#include <GG/DrawUtil.h>
#include <GG/StaticGraphic.h>
#include <GG/GUI.h>

namespace {
    const GG::X TEXT_MARGIN_X(3);
    const GG::Y TEXT_MARGIN_Y(3);
    void    AddOptions(OptionsDB& db)
    { db.Add("UI.autogenerated-effects-descriptions", UserStringNop("OPTIONS_DB_AUTO_EFFECT_DESC"),  false,  Validator<bool>()); }
    bool temp_bool = RegisterOptions(&AddOptions);

    const std::string EMPTY_STRING;
    const std::string INCOMPLETE_DESIGN = "incomplete design";
    const std::string UNIVERSE_OBJECT = "universe object";
    const std::string PLANET_SUITABILITY_REPORT = "planet suitability report";
    const std::string COMBAT_LOG = "combat log";
}

namespace {
    const Encyclopedia& GetEncyclopedia() {
        static Encyclopedia encyclopedia;
        return encyclopedia;
    }

    std::string LinkTaggedText(const std::string& tag, const std::string& stringtable_entry)
    { return "<" + tag + " " + stringtable_entry + ">" + UserString(stringtable_entry) + "</" + tag + ">"; }

    std::string LinkTaggedIDText(const std::string& tag, int id, const std::string& text)
    { return "<" + tag + " " + boost::lexical_cast<std::string>(id) + ">" + text + "</" + tag + ">"; }

    std::string PediaDirText(const std::string& dir_name) {
        std::string retval;
        const Encyclopedia& encyclopedia = GetEncyclopedia();
        const Universe& universe = GetUniverse();
        int client_empire_id = HumanClientApp::GetApp()->EmpireID();
        const ObjectMap& objects = Objects();

        std::multimap<std::string, std::string> sorted_entries_list;

        if (dir_name == "ENC_INDEX") {
            sorted_entries_list.insert(std::make_pair(UserString("ENC_SHIP_PART"),      LinkTaggedText(TextLinker::ENCYCLOPEDIA_TAG, "ENC_SHIP_PART") + "\n"));
            sorted_entries_list.insert(std::make_pair(UserString("ENC_SHIP_HULL"),      LinkTaggedText(TextLinker::ENCYCLOPEDIA_TAG, "ENC_SHIP_HULL") + "\n"));
            sorted_entries_list.insert(std::make_pair(UserString("ENC_TECH"),           LinkTaggedText(TextLinker::ENCYCLOPEDIA_TAG, "ENC_TECH") + "\n"));
            sorted_entries_list.insert(std::make_pair(UserString("ENC_BUILDING_TYPE"),  LinkTaggedText(TextLinker::ENCYCLOPEDIA_TAG, "ENC_BUILDING_TYPE") + "\n"));
            sorted_entries_list.insert(std::make_pair(UserString("ENC_SPECIAL"),        LinkTaggedText(TextLinker::ENCYCLOPEDIA_TAG, "ENC_SPECIAL") + "\n"));
            sorted_entries_list.insert(std::make_pair(UserString("ENC_SPECIES"),        LinkTaggedText(TextLinker::ENCYCLOPEDIA_TAG, "ENC_SPECIES") + "\n"));
            sorted_entries_list.insert(std::make_pair(UserString("ENC_FIELD_TYPE"),     LinkTaggedText(TextLinker::ENCYCLOPEDIA_TAG, "ENC_FIELD_TYPE") + "\n"));
            sorted_entries_list.insert(std::make_pair(UserString("ENC_EMPIRE"),         LinkTaggedText(TextLinker::ENCYCLOPEDIA_TAG, "ENC_EMPIRE") + "\n"));
            sorted_entries_list.insert(std::make_pair(UserString("ENC_SHIP_DESIGN"),    LinkTaggedText(TextLinker::ENCYCLOPEDIA_TAG, "ENC_SHIP_DESIGN") + "\n"));
            sorted_entries_list.insert(std::make_pair(UserString("ENC_SHIP"),           LinkTaggedText(TextLinker::ENCYCLOPEDIA_TAG, "ENC_SHIP") + "\n"));
            sorted_entries_list.insert(std::make_pair(UserString("ENC_MONSTER"),        LinkTaggedText(TextLinker::ENCYCLOPEDIA_TAG, "ENC_MONSTER") + "\n"));
            sorted_entries_list.insert(std::make_pair(UserString("ENC_FLEET"),          LinkTaggedText(TextLinker::ENCYCLOPEDIA_TAG, "ENC_FLEET") + "\n"));
            sorted_entries_list.insert(std::make_pair(UserString("ENC_PLANET"),         LinkTaggedText(TextLinker::ENCYCLOPEDIA_TAG, "ENC_PLANET") + "\n"));
            sorted_entries_list.insert(std::make_pair(UserString("ENC_BUILDING"),       LinkTaggedText(TextLinker::ENCYCLOPEDIA_TAG, "ENC_BUILDING") + "\n"));
            sorted_entries_list.insert(std::make_pair(UserString("ENC_SYSTEM"),         LinkTaggedText(TextLinker::ENCYCLOPEDIA_TAG, "ENC_SYSTEM") + "\n"));
            sorted_entries_list.insert(std::make_pair(UserString("ENC_FIELD"),          LinkTaggedText(TextLinker::ENCYCLOPEDIA_TAG, "ENC_FIELD") + "\n"));

            for (std::map<std::string, std::vector<EncyclopediaArticle> >::const_iterator it = encyclopedia.articles.begin();
                 it != encyclopedia.articles.end(); ++it)
            { sorted_entries_list.insert(std::make_pair(UserString(it->first),  LinkTaggedText(TextLinker::ENCYCLOPEDIA_TAG, it->first) + "\n")); }

        } else if (dir_name == "ENC_SHIP_PART") {
            const PartTypeManager& part_type_manager = GetPartTypeManager();
            for (PartTypeManager::iterator it = part_type_manager.begin(); it != part_type_manager.end(); ++it)
                sorted_entries_list.insert(std::make_pair(UserString(it->first),  LinkTaggedText(VarText::SHIP_PART_TAG, it->first) + "\n"));

        } else if (dir_name == "ENC_SHIP_HULL") {
            const HullTypeManager& hull_type_manager = GetHullTypeManager();
            for (HullTypeManager::iterator it = hull_type_manager.begin(); it != hull_type_manager.end(); ++it)
                sorted_entries_list.insert(std::make_pair(UserString(it->first),  LinkTaggedText(VarText::SHIP_HULL_TAG, it->first) + "\n"));

        } else if (dir_name == "ENC_TECH") {
            std::vector<std::string> tech_names = GetTechManager().TechNames();
            std::map<std::string, std::string> userstring_tech_names;
            // sort tech names by user-visible name, so names are shown alphabetically in UI
            for (std::vector<std::string>::const_iterator it = tech_names.begin(); it != tech_names.end(); ++it)
                userstring_tech_names[UserString(*it)] = *it;
            for (std::map<std::string, std::string>::const_iterator it = userstring_tech_names.begin(); it != userstring_tech_names.end(); ++it)
                sorted_entries_list.insert(std::make_pair(UserString(it->first),  LinkTaggedText(VarText::TECH_TAG, it->second) + "\n"));

        } else if (dir_name == "ENC_BUILDING_TYPE") {
            const BuildingTypeManager& building_type_manager = GetBuildingTypeManager();
            for (BuildingTypeManager::iterator it = building_type_manager.begin(); it != building_type_manager.end(); ++it)
                sorted_entries_list.insert(std::make_pair(UserString(it->first),  LinkTaggedText(VarText::BUILDING_TYPE_TAG, it->first) + "\n"));

        } else if (dir_name == "ENC_SPECIAL") {
            const std::vector<std::string> special_names = SpecialNames();
            for (std::vector<std::string>::const_iterator it = special_names.begin(); it != special_names.end(); ++it)
                sorted_entries_list.insert(std::make_pair(UserString(*it),  LinkTaggedText(VarText::SPECIAL_TAG, *it) + "\n"));

        } else if (dir_name == "ENC_SPECIES") {
            const SpeciesManager& species_manager = GetSpeciesManager();
            for (SpeciesManager::iterator it = species_manager.begin(); it != species_manager.end(); ++it)
                sorted_entries_list.insert(std::make_pair(UserString(it->first),  LinkTaggedText(VarText::SPECIES_TAG, it->first) + "\n"));

        } else if (dir_name == "ENC_FIELD_TYPE") {
            const FieldTypeManager& fields_manager = GetFieldTypeManager();
            for (FieldTypeManager::iterator it = fields_manager.begin(); it != fields_manager.end(); ++it)
                sorted_entries_list.insert(std::make_pair(UserString(it->first),  LinkTaggedText(VarText::FIELD_TYPE_TAG, it->first) + "\n"));

        } else if (dir_name == "ENC_EMPIRE") {
            const EmpireManager& empire_manager = Empires();
            for (EmpireManager::const_iterator it = empire_manager.begin(); it != empire_manager.end(); ++it)
                sorted_entries_list.insert(std::make_pair(UserString(it->second->Name()),  LinkTaggedIDText(VarText::EMPIRE_ID_TAG, it->first, it->second->Name()) + "\n"));

        } else if (dir_name == "ENC_SHIP_DESIGN") {
            for (Universe::ship_design_iterator it = universe.beginShipDesigns(); it != universe.endShipDesigns(); ++it)
                if (!it->second->IsMonster())
                    sorted_entries_list.insert(std::make_pair(UserString(it->second->Name()),  LinkTaggedIDText(VarText::DESIGN_ID_TAG, it->first, it->second->Name()) + "\n"));

        } else if (dir_name == "ENC_SHIP") {
            for (ObjectMap::const_iterator<Ship> ship_it = objects.const_begin<Ship>();
                 ship_it != objects.const_end<Ship>(); ++ship_it)
            {
                const std::string& ship_name = ship_it->PublicName(client_empire_id);
                sorted_entries_list.insert(std::make_pair(ship_name,
                    LinkTaggedIDText(VarText::SHIP_ID_TAG, ship_it->ID(), ship_name) + "  "));
            }

        } else if (dir_name == "ENC_MONSTER") {
            // monster objects
            std::vector<const Ship*> monsters;
            for (ObjectMap::const_iterator<Ship> ship_it = objects.const_begin<Ship>();
                 ship_it != objects.const_end<Ship>(); ++ship_it)
            {
                if (ship_it->IsMonster())
                    monsters.push_back(*ship_it);
            }
            if (!monsters.empty()) {
                retval += UserString("MONSTER_OBJECTS");
                for (std::vector<const Ship*>::const_iterator ship_it = monsters.begin(); ship_it != monsters.end(); ++ship_it) {
                    const Ship* ship = *ship_it;
                    const std::string& ship_name = ship->PublicName(client_empire_id);
                    retval += LinkTaggedIDText(VarText::SHIP_ID_TAG, ship->ID(), ship_name) + "  ";
                }
            } else {
                retval += UserString("NO_MONSTER_OBJECTS");
            }

            // monster types
            retval += "\n\n" + UserString("MONSTER_TYPES") + "\n";
            for (Universe::ship_design_iterator it = universe.beginShipDesigns(); it != universe.endShipDesigns(); ++it)
                if (it->second->IsMonster())
                    sorted_entries_list.insert(std::make_pair(UserString(it->second->Name()),  LinkTaggedIDText(VarText::DESIGN_ID_TAG, it->first, it->second->Name()) + "\n"));

        } else if (dir_name == "ENC_FLEET") {
            for (ObjectMap::const_iterator<Fleet> fleet_it = objects.const_begin<Fleet>();
                 fleet_it != objects.const_end<Fleet>(); ++fleet_it)
            {
                const std::string& flt_name = fleet_it->PublicName(client_empire_id);
                sorted_entries_list.insert(std::make_pair(flt_name,
                    LinkTaggedIDText(VarText::FLEET_ID_TAG, fleet_it->ID(), flt_name) + "  "));
            }

        } else if (dir_name == "ENC_PLANET") {
            for (ObjectMap::const_iterator<Planet> planet_it = objects.const_begin<Planet>();
                 planet_it != objects.const_end<Planet>(); ++planet_it)
            {
                const std::string& plt_name = planet_it->PublicName(client_empire_id);
                sorted_entries_list.insert(std::make_pair(plt_name,
                    LinkTaggedIDText(VarText::PLANET_ID_TAG, planet_it->ID(), plt_name) + "  "));
            }

        } else if (dir_name == "ENC_BUILDING") {
            for (ObjectMap::const_iterator<Building> building_it = objects.const_begin<Building>();
                 building_it != objects.const_end<Building>(); ++building_it)
            {
                const std::string& bld_name = building_it->PublicName(client_empire_id);
                sorted_entries_list.insert(std::make_pair(bld_name,
                    LinkTaggedIDText(VarText::BUILDING_ID_TAG, building_it->ID(), bld_name) + "  "));
            }

        } else if (dir_name == "ENC_SYSTEM") {
            for (ObjectMap::const_iterator<System> system_it = objects.const_begin<System>();
                 system_it != objects.const_end<System>(); ++system_it)
            {
                const std::string& sys_name = system_it->ApparentName(client_empire_id);
                sorted_entries_list.insert(std::make_pair(sys_name,
                    LinkTaggedIDText(VarText::SYSTEM_ID_TAG, system_it->ID(), sys_name) + "  "));
            }

        } else if (dir_name == "ENC_FIELD") {
            for (ObjectMap::const_iterator<Field> field_it = objects.const_begin<Field>();
                 field_it != objects.const_end<Field>(); ++field_it)
            {
                const std::string& field_name = field_it->Name();
                sorted_entries_list.insert(std::make_pair(field_name,
                    LinkTaggedIDText(VarText::FIELD_ID_TAG, field_it->ID(), field_name) + "  "));
            }

        } else {
            std::map<std::string, std::vector<EncyclopediaArticle> >::const_iterator category_it =
                encyclopedia.articles.find(dir_name);
            if (category_it != encyclopedia.articles.end()) {
                const std::vector<EncyclopediaArticle>& articles = category_it->second;
                for (std::vector<EncyclopediaArticle>::const_iterator article_it = articles.begin();
                     article_it != articles.end(); ++article_it)
                { sorted_entries_list.insert(std::make_pair(UserString(article_it->name),  LinkTaggedText(TextLinker::ENCYCLOPEDIA_TAG, article_it->name) + "\n")); }
            }
        }

        // add sorted entries
        for (std::multimap<std::string, std::string>::const_iterator it = sorted_entries_list.begin();
             it != sorted_entries_list.end(); ++it)
        { retval += it->second; }

        return retval;
    }
}

std::list <std::pair<std::string, std::string> >            EncyclopediaDetailPanel::m_items = std::list<std::pair<std::string, std::string> >(0);
std::list <std::pair<std::string, std::string> >::iterator  EncyclopediaDetailPanel::m_items_it = m_items.begin();

EncyclopediaDetailPanel::EncyclopediaDetailPanel(GG::X w, GG::Y h) :
    CUIWnd("", GG::X1, GG::Y1, w - 1, h - 1, GG::ONTOP | GG::INTERACTIVE | GG::DRAGABLE | GG::RESIZABLE | CLOSABLE),
    m_name_text(0),
    m_cost_text(0),
    m_summary_text(0),
    m_description_box(0),
    m_icon(0),
    m_other_icon(0)
{
    const int PTS = ClientUI::Pts();
    const int NAME_PTS = PTS*3/2;
    const int COST_PTS = PTS;
    const int SUMMARY_PTS = PTS*4/3;
    const int ICON_WIDTH(32);
    boost::shared_ptr<GG::Font> font = ClientUI::GetFont();

    m_name_text =       new GG::TextControl(GG::X0, GG::Y0, GG::X(10), GG::Y(10), "", ClientUI::GetBoldFont(NAME_PTS),  ClientUI::TextColor());
    m_cost_text =       new GG::TextControl(GG::X0, GG::Y0, GG::X(10), GG::Y(10), "", ClientUI::GetFont(COST_PTS),      ClientUI::TextColor());
    m_summary_text =    new GG::TextControl(GG::X0, GG::Y0, GG::X(10), GG::Y(10), "", ClientUI::GetFont(SUMMARY_PTS),   ClientUI::TextColor());

    m_index_button =    new GG::Button(GG::X0, GG::Y0, GG::X(ICON_WIDTH), GG::Y(ICON_WIDTH), "", font, GG::CLR_WHITE, GG::CLR_ZERO);
    m_back_button =     new GG::Button(GG::X0, GG::Y0, GG::X(ICON_WIDTH), GG::Y(ICON_WIDTH), "", font, GG::CLR_WHITE, GG::CLR_ZERO);
    m_next_button =     new GG::Button(GG::X0, GG::Y0, GG::X(ICON_WIDTH), GG::Y(ICON_WIDTH), "", font, GG::CLR_WHITE, GG::CLR_ZERO);
    m_back_button->Disable();
    m_next_button->Disable();

    m_index_button->SetUnpressedGraphic(GG::SubTexture(ClientUI::GetTexture( ClientUI::ArtDir() / "icons" / "buttons" / "uparrownormal.png"      )));
    m_index_button->SetPressedGraphic  (GG::SubTexture(ClientUI::GetTexture( ClientUI::ArtDir() / "icons" / "buttons" / "uparrowclicked.png"     )));
    m_index_button->SetRolloverGraphic (GG::SubTexture(ClientUI::GetTexture( ClientUI::ArtDir() / "icons" / "buttons" / "uparrowmouseover.png"   )));
    m_back_button->SetUnpressedGraphic (GG::SubTexture(ClientUI::GetTexture( ClientUI::ArtDir() / "icons" / "buttons" / "leftarrownormal.png"    )));
    m_back_button->SetPressedGraphic   (GG::SubTexture(ClientUI::GetTexture( ClientUI::ArtDir() / "icons" / "buttons" / "leftarrowclicked.png"   )));
    m_back_button->SetRolloverGraphic  (GG::SubTexture(ClientUI::GetTexture( ClientUI::ArtDir() / "icons" / "buttons" / "leftarrowmouseover.png" )));
    m_next_button->SetUnpressedGraphic (GG::SubTexture(ClientUI::GetTexture( ClientUI::ArtDir() / "icons" / "buttons" / "rightarrownormal.png"   )));
    m_next_button->SetPressedGraphic   (GG::SubTexture(ClientUI::GetTexture( ClientUI::ArtDir() / "icons" / "buttons" / "rightarrowclicked.png"  )));
    m_next_button->SetRolloverGraphic  (GG::SubTexture(ClientUI::GetTexture( ClientUI::ArtDir() / "icons" / "buttons" / "rightarrowmouseover.png")));

    CUILinkTextMultiEdit* desc_box = new CUILinkTextMultiEdit(GG::X0, GG::Y0, GG::X(10), GG::Y(10), "", GG::MULTI_WORDBREAK | GG::MULTI_READ_ONLY);
    GG::Connect(desc_box->LinkClickedSignal,        &EncyclopediaDetailPanel::HandleLinkClick,          this);
    GG::Connect(desc_box->LinkDoubleClickedSignal,  &EncyclopediaDetailPanel::HandleLinkDoubleClick,    this);
    GG::Connect(desc_box->LinkRightClickedSignal,   &EncyclopediaDetailPanel::HandleLinkDoubleClick,    this);
    GG::Connect(m_index_button->LeftClickedSignal,  &EncyclopediaDetailPanel::OnIndex,                  this);
    GG::Connect(m_back_button->LeftClickedSignal,   &EncyclopediaDetailPanel::OnBack,                   this);
    GG::Connect(m_next_button->LeftClickedSignal,   &EncyclopediaDetailPanel::OnNext,                   this);
    m_description_box = desc_box;
    m_description_box->SetColor(GG::CLR_ZERO);
    m_description_box->SetInteriorColor(ClientUI::CtrlColor());

    AttachChild(m_name_text);
    AttachChild(m_cost_text);
    AttachChild(m_summary_text);
    AttachChild(m_description_box);
    AttachChild(m_index_button);
    AttachChild(m_back_button);
    AttachChild(m_next_button);

    SetChildClippingMode(ClipToWindow);

    DoLayout();

    AddItem(TextLinker::ENCYCLOPEDIA_TAG, "ENC_INDEX");
}

void EncyclopediaDetailPanel::DoLayout() {
    const int PTS = ClientUI::Pts();
    const int NAME_PTS = PTS*3/2;
    const int COST_PTS = PTS;
    const int SUMMARY_PTS = PTS*4/3;

    const int ICON_SIZE = 12 + NAME_PTS + COST_PTS + SUMMARY_PTS;

    const int BTN_WIDTH = 24;

    // name
    GG::Pt ul = GG::Pt();
    GG::Pt lr = ul + GG::Pt(Width(), GG::Y(NAME_PTS + 4));
    m_name_text->SizeMove(ul, lr);

    // cost / turns
    ul += GG::Pt(GG::X0, m_name_text->Height());
    lr = ul + GG::Pt(Width(), GG::Y(COST_PTS + 4));
    m_cost_text->SizeMove(ul, lr);

    // one line summary
    ul += GG::Pt(GG::X0, m_cost_text->Height());
    lr = ul + GG::Pt(Width(), GG::Y(SUMMARY_PTS + 4));
    m_summary_text->SizeMove(ul, lr);

    // main verbose description (fluff, effects, unlocks, ...)
    ul = GG::Pt(BORDER_LEFT, ICON_SIZE + TEXT_MARGIN_Y + 1);
    lr = GG::Pt(Width() - BORDER_RIGHT, Height() - BORDER_BOTTOM*3 - PTS - 4);
    m_description_box->SizeMove(ul, lr);

    // "back" button
    ul = GG::Pt(Width() - BORDER_RIGHT*3 - BTN_WIDTH * 3 - 8, Height() - BORDER_BOTTOM*2 - PTS);
    lr = GG::Pt(Width() - BORDER_RIGHT*3 - BTN_WIDTH * 2 - 8, Height() - BORDER_BOTTOM*2);
    m_back_button->SizeMove(ul, lr);

    // "up" button
    ul = GG::Pt(Width() - BORDER_RIGHT*3 - BTN_WIDTH * 2 - 4, Height() - BORDER_BOTTOM*3 - PTS);
    lr = GG::Pt(Width() - BORDER_RIGHT*3 - BTN_WIDTH - 4, Height() - BORDER_BOTTOM*3);
    m_index_button->SizeMove(ul, lr);

    // "next" button
    ul = GG::Pt(Width() - BORDER_RIGHT*3 - BTN_WIDTH, Height() - BORDER_BOTTOM*2 - PTS);
    lr = GG::Pt(Width() - BORDER_RIGHT*3, Height() - BORDER_BOTTOM*2);
    m_next_button->SizeMove(ul, lr);

    // icon
    if (m_icon) {
        ul = GG::Pt(GG::X1, GG::Y1);
        lr = ul + GG::Pt(GG::X(ICON_SIZE), GG::Y(ICON_SIZE));
        m_icon->SizeMove(ul, lr);
    }
    // other icon
    if (m_other_icon) {
        lr = GG::Pt(Width() - BORDER_RIGHT, GG::Y(ICON_SIZE + 1));
        ul = lr - GG::Pt(GG::X(ICON_SIZE), GG::Y(ICON_SIZE));
        m_other_icon->SizeMove(ul, lr);
    }
    MoveChildUp(m_close_button);    // so it's over top of the top-right icon
}

void EncyclopediaDetailPanel::SizeMove(const GG::Pt& ul, const GG::Pt& lr) {
    GG::Pt old_size = GG::Wnd::Size();

    CUIWnd::SizeMove(ul, lr);

    if (old_size != GG::Wnd::Size())
        DoLayout();
}

GG::Pt EncyclopediaDetailPanel::ClientUpperLeft() const
{ return GG::Wnd::UpperLeft(); }

void EncyclopediaDetailPanel::Render() {
    GG::Pt ul = UpperLeft();
    GG::Pt lr = LowerRight();
    const GG::Y ICON_SIZE = m_summary_text->LowerRight().y - m_name_text->UpperLeft().y;
    GG::Pt cl_ul = ul + GG::Pt(BORDER_LEFT, ICON_SIZE + BORDER_BOTTOM); // BORDER_BOTTOM is the size of the border at the bottom of a standard CUIWnd
    GG::Pt cl_lr = lr - GG::Pt(BORDER_RIGHT, BORDER_BOTTOM);

   // use GL to draw the lines
    glDisable(GL_TEXTURE_2D);
    GLint initial_modes[2];
    glGetIntegerv(GL_POLYGON_MODE, initial_modes);

    // draw background
    glPolygonMode(GL_BACK, GL_FILL);
    glBegin(GL_POLYGON);
        glColor(ClientUI::WndColor());
        glVertex(ul.x, ul.y);
        glVertex(lr.x, ul.y);
        glVertex(lr.x, lr.y - OUTER_EDGE_ANGLE_OFFSET);
        glVertex(lr.x - OUTER_EDGE_ANGLE_OFFSET, lr.y);
        glVertex(ul.x, lr.y);
        glVertex(ul.x, ul.y);
    glEnd();

    // draw outer border on pixel inside of the outer edge of the window
    glPolygonMode(GL_BACK, GL_LINE);
    glBegin(GL_POLYGON);
        glColor(ClientUI::WndOuterBorderColor());
        glVertex(ul.x, ul.y);
        glVertex(lr.x, ul.y);
        glVertex(lr.x, lr.y - OUTER_EDGE_ANGLE_OFFSET);
        glVertex(lr.x - OUTER_EDGE_ANGLE_OFFSET, lr.y);
        glVertex(ul.x, lr.y);
        glVertex(ul.x, ul.y);
    glEnd();

    // reset this to whatever it was initially
    glPolygonMode(GL_BACK, initial_modes[1]);

    // draw inner border, including extra resize-tab lines
    glBegin(GL_LINE_STRIP);
        glColor(ClientUI::WndInnerBorderColor());
        glVertex(cl_ul.x, cl_ul.y);
        glVertex(cl_lr.x, cl_ul.y);
        glVertex(cl_lr.x, cl_lr.y - INNER_BORDER_ANGLE_OFFSET);
        glVertex(cl_lr.x - INNER_BORDER_ANGLE_OFFSET, cl_lr.y);
        glVertex(cl_ul.x, cl_lr.y);
        glVertex(cl_ul.x, cl_ul.y);
    glEnd();
    glBegin(GL_LINES);
        // draw the extra lines of the resize tab
        glColor(ClientUI::WndInnerBorderColor());
        glVertex(cl_lr.x, cl_lr.y - RESIZE_HASHMARK1_OFFSET);
        glVertex(cl_lr.x - RESIZE_HASHMARK1_OFFSET, cl_lr.y);

        glVertex(cl_lr.x, cl_lr.y - RESIZE_HASHMARK2_OFFSET);
        glVertex(cl_lr.x - RESIZE_HASHMARK2_OFFSET, cl_lr.y);
    glEnd();
    glEnable(GL_TEXTURE_2D);
}

void EncyclopediaDetailPanel::HandleLinkClick(const std::string& link_type, const std::string& data) {
    using boost::lexical_cast;
    try {
        if (link_type == VarText::PLANET_ID_TAG) {
            ClientUI::GetClientUI()->ZoomToPlanet(lexical_cast<int>(data));
            this->SetPlanet(lexical_cast<int>(data));

        } else if (link_type == VarText::SYSTEM_ID_TAG) {
            ClientUI::GetClientUI()->ZoomToSystem(lexical_cast<int>(data));
        } else if (link_type == VarText::FLEET_ID_TAG) {
            ClientUI::GetClientUI()->ZoomToFleet(lexical_cast<int>(data));
        } else if (link_type == VarText::SHIP_ID_TAG) {
            ClientUI::GetClientUI()->ZoomToShip(lexical_cast<int>(data));
        } else if (link_type == VarText::BUILDING_ID_TAG) {
            ClientUI::GetClientUI()->ZoomToBuilding(lexical_cast<int>(data));
        } else if (link_type == VarText::FIELD_ID_TAG) {
            ClientUI::GetClientUI()->ZoomToField(lexical_cast<int>(data));

        } else if (link_type == VarText::COMBAT_ID_TAG) {
            ClientUI::GetClientUI()->ZoomToCombatLog(lexical_cast<int>(data));

        } else if (link_type == VarText::EMPIRE_ID_TAG) {
            this->SetEmpire(lexical_cast<int>(data));
        } else if (link_type == VarText::DESIGN_ID_TAG) {
            this->SetDesign(lexical_cast<int>(data));
        } else if (link_type == VarText::PREDEFINED_DESIGN_TAG) {
            if (const ShipDesign* design = GetPredefinedShipDesign(data))
                this->SetDesign(design->ID());

        } else if (link_type == VarText::TECH_TAG) {
            this->SetTech(data);
        } else if (link_type == VarText::BUILDING_TYPE_TAG) {
            this->SetBuildingType(data);
        } else if (link_type == VarText::FIELD_TYPE_TAG) {
            this->SetFieldType(data);
        } else if (link_type == VarText::SPECIAL_TAG) {
            this->SetSpecial(data);
        } else if (link_type == VarText::SHIP_HULL_TAG) {
            this->SetHullType(data);
        } else if (link_type == VarText::SHIP_PART_TAG) {
            this->SetPartType(data);
        } else if (link_type == VarText::SPECIES_TAG) {
            this->SetSpecies(data);
        } else if (link_type == TextLinker::ENCYCLOPEDIA_TAG) {
            this->SetText(data, false);
        }
    } catch (const boost::bad_lexical_cast&) {
        Logger().errorStream() << "EncyclopediaDetailPanel::HandleLinkClick caught lexical cast exception for link type: " << link_type << " and data: " << data;
    }
}

void EncyclopediaDetailPanel::HandleLinkDoubleClick(const std::string& link_type, const std::string& data) {
    using boost::lexical_cast;
    try {
        if (link_type == VarText::PLANET_ID_TAG) {
            ClientUI::GetClientUI()->ZoomToPlanet(lexical_cast<int>(data));
        } else if (link_type == VarText::SYSTEM_ID_TAG) {
            ClientUI::GetClientUI()->ZoomToSystem(lexical_cast<int>(data));
        } else if (link_type == VarText::FLEET_ID_TAG) {
            ClientUI::GetClientUI()->ZoomToFleet(lexical_cast<int>(data));
        } else if (link_type == VarText::SHIP_ID_TAG) {
            ClientUI::GetClientUI()->ZoomToShip(lexical_cast<int>(data));
        } else if (link_type == VarText::BUILDING_ID_TAG) {
            ClientUI::GetClientUI()->ZoomToBuilding(lexical_cast<int>(data));

        } else if (link_type == VarText::EMPIRE_ID_TAG) {
            ClientUI::GetClientUI()->ZoomToEmpire(lexical_cast<int>(data));
        } else if (link_type == VarText::DESIGN_ID_TAG) {
            ClientUI::GetClientUI()->ZoomToShipDesign(lexical_cast<int>(data));
        } else if (link_type == VarText::PREDEFINED_DESIGN_TAG) {
            if (const ShipDesign* design = GetPredefinedShipDesign(data))
                ClientUI::GetClientUI()->ZoomToShipDesign(design->ID());

        } else if (link_type == VarText::TECH_TAG) {
            ClientUI::GetClientUI()->ZoomToTech(data);
        } else if (link_type == VarText::BUILDING_TYPE_TAG) {
            ClientUI::GetClientUI()->ZoomToBuildingType(data);
        } else if (link_type == VarText::SPECIAL_TAG) {
            ClientUI::GetClientUI()->ZoomToSpecial(data);
        } else if (link_type == VarText::SHIP_HULL_TAG) {
            ClientUI::GetClientUI()->ZoomToShipHull(data);
        } else if (link_type == VarText::SHIP_PART_TAG) {
            ClientUI::GetClientUI()->ZoomToShipPart(data);
        } else if (link_type == VarText::SPECIES_TAG) {
            ClientUI::GetClientUI()->ZoomToSpecies(data);

        } else if (link_type == TextLinker::ENCYCLOPEDIA_TAG) {
            this->SetText(data, false);
        }
    } catch (const boost::bad_lexical_cast&) {
        Logger().errorStream() << "EncyclopediaDetailPanel::HandleLinkDoubleClick caught lexical cast exception for link type: " << link_type << " and data: " << data;
    }
}

namespace {
    int DefaultLocationForEmpire(int empire_id) {
        const Empire* empire = Empires().Lookup(empire_id);
        if (!empire) {
            Logger().debugStream() << "DefaultLocationForEmpire: Unable to get empire with ID: " << empire_id;
            return INVALID_OBJECT_ID;
        }
        // get a location where the empire might build something.
        const UniverseObject* location = GetUniverseObject(empire->CapitalID());
        // no capital?  scan through all objects to find one owned by this empire
        // TODO: only loop over planets?
        // TODO: pass in a location condition, and pick a location that matches it if possible
        if (!location) {
            for (ObjectMap::const_iterator<> obj_it = Objects().const_begin(); obj_it != Objects().const_end(); ++obj_it) {
                if (obj_it->OwnedBy(empire_id)) {
                    location = *obj_it;
                    break;
                }
            }
        }
        return location ? location->ID() : INVALID_OBJECT_ID;
    }

    std::vector<std::string> TechsThatUnlockItem(const ItemSpec& item) {
        std::vector<std::string> retval;

        const TechManager& tm = GetTechManager();
        for (TechManager::iterator it = tm.begin(); it != tm.end(); ++it) {
            const Tech* tech = *it;
            if (!tech) continue;
            const std::string& tech_name = tech->Name();

            const std::vector<ItemSpec>& unlocked_items = tech->UnlockedItems();
            bool found_item = false;
            for (std::vector<ItemSpec>::const_iterator item_it = unlocked_items.begin();
                 item_it != unlocked_items.end(); ++item_it)
            {
                if (*item_it == item) {
                    found_item = true;
                    break;
                }
            }
            if (found_item)
                retval.push_back(tech_name);
        }

        return retval;
    }

    const std::string& GeneralTypeOfObject(UniverseObjectType obj_type) {
        switch (obj_type) {
        case OBJ_SHIP:          return UserString("ENC_SHIP");          break;
        case OBJ_FLEET:         return UserString("ENC_FLEET");         break;
        case OBJ_PLANET:        return UserString("ENC_PLANET");        break;
        case OBJ_BUILDING:      return UserString("ENC_BUILDING");      break;
        case OBJ_SYSTEM:        return UserString("ENC_SYSTEM");        break;
        case OBJ_FIELD:         return UserString("END_FIELD");         break;
        case OBJ_POP_CENTER:    return UserString("ENC_POP_CENTER");    break;
        case OBJ_PROD_CENTER:   return UserString("END_PROD_CENTER");   break;
        default:                return EMPTY_STRING;
        }
    }

    const std::string& LinkTag(UniverseObjectType obj_type) {
        switch (obj_type) {
        case OBJ_SHIP:          return VarText::SHIP_ID_TAG;        break;
        case OBJ_FLEET:         return VarText::FLEET_ID_TAG;       break;
        case OBJ_PLANET:        return VarText::PLANET_ID_TAG;      break;
        case OBJ_BUILDING:      return VarText::BUILDING_ID_TAG;    break;
        case OBJ_SYSTEM:        return VarText::SYSTEM_ID_TAG;      break;
        case OBJ_FIELD:
        default:                return EMPTY_STRING;
        }
    }
}

void EncyclopediaDetailPanel::Refresh() {
    if (m_icon) {
        DeleteChild(m_icon);
        m_icon = 0;
    }
    if (m_other_icon) {
        DeleteChild(m_other_icon);
        m_other_icon = 0;
    }
    m_name_text->Clear();
    m_summary_text->Clear();
    m_cost_text->Clear();
    m_description_box->Clear();

    const Encyclopedia& encyclopedia = GetEncyclopedia();

    // get details of item as applicable in order to set summary, cost, description TextControls
    std::string name;
    boost::shared_ptr<GG::Texture> texture;
    boost::shared_ptr<GG::Texture> other_texture;
    int turns = -1;
    double cost = 0.0;
    std::string cost_units;             // "PP" or "RP" or empty string, depending on whether and what something costs
    std::string general_type;           // general type of thing being shown, eg. "Building" or "Ship Part"
    std::string specific_type;          // specific type of thing; thing's purpose.  eg. "Farming" or "Colonization".  May be left blank for things without specific types (eg. specials)
    std::string detailed_description;
    GG::Clr color(GG::CLR_ZERO);

    using boost::io::str;
    if (m_items.empty())
        return;

    int client_empire_id = HumanClientApp::GetApp()->EmpireID();
    const ObjectMap& objects = Objects();

    if (m_items_it->first == TextLinker::ENCYCLOPEDIA_TAG) {
        // attempt to treat as a directory
        detailed_description = PediaDirText(m_items_it->second);
        if (!detailed_description.empty()) {
            name = UserString(m_items_it->second);

        } else {
            // couldn't find a directory; look up in custom encyclopedia entries
            for (std::map<std::string, std::vector<EncyclopediaArticle> >::const_iterator category_it = encyclopedia.articles.begin();
                 category_it != encyclopedia.articles.end(); ++category_it)
            {
                const std::vector<EncyclopediaArticle>& articles = category_it->second;
                for (std::vector<EncyclopediaArticle>::const_iterator article_it = articles.begin();
                     article_it != articles.end(); ++article_it)
                {
                    if (article_it->name != m_items_it->second)
                        continue;
                    name = UserString(article_it->name);
                    detailed_description = UserString(article_it->description);
                    general_type = UserString(article_it->category);
                    specific_type = UserString(article_it->short_description);
                    texture = ClientUI::GetTexture(ClientUI::ArtDir() / article_it->icon, true);
                    break;
                }
            }
        }

    } else if (m_items_it->first == "ENC_SHIP_PART") {
        const PartType* part = GetPartType(m_items_it->second);
        if (!part) {
            Logger().errorStream() << "EncyclopediaDetailPanel::Refresh couldn't find part with name " << m_items_it->second;
            return;
        }

        // Ship Parts
        name = UserString(m_items_it->second);
        texture = ClientUI::PartIcon(m_items_it->second);
        int default_location_id = DefaultLocationForEmpire(client_empire_id);
        turns = part->ProductionTime(client_empire_id, default_location_id);
        cost = part->ProductionCost(client_empire_id, default_location_id);
        cost_units = UserString("ENC_PP");
        general_type = UserString("ENC_SHIP_PART");
        specific_type = UserString(boost::lexical_cast<std::string>(part->Class()));

        std::vector<std::string> unlocked_by_techs = TechsThatUnlockItem(ItemSpec(UIT_SHIP_PART, m_items_it->second));
        if (!unlocked_by_techs.empty()) {
            detailed_description += UserString("ENC_UNLOCKED_BY");
            for (std::vector<std::string>::const_iterator unlock_tech_it = unlocked_by_techs.begin();
                 unlock_tech_it != unlocked_by_techs.end(); ++unlock_tech_it)
            { detailed_description += LinkTaggedText(VarText::TECH_TAG, *unlock_tech_it) + "  "; }
            detailed_description += "\n\n";
        }

        detailed_description += UserString(part->Description()) + "\n\n" + part->StatDescription();

        if (GetOptionsDB().Get<bool>("UI.autogenerated-effects-descriptions")) {
            if (part->Location())
                detailed_description += str(FlexibleFormat(UserString("ENC_LOCATION_CONDITION_STR")) % part->Location()->Description());
            if (!part->Effects().empty())
                detailed_description += str(FlexibleFormat(UserString("ENC_EFFECTS_STR")) % EffectsDescription(part->Effects()));
        }

    } else if (m_items_it->first == "ENC_SHIP_HULL") {
        const HullType* hull = GetHullType(m_items_it->second);
        if (!hull) {
            Logger().errorStream() << "EncyclopediaDetailPanel::Refresh couldn't find hull with name " << m_items_it->second;
            return;
        }

        // Ship Hulls
        name = UserString(m_items_it->second);
        texture = ClientUI::HullTexture(m_items_it->second);
        int default_location_id = DefaultLocationForEmpire(client_empire_id);
        turns = hull->ProductionTime(client_empire_id, default_location_id);
        cost = hull->ProductionCost(client_empire_id, default_location_id);
        cost_units = UserString("ENC_PP");
        general_type = UserString("ENC_SHIP_HULL");

        std::vector<std::string> unlocked_by_techs = TechsThatUnlockItem(ItemSpec(UIT_SHIP_HULL, m_items_it->second));
        if (!unlocked_by_techs.empty()) {
            detailed_description += UserString("ENC_UNLOCKED_BY");
            for (std::vector<std::string>::const_iterator unlock_tech_it = unlocked_by_techs.begin();
                 unlock_tech_it != unlocked_by_techs.end(); ++unlock_tech_it)
            { detailed_description += LinkTaggedText(VarText::TECH_TAG, *unlock_tech_it) + "  "; }
            detailed_description += "\n\n";
        }

        detailed_description += UserString(hull->Description()) + "\n\n" + hull->StatDescription();

        if (GetOptionsDB().Get<bool>("UI.autogenerated-effects-descriptions")) {
            if (hull->Location())
                detailed_description += str(FlexibleFormat(UserString("ENC_LOCATION_CONDITION_STR")) % hull->Location()->Description());
            if (!hull->Effects().empty())
                detailed_description += str(FlexibleFormat(UserString("ENC_EFFECTS_STR")) % EffectsDescription(hull->Effects()));
        }

    } else if (m_items_it->first == "ENC_TECH") {
        const Tech* tech = GetTech(m_items_it->second);
        if (!tech) {
            Logger().errorStream() << "EncyclopediaDetailPanel::Refresh couldn't find tech with name " << m_items_it->second;
            return;
        }

        // Technologies
        name = UserString(m_items_it->second);
        texture = ClientUI::TechIcon(m_items_it->second);
        other_texture = ClientUI::CategoryIcon(tech->Category()); 
        color = ClientUI::CategoryColor(tech->Category());
        turns = tech->ResearchTime(client_empire_id);
        cost = tech->ResearchCost(client_empire_id);
        cost_units = UserString("ENC_RP");
        general_type = str(FlexibleFormat(UserString("ENC_TECH_DETAIL_TYPE_STR"))
            % UserString(tech->Category())
            % UserString(boost::lexical_cast<std::string>(tech->Type()))
            % UserString(tech->ShortDescription()));

        const std::set<std::string>& unlocked_by_techs = tech->Prerequisites();
        if (!unlocked_by_techs.empty()) {
            detailed_description += UserString("ENC_UNLOCKED_BY");
            for (std::set<std::string>::const_iterator it = unlocked_by_techs.begin();
                 it != unlocked_by_techs.end(); ++it)
            { detailed_description += LinkTaggedText(VarText::TECH_TAG, *it) + "  "; }
            detailed_description += "\n\n";
        }

        const std::set<std::string>& unlocked_techs = tech->UnlockedTechs();
        const std::vector<ItemSpec>& unlocked_items = tech->UnlockedItems();
        if (!unlocked_techs.empty() || !unlocked_items.empty())
            detailed_description += UserString("ENC_UNLOCKS");

        if (!unlocked_techs.empty()) {
            for (std::set<std::string>::const_iterator it = unlocked_techs.begin();
                 it != unlocked_techs.end(); ++it)
            {
                std::string link_text = LinkTaggedText(VarText::TECH_TAG, *it);
                detailed_description += str(FlexibleFormat(UserString("ENC_TECH_DETAIL_UNLOCKED_ITEM_STR"))
                    % UserString("UIT_TECH")
                    % link_text);
            }
        }

        if (!unlocked_items.empty()) {
            for (unsigned int i = 0; i < unlocked_items.size(); ++i) {
                const ItemSpec& item = unlocked_items[i];

                std::string TAG;
                switch (item.type) {
                case UIT_BUILDING:      TAG = VarText::BUILDING_TYPE_TAG;       break;
                case UIT_SHIP_PART:     TAG = VarText::SHIP_PART_TAG;           break;
                case UIT_SHIP_HULL:     TAG = VarText::SHIP_HULL_TAG;           break;
                case UIT_SHIP_DESIGN:   TAG = VarText::PREDEFINED_DESIGN_TAG;   break;
                case UIT_TECH:          TAG = VarText::TECH_TAG;                break;
                default: break;
                }

                std::string link_text;
                if (!TAG.empty())
                    link_text = LinkTaggedText(TAG, item.name);
                else
                    link_text = UserString(item.name);

                detailed_description += str(FlexibleFormat(UserString("ENC_TECH_DETAIL_UNLOCKED_ITEM_STR"))
                    % UserString(boost::lexical_cast<std::string>(unlocked_items[i].type))
                    % link_text);
            }
        }

        if (!unlocked_techs.empty() || !unlocked_items.empty())
            detailed_description += "\n";

        detailed_description += UserString(tech->Description());

        if (GetOptionsDB().Get<bool>("UI.autogenerated-effects-descriptions") && !tech->Effects().empty()) {
            detailed_description += str(FlexibleFormat(UserString("ENC_EFFECTS_STR")) % EffectsDescription(tech->Effects()));
        }

    } else if (m_items_it->first == "ENC_BUILDING_TYPE") {
        const BuildingType* building_type = GetBuildingType(m_items_it->second);
        if (!building_type) {
            Logger().errorStream() << "EncyclopediaDetailPanel::Refresh couldn't find building type with name " << m_items_it->second;
            return;
        }

        // Building types
        name = UserString(m_items_it->second);
        texture = ClientUI::BuildingIcon(m_items_it->second);
        int default_location_id = DefaultLocationForEmpire(client_empire_id);
        turns = building_type->ProductionTime(client_empire_id, default_location_id);
        cost = building_type->ProductionCost(client_empire_id, default_location_id);
        cost_units = UserString("ENC_PP");
        general_type = UserString("ENC_BUILDING_TYPE");

        std::vector<std::string> unlocked_by_techs = TechsThatUnlockItem(ItemSpec(UIT_BUILDING, m_items_it->second));
        if (!unlocked_by_techs.empty()) {
            detailed_description += UserString("ENC_UNLOCKED_BY");
            for (std::vector<std::string>::const_iterator unlock_tech_it = unlocked_by_techs.begin();
                 unlock_tech_it != unlocked_by_techs.end(); ++unlock_tech_it)
            { detailed_description += LinkTaggedText(VarText::TECH_TAG, *unlock_tech_it) + "  "; }
            detailed_description += "\n\n";
        }

        detailed_description += UserString(building_type->Description());

        if (GetOptionsDB().Get<bool>("UI.autogenerated-effects-descriptions")) {
            if (building_type->Location())
                detailed_description += str(FlexibleFormat(UserString("ENC_LOCATION_CONDITION_STR")) % building_type->Location()->Description());
            if (!building_type->Effects().empty())
                detailed_description += str(FlexibleFormat(UserString("ENC_EFFECTS_STR")) % EffectsDescription(building_type->Effects()));
        }

    } else if (m_items_it->first == "ENC_SPECIAL") {
        const Special* special = GetSpecial(m_items_it->second);
        if (!special) {
            Logger().errorStream() << "EncyclopediaDetailPanel::Refresh couldn't find special with name " << m_items_it->second;
            return;
        }

        // Specials
        name = UserString(m_items_it->second);
        texture = ClientUI::SpecialIcon(m_items_it->second);
        detailed_description = UserString(special->Description());
        general_type = UserString("ENC_SPECIAL");

        // objects that have special
        std::vector<const UniverseObject*> objects_with_special;
        for (ObjectMap::const_iterator<> obj_it = objects.const_begin(); obj_it != objects.const_end(); ++obj_it)
            if (obj_it->Specials().find(m_items_it->second) != obj_it->Specials().end())
                objects_with_special.push_back(*obj_it);

        if (!objects_with_special.empty()) {
            detailed_description += "\n\n" + UserString("OBJECTS_WITH_SPECIAL");
            for (std::vector<const UniverseObject*>::const_iterator obj_it = objects_with_special.begin();
                 obj_it != objects_with_special.end(); ++obj_it)
            {
                const UniverseObject* obj = *obj_it;

                if (const Ship* ship = universe_object_cast<const Ship*>(obj))
                    detailed_description += LinkTaggedIDText(VarText::SHIP_ID_TAG, ship->ID(), ship->PublicName(client_empire_id)) + "  ";

                else if (const Fleet* fleet = universe_object_cast<const Fleet*>(obj))
                    detailed_description += LinkTaggedIDText(VarText::FLEET_ID_TAG, fleet->ID(), fleet->PublicName(client_empire_id)) + "  ";

                else if (const Planet* planet = universe_object_cast<const Planet*>(obj))
                    detailed_description += LinkTaggedIDText(VarText::PLANET_ID_TAG, planet->ID(), planet->PublicName(client_empire_id)) + "  ";

                else if (const Building* building = universe_object_cast<const Building*>(obj))
                    detailed_description += LinkTaggedIDText(VarText::BUILDING_ID_TAG, building->ID(), building->PublicName(client_empire_id)) + "  ";

                else if (const System* system = universe_object_cast<const System*>(obj))
                    detailed_description += LinkTaggedIDText(VarText::SYSTEM_ID_TAG, system->ID(), system->PublicName(client_empire_id)) + "  ";

                else
                    detailed_description += obj->PublicName(client_empire_id) + "  ";
            }
            detailed_description += "\n";
        }

        if (GetOptionsDB().Get<bool>("UI.autogenerated-effects-descriptions")) {
            if (special->Location()) {
                const std::string& loc_cond = UserString("ENC_LOCATION_CONDITION_STR");
                std::string desc = special->Location()->Description();
                detailed_description += str(FlexibleFormat(loc_cond) % desc);
            }
            if (!special->Effects().empty())
                detailed_description += str(FlexibleFormat(UserString("ENC_EFFECTS_STR")) % EffectsDescription(special->Effects()));
        }

    } else if (m_items_it->first == "ENC_EMPIRE") {
        int empire_id = ALL_EMPIRES;
        try {
            empire_id = boost::lexical_cast<int>(m_items_it->second);
        } catch(...)
        {}
        const Empire* empire = Empires().Lookup(empire_id);
        if (!empire) {
            Logger().errorStream() << "EncyclopediaDetailPanel::Refresh couldn't find empire with id " << m_items_it->second;
            return;
        }

        // Empires
        name = empire->Name();
        const Planet* capital = objects.Object<Planet>(empire->CapitalID());
        if (capital)
            detailed_description += UserString("EMPIRE_CAPITAL") +
                LinkTaggedIDText(VarText::PLANET_ID_TAG, capital->ID(), capital->Name());
        else
            detailed_description += UserString("NO_CAPITAL");

        // Empire meters
        detailed_description += "\n\n" + UserString("EMPIRE_METERS") + "\n";
        for (std::map<std::string, Meter>::const_iterator meter_it = empire->meter_begin();
             meter_it != empire->meter_end(); ++meter_it)
        {
            detailed_description += UserString(meter_it->first) + ": " + DoubleToString(meter_it->second.Initial(), 3, false) + "\n";
        }

        // Planets
        std::vector<const UniverseObject*> empire_planets = objects.FindObjects(OwnedVisitor<Planet>(empire_id));
        if (!empire_planets.empty()) {
            detailed_description += "\n\n" + UserString("OWNED_PLANETS");
            for (std::vector<const UniverseObject*>::const_iterator planet_it = empire_planets.begin();
                 planet_it != empire_planets.end(); ++planet_it)
            {
                const UniverseObject* obj = *planet_it;
                detailed_description += LinkTaggedIDText(VarText::PLANET_ID_TAG, obj->ID(), obj->PublicName(client_empire_id)) + "  ";
            }
        } else {
            detailed_description += "\n\n" + UserString("NO_OWNED_PLANETS_KNOWN");
        }

        // Fleets
        std::vector<const UniverseObject*> empire_fleets = objects.FindObjects(OwnedVisitor<Fleet>(empire_id));
        if (!empire_fleets.empty()) {
            detailed_description += "\n\n" + UserString("OWNED_FLEETS") + "\n";
            for (std::vector<const UniverseObject*>::const_iterator fleet_it = empire_fleets.begin();
                 fleet_it != empire_fleets.end(); ++fleet_it)
            {
                const UniverseObject* obj = *fleet_it;
                std::string fleet_link = LinkTaggedIDText(VarText::FLEET_ID_TAG, obj->ID(), obj->PublicName(client_empire_id));
                std::string system_link;
                if (const System* system = GetSystem(obj->SystemID())) {
                    std::string sys_name = system->ApparentName(client_empire_id);
                    system_link = LinkTaggedIDText(VarText::SYSTEM_ID_TAG, system->ID(), sys_name);
                    detailed_description += str(FlexibleFormat(UserString("OWNED_FLEET_AT_SYSTEM"))
                                            % fleet_link % system_link);
                } else {
                    detailed_description += fleet_link;
                }
                detailed_description += "\n";
            }
        } else {
            detailed_description += "\n\n" + UserString("NO_OWNED_FLEETS_KNOWN");
        }

    } else if (m_items_it->first == "ENC_SPECIES") {
        const Species* species = GetSpecies(m_items_it->second);
        if (!species) {
            Logger().errorStream() << "EncyclopediaDetailPanel::Refresh couldn't find species with name " << m_items_it->second;
            return;
        }

        // Species
        name = UserString(m_items_it->second);
        texture = ClientUI::SpeciesIcon(m_items_it->second);
        general_type = UserString("ENC_SPECIES");
        detailed_description = UserString(species->GameplayDescription());
        // inherent species limitations
        detailed_description += "\n";
        if (species->CanProduceShips())
            detailed_description += UserString("CAN_PRODUCE_SHIPS");
        else
            detailed_description += UserString("CANNOT_PRODUCE_SHIPS");
        detailed_description += "\n";
        if (species->CanColonize())
            detailed_description += UserString("CAN_COLONIZE");
        else
            detailed_description += UserString("CANNNOT_COLONIZE");

        // environmental preferences
        detailed_description += "\n\n";
        const std::map<PlanetType, PlanetEnvironment>& pt_env_map = species->PlanetEnvironments();
        if (!pt_env_map.empty()) {
            detailed_description += UserString("ENVIRONMENTAL_PREFERENCES") + "\n";
            for (std::map<PlanetType, PlanetEnvironment>::const_iterator pt_env_it = pt_env_map.begin();
                 pt_env_it != pt_env_map.end(); ++pt_env_it)
            {
                detailed_description += UserString(boost::lexical_cast<std::string>(pt_env_it->first)) + " : " +
                                        UserString(boost::lexical_cast<std::string>(pt_env_it->second)) + "\n";
            }
        } else {
            detailed_description += "\n";
        }

        if (GetOptionsDB().Get<bool>("UI.autogenerated-effects-descriptions") && !species->Effects().empty()) {
            detailed_description += str(FlexibleFormat(UserString("ENC_EFFECTS_STR")) % EffectsDescription(species->Effects()));
        }

        // Long description
        detailed_description += "\n";
        detailed_description += UserString(species->Description());

        // homeworld
        detailed_description += "\n\n";
        if (species->Homeworlds().empty()) {
            detailed_description += UserString("NO_HOMEWORLD") + "\n";
        } else {
            detailed_description += UserString("HOMEWORLD") + "\n";
            for (std::set<int>::const_iterator hw_it = species->Homeworlds().begin();
                 hw_it != species->Homeworlds().end(); ++hw_it)
            {
                if (const Planet* homeworld = objects.Object<Planet>(*hw_it))
                    detailed_description += LinkTaggedIDText(VarText::PLANET_ID_TAG, *hw_it, homeworld->PublicName(client_empire_id)) + "\n";
                else
                    detailed_description += UserString("UNKNOWN_PLANET") + "\n";
            }
        }

        // occupied planets
        std::vector<const Planet*> planets = objects.FindObjects<Planet>();
        std::vector<const Planet*> species_occupied_planets;
        for (std::vector<const Planet*>::const_iterator planet_it = planets.begin(); planet_it != planets.end(); ++planet_it) {
            const Planet* planet = *planet_it;
            if (planet->SpeciesName() == m_items_it->second)
                species_occupied_planets.push_back(planet);
        }
        if (!species_occupied_planets.empty()) {
            detailed_description += "\n" + UserString("OCCUPIED_PLANETS") + "\n";
            for (std::vector<const Planet*>::const_iterator planet_it = species_occupied_planets.begin();
                 planet_it != species_occupied_planets.end(); ++planet_it)
            {
                const Planet* planet = *planet_it;
                detailed_description += LinkTaggedIDText(VarText::PLANET_ID_TAG, planet->ID(), planet->PublicName(client_empire_id)) + "  ";
            }
            detailed_description += "\n";
        }

    } else if (m_items_it->first == "ENC_FIELD_TYPE") {
        const FieldType* field_type = GetFieldType(m_items_it->second);
        if (!field_type) {
            Logger().errorStream() << "EncyclopediaDetailPanel::Refresh couldn't find fiedl type with name " << m_items_it->second;
            return;
        }

        // Field types
        name = UserString(m_items_it->second);
        texture = ClientUI::FieldTexture(m_items_it->second);
        general_type = UserString("ENC_FIELD_TYPE");

        detailed_description += UserString(field_type->Description());

        if (GetOptionsDB().Get<bool>("UI.autogenerated-effects-descriptions")) {
            if (!field_type->Effects().empty())
                detailed_description += str(FlexibleFormat(UserString("ENC_EFFECTS_STR")) % EffectsDescription(field_type->Effects()));
        }

    } else if (m_items_it->first == "ENC_SHIP_DESIGN") {
        int design_id = boost::lexical_cast<int>(m_items_it->second);
        const ShipDesign* design = GetShipDesign(boost::lexical_cast<int>(m_items_it->second));
        if (!design) {
            Logger().errorStream() << "EncyclopediaDetailPanel::Refresh couldn't find ShipDesign with id " << m_items_it->second;
            return;
        }

        // Ship Designs
        name = design->Name();
        texture = ClientUI::ShipDesignIcon(design_id);
        int default_location_id = DefaultLocationForEmpire(client_empire_id);
        turns = design->ProductionTime(client_empire_id, default_location_id);
        cost = design->ProductionCost(client_empire_id, default_location_id);
        cost_units = UserString("ENC_PP");
        general_type = design->IsMonster() ? UserString("ENC_MONSTER") : UserString("ENC_SHIP_DESIGN");

        std::string hull_link;
        if (!design->Hull().empty())
             hull_link = LinkTaggedText(VarText::SHIP_HULL_TAG, design->Hull());

        std::string parts_list;
        const std::vector<std::string>& parts = design->Parts();
        std::vector<std::string> non_empty_parts;
        for (std::vector<std::string>::const_iterator part_it = parts.begin();
             part_it != parts.end(); ++part_it)
        {
            if (!part_it->empty())
                non_empty_parts.push_back(*part_it);
        }
        if (!non_empty_parts.empty()) {
            for (std::vector<std::string>::const_iterator part_it = non_empty_parts.begin();
                 part_it != non_empty_parts.end(); ++part_it)
            {
                if (part_it != non_empty_parts.begin())
                    parts_list += ", ";
                parts_list += LinkTaggedText(VarText::SHIP_PART_TAG, *part_it);
            }
        }

        Ship* temp = new Ship(client_empire_id, design_id, "", client_empire_id);
        GetUniverse().InsertID(temp, TEMPORARY_OBJECT_ID);
        GetUniverse().UpdateMeterEstimates(TEMPORARY_OBJECT_ID);

        detailed_description = str(FlexibleFormat(UserString("ENC_SHIP_DESIGN_DESCRIPTION_STR"))
            % design->Description()
            % hull_link
            % parts_list
            % static_cast<int>(design->SRWeapons().size())
            % static_cast<int>(design->LRWeapons().size())
            % static_cast<int>(design->FWeapons().size())
            % static_cast<int>(design->PDWeapons().size())
            % temp->CurrentMeterValue(METER_MAX_STRUCTURE)
            % temp->CurrentMeterValue(METER_MAX_SHIELD)
            % temp->CurrentMeterValue(METER_DETECTION)
            % temp->CurrentMeterValue(METER_STEALTH)
            % temp->CurrentMeterValue(METER_BATTLE_SPEED)
            % temp->CurrentMeterValue(METER_STARLANE_SPEED)
            % temp->CurrentMeterValue(METER_MAX_FUEL)
            % design->ColonyCapacity()
            % design->TroopCapacity()
            % design->Attack());

        GetUniverse().Delete(TEMPORARY_OBJECT_ID);

        // ships of this design
        std::vector<const Ship*> all_ships = objects.FindObjects<Ship>();
        std::vector<const Ship*> design_ships;
        for (std::vector<const Ship*>::const_iterator ship_it = all_ships.begin();
             ship_it != all_ships.end(); ++ship_it)
        {
            const Ship* ship = *ship_it;
            if (ship->DesignID() == design_id)
                design_ships.push_back(ship);
        }
        if (!design_ships.empty()) {
            detailed_description += "\n\n" + UserString("SHIPS_OF_DESIGN");
            for (std::vector<const Ship*>::const_iterator ship_it = design_ships.begin();
                 ship_it != design_ships.end(); ++ship_it)
            {
                detailed_description += LinkTaggedIDText(VarText::SHIP_ID_TAG, (*ship_it)->ID(), (*ship_it)->PublicName(client_empire_id)) + "  ";
            }
        } else {
            detailed_description += "\n\n" + UserString("NO_SHIPS_OF_DESIGN");
        }


    } else if (m_items_it->first == INCOMPLETE_DESIGN) {
        boost::shared_ptr<const ShipDesign> incomplete_design = m_incomplete_design.lock();
        if (incomplete_design) {
            // incomplete design.  not yet in game universe; being created on design screen
            name = incomplete_design->Name();

            const std::string& design_icon = incomplete_design->Icon();
            if (design_icon.empty())
                texture = ClientUI::HullIcon(incomplete_design->Hull());
            else
                texture = ClientUI::GetTexture(ClientUI::ArtDir() / design_icon, true);

            int default_location_id = DefaultLocationForEmpire(client_empire_id);
            turns = incomplete_design->ProductionTime(client_empire_id, default_location_id);
            cost = incomplete_design->ProductionCost(client_empire_id, default_location_id);
            cost_units = UserString("ENC_PP");

            std::string hull_link;
            if (!incomplete_design->Hull().empty())
                 hull_link = LinkTaggedText(VarText::SHIP_HULL_TAG, incomplete_design->Hull());

            std::string parts_list;
            const std::vector<std::string>& parts = incomplete_design->Parts();
            std::vector<std::string> non_empty_parts;
            for (std::vector<std::string>::const_iterator part_it = parts.begin();
                 part_it != parts.end(); ++part_it)
            {
                if (!part_it->empty())
                    non_empty_parts.push_back(*part_it);
            }
            if (!non_empty_parts.empty()) {
                for (std::vector<std::string>::const_iterator part_it = non_empty_parts.begin();
                     part_it != non_empty_parts.end(); ++part_it)
                {
                    if (part_it != non_empty_parts.begin())
                        parts_list += ", ";
                    parts_list += LinkTaggedText(VarText::SHIP_PART_TAG, *part_it);
                }
            }

            GetUniverse().InsertShipDesignID(new ShipDesign(*incomplete_design.get()), TEMPORARY_OBJECT_ID);

            Ship* temp = new Ship(client_empire_id, TEMPORARY_OBJECT_ID, "", client_empire_id);
            GetUniverse().InsertID(temp, TEMPORARY_OBJECT_ID);
            GetUniverse().UpdateMeterEstimates(TEMPORARY_OBJECT_ID);

            detailed_description = str(FlexibleFormat(UserString("ENC_SHIP_DESIGN_DESCRIPTION_STR"))
                % incomplete_design->Description()
                % hull_link
                % parts_list
                % static_cast<int>(incomplete_design->SRWeapons().size())
                % static_cast<int>(incomplete_design->LRWeapons().size())
                % static_cast<int>(incomplete_design->FWeapons().size())
                % static_cast<int>(incomplete_design->PDWeapons().size())
                % temp->CurrentMeterValue(METER_MAX_STRUCTURE)
                % temp->CurrentMeterValue(METER_MAX_SHIELD)
                % temp->CurrentMeterValue(METER_DETECTION)
                % temp->CurrentMeterValue(METER_STEALTH)
                % temp->CurrentMeterValue(METER_BATTLE_SPEED)
                % temp->CurrentMeterValue(METER_STARLANE_SPEED)
                % temp->CurrentMeterValue(METER_MAX_FUEL)
                % incomplete_design->ColonyCapacity()
                % incomplete_design->TroopCapacity()
                % incomplete_design->Attack());

            GetUniverse().Delete(TEMPORARY_OBJECT_ID);
            GetUniverse().DeleteShipDesign(TEMPORARY_OBJECT_ID);
        }

        general_type = UserString("ENC_INCOMPETE_SHIP_DESIGN");

    } else if (m_items_it->first == UNIVERSE_OBJECT) {
        int id = boost::lexical_cast<int>(m_items_it->second);

        if (id != INVALID_OBJECT_ID) {
            const UniverseObject* obj = objects.Object(id);
            if (!obj) {
                Logger().errorStream() << "EncyclopediaDetailPanel::Refresh couldn't find UniverseObject with id " << m_items_it->second;
                return;
            }

            detailed_description = obj->Dump();
            name = obj->PublicName(client_empire_id);
            general_type = GeneralTypeOfObject(obj->ObjectType());
            if (general_type.empty()) {
                Logger().errorStream() << "EncyclopediaDetailPanel::Refresh couldn't interpret object: " << obj->Name() << " (" << m_items_it->second << ")";
                return;
            }
        }

    } else if (m_items_it->first == PLANET_SUITABILITY_REPORT) {
        general_type = UserString("SP_PLANET_SUITABILITY");

        int planet_id = boost::lexical_cast<int>(m_items_it->second);
        Planet* planet = GetPlanet(planet_id);

        std::string original_planet_species = planet->SpeciesName();
        int original_owner_id = planet->Owner();
        float orig_initial_target_pop = planet->GetMeter(METER_TARGET_POPULATION)->Initial();
        name = planet->PublicName(planet_id);

        int empire_id = HumanClientApp::GetApp()->EmpireID();
        Empire* empire = HumanClientApp::GetApp()->Empires().Lookup(empire_id);
        if (!empire) {
            return;
        }
        const std::vector<int> pop_center_ids = empire->GetPopulationPool().PopCenterIDs();

        std::set<std::string> species_names;
        std::map<std::string, std::pair<PlanetEnvironment, float> > population_counts;

        // Collect species colonizing/environment hospitality information
        for (std::vector<int>::const_iterator it = pop_center_ids.begin(); it != pop_center_ids.end(); it++) {
            const UniverseObject* obj = objects.Object(*it);
            const PopCenter* pc = dynamic_cast<const PopCenter*>(obj);
            if (!pc)
                continue;

            const std::string& species_name = pc->SpeciesName();
            if (species_name.empty())
                continue;

            const Species* species = GetSpecies(species_name);
            if (!species)
                continue;

            // Exclude species that can't colonize this planet (either by virtue
            // of "can't produce ships" or "cannot colonize" traits) UNLESS they
            // are already here (aka: it's their home planet). Showing them on
            // their own planet allows comparison vs other races, which might
            // be better suited to this planet. 
            if (!species->CanProduceShips() || !species->CanColonize()) {
                if (species_name != planet->SpeciesName())
                    continue;
            }
            species_names.insert(species_name);
        }

        boost::shared_ptr<GG::Font> font = ClientUI::GetFont();
        GG::X max_species_name_column1_width(0);

        for (std::set<std::string>::const_iterator it = species_names.begin();
             it != species_names.end(); it++)
        {
            std::string species_name = *it;

            std::string species_name_column1 = str(FlexibleFormat(UserString("ENC_SPECIES_PLANET_TYPE_SUITABILITY_COLUMN1")) % UserString(species_name)); 
            max_species_name_column1_width = std::max(font->TextExtent(species_name_column1).x, max_species_name_column1_width);

            // Setting the planet's species allows all of it meters to reflect
            // species (and empire) properties, such as environment type
            // preferences and tech.
            // @see also: MapWnd::UpdateMeterEstimates()
            planet->SetSpecies(species_name);
            planet->SetOwner(empire_id);
            planet->GetMeter(METER_TARGET_POPULATION)->Set(0.0, 0.0);
            GetUniverse().UpdateMeterEstimates(planet_id);

            const Species* species = GetSpecies(species_name);
            PlanetEnvironment planet_environment = PE_UNINHABITABLE;
            if (species)
                planet_environment = species->GetPlanetEnvironment(planet->Type());

            double planet_capacity = ((planet_environment == PE_UNINHABITABLE) ? 0 : planet->CurrentMeterValue(METER_TARGET_POPULATION));

            population_counts[species_name].first = planet_environment;
            population_counts[species_name].second = planet_capacity;
        }

        std::multimap<float, std::pair<std::string, PlanetEnvironment> > target_population_species;
        for (std::map<std::string, std::pair<PlanetEnvironment, float> >::const_iterator
             it = population_counts.begin(); it != population_counts.end(); ++it)
        {
            target_population_species.insert(std::make_pair(
                it->second.second, std::make_pair(it->first, it->second.first)));
        }

        bool positive_header_placed = false;
        bool negative_header_placed = false;

        for (std::multimap<float, std::pair<std::string, PlanetEnvironment> >::const_reverse_iterator
             it = target_population_species.rbegin(); it != target_population_species.rend(); it++)
        {
            std::string user_species_name = UserString(it->second.first);
            std::string species_name_column1 = str(FlexibleFormat(UserString("ENC_SPECIES_PLANET_TYPE_SUITABILITY_COLUMN1")) % LinkTaggedText(VarText::SPECIES_TAG, it->second.first));

            while (font->TextExtent(species_name_column1).x < max_species_name_column1_width)
            { species_name_column1 += "\t"; }

            if (it->first > 0) {
                if (!positive_header_placed) {
                    detailed_description += str(FlexibleFormat(UserString("ENC_SUITABILITY_REPORT_POSITIVE_HEADER")) % planet->PublicName(planet_id));                    
                    positive_header_placed = true;
                }

                detailed_description += str(FlexibleFormat(UserString("ENC_SPECIES_PLANET_TYPE_SUITABILITY"))
                    % species_name_column1
                    % UserString(boost::lexical_cast<std::string>(it->second.second))
                    % (GG::RgbaTag(ClientUI::StatIncrColor()) + DoubleToString(it->first, 2, true) + "</rgba>"));

            } else if (it->first <= 0) {
                if (!negative_header_placed) {
                    if (positive_header_placed)
                        detailed_description += "\n\n";

                    detailed_description += str(FlexibleFormat(UserString("ENC_SUITABILITY_REPORT_NEGATIVE_HEADER")) % planet->PublicName(planet_id));                    
                    negative_header_placed = true;
                }

                detailed_description += str(FlexibleFormat(UserString("ENC_SPECIES_PLANET_TYPE_SUITABILITY"))
                    % species_name_column1
                    % UserString(boost::lexical_cast<std::string>(it->second.second))
                    % (GG::RgbaTag(ClientUI::StatDecrColor()) + DoubleToString(it->first, 2, true) + "</rgba>"));
            }

            detailed_description += "\n";
        }

        planet->SetSpecies(original_planet_species);
        planet->SetOwner(original_owner_id);
        planet->GetMeter(METER_TARGET_POPULATION)->Set(orig_initial_target_pop, orig_initial_target_pop);
        GetUniverse().UpdateMeterEstimates(planet_id);

    } else if (m_items_it->first == COMBAT_LOG) {
        int log_id = boost::lexical_cast<int>(m_items_it->second);
        bool available = CombatLogAvailable(log_id);
        if (!available) {
            Logger().errorStream() << "EncyclopediaDetailPanel::Refresh couldn't find combat log with id: " << m_items_it->second;
            return;
        }
        const CombatLog& log = GetCombatLog(log_id);

        name = UserString("ENC_COMBAT_LOG");
        texture = ClientUI::GetTexture(ClientUI::ArtDir() / "/icons/sitrep/combat.png", true);
        general_type = UserString("ENC_COMBAT_LOG");

        const System* system = objects.Object<System>(log.system_id);
        const std::string& sys_name = (system ? system->PublicName(client_empire_id) : UserString("ERROR"));

        detailed_description = str(FlexibleFormat(UserString("ENC_COMBAT_LOG_DESCRIPTION_STR"))
                                   % LinkTaggedIDText(VarText::SYSTEM_ID_TAG, log.system_id, sys_name)
                                   % log.turn) + "\n\n";

        for (std::vector<AttackEvent>::const_iterator it = log.attack_events.begin();
             it != log.attack_events.end(); ++it)
        {
            const UniverseObject* attacker = objects.Object(it->attacker_id);
            std::string attacker_link;
            if (attacker) {
                const std::string& attacker_name = attacker->PublicName(client_empire_id);
                const std::string& attacker_tag = LinkTag(attacker->ObjectType());
                attacker_link = LinkTaggedIDText(attacker_tag, it->attacker_id, attacker_name);
            } else {
                attacker_link = UserString("ENC_COMBAT_UNKNOWN_OBJECT");
            }

            const UniverseObject* target = objects.Object(it->target_id);
            std::string target_link;
            if (target) {
                const std::string& target_name = target->PublicName(client_empire_id);
                const std::string& target_tag = LinkTag(target->ObjectType());
                target_link = LinkTaggedIDText(target_tag, it->target_id, target_name);
            } else {
                target_link = UserString("ENC_COMBAT_UNKNOWN_OBJECT");
            }

            const std::string& template_str = (it->target_destroyed ?
                                               UserString("ENC_COMBAT_ATTACK_DESTROY_STR") :
                                               UserString("ENC_COMBAT_ATTACK_STR"));

            detailed_description += str(FlexibleFormat(template_str)
                                        % attacker_link
                                        % target_link
                                        % it->damage
                                        % it->round) + "\n";
        }
    }

    // Create Icons
    if (texture) {
        m_icon =        new GG::StaticGraphic(GG::X0, GG::Y0, GG::X(10), GG::Y(10), texture,        GG::GRAPHIC_FITGRAPHIC | GG::GRAPHIC_PROPSCALE);
        if (color != GG::CLR_ZERO)
            m_icon->SetColor(color);
    }
    if (other_texture) {
        m_other_icon =  new GG::StaticGraphic(GG::X0, GG::Y0, GG::X(10), GG::Y(10), other_texture,  GG::GRAPHIC_FITGRAPHIC | GG::GRAPHIC_PROPSCALE);
        if (color != GG::CLR_ZERO)
            m_other_icon->SetColor(color);
    }

    if (m_icon) {
        m_icon->Show();
        AttachChild(m_icon);
    }
    if (m_other_icon) {
        m_other_icon->Show();
        AttachChild(m_other_icon);
    }

    // Set Text
    if (!name.empty())
        m_name_text->SetText(name);

    m_summary_text->SetText(str(FlexibleFormat(UserString("ENC_DETAIL_TYPE_STR"))
        % specific_type
        % general_type));

    if (color != GG::CLR_ZERO)
        m_summary_text->SetColor(color);

    if (cost != 0.0 && turns != -1) {
        m_cost_text->SetText(str(FlexibleFormat(UserString("ENC_COST_AND_TURNS_STR"))
            % DoubleToString(cost, 3, false)
            % cost_units
            % turns));
    }

    if (!detailed_description.empty())
        m_description_box->SetText(detailed_description);

    DoLayout();
}

void EncyclopediaDetailPanel::AddItem(const std::string& type, const std::string& name) {
    // if the actual item is not the last one, all aubsequented items are deleted
    if (!m_items.empty()) {
        if (m_items_it->first == type && m_items_it->second == name)
            return;
        std::list<std::pair <std::string, std::string> >::iterator end = m_items.end();
        end--;
        if (m_items_it != end) {
            std::list<std::pair <std::string, std::string> >::iterator i = m_items_it;
            ++i;
            m_items.erase(i, m_items.end());
        }
    }

    m_items.push_back(std::pair<std::string, std::string>(type, name));
    if (m_items.size() == 1)
        m_items_it = m_items.begin();
    else
        ++m_items_it;

    if (m_back_button->Disabled() && m_items.size() > 1) // enable Back button
        m_back_button->Disable(false); 

    if (!m_next_button->Disabled())                      // disable Next button
        m_next_button->Disable(true);

    Refresh();
}

void EncyclopediaDetailPanel::PopItem() {
    if (!m_items.empty()) {
        m_items.pop_back();
        if (m_items_it == m_items.end() && m_items_it != m_items.begin())
            m_items_it--;
        Refresh();
    }
}

void EncyclopediaDetailPanel::ClearItems() {
    m_items.clear();
    m_items_it = m_items.end();
}

void EncyclopediaDetailPanel::SetText(const std::string& text, bool lookup_in_stringtable) {
    if (m_items_it != m_items.end() && text == m_items_it->second)
        return;
    if (text == "ENC_INDEX")
        SetIndex();
    else
        AddItem(TextLinker::ENCYCLOPEDIA_TAG, (text.empty() || !lookup_in_stringtable) ? text : UserString(text));
}

void EncyclopediaDetailPanel::SetPlanet(int planet_id) {
    int current_item_id = INVALID_OBJECT_ID;
    if (m_items_it != m_items.end()) {
        try {
            current_item_id = boost::lexical_cast<int>(m_items_it->second);
        } catch (...) {
        }
    }
    if (planet_id == current_item_id)
        return;

    AddItem(PLANET_SUITABILITY_REPORT, boost::lexical_cast<std::string>(planet_id));
}

void EncyclopediaDetailPanel::SetCombatLog(int log_id) {
    int current_item_id = -1;
    if (m_items_it != m_items.end()) {
        try {
            current_item_id = boost::lexical_cast<int>(m_items_it->second);
        } catch (...) {
        }
    }
    if (log_id == current_item_id)
        return;

    AddItem(COMBAT_LOG, boost::lexical_cast<std::string>(log_id));
}

void EncyclopediaDetailPanel::SetTech(const std::string& tech_name) {
    if (m_items_it != m_items.end() && tech_name == m_items_it->second)
        return;
    AddItem("ENC_TECH", tech_name);
}

void EncyclopediaDetailPanel::SetPartType(const std::string& part_name) {
    if (m_items_it != m_items.end() && part_name == m_items_it->second)
        return;
    AddItem("ENC_SHIP_PART", part_name);
}

void EncyclopediaDetailPanel::SetHullType(const std::string& hull_name) {
    if (m_items_it != m_items.end() && hull_name == m_items_it->second)
        return;
    AddItem("ENC_SHIP_HULL", hull_name);
}

void EncyclopediaDetailPanel::SetBuildingType(const std::string& building_name) {
    if (m_items_it != m_items.end() && building_name == m_items_it->second)
        return;
    AddItem("ENC_BUILDING_TYPE", building_name);
}

void EncyclopediaDetailPanel::SetSpecial(const std::string& special_name) {
    if (m_items_it != m_items.end() && special_name == m_items_it->second)
        return;
    AddItem("ENC_SPECIAL", special_name);
}

void EncyclopediaDetailPanel::SetSpecies(const std::string& species_name) {
    if (m_items_it != m_items.end() && species_name == m_items_it->second)
        return;
    AddItem("ENC_SPECIES", species_name);
}

void EncyclopediaDetailPanel::SetFieldType(const std::string& field_type_name) {
    if (m_items_it != m_items.end() && field_type_name == m_items_it->second)
        return;
    AddItem("ENC_FIELD_TYPE", field_type_name);
}

void EncyclopediaDetailPanel::SetObject(int object_id) {
    int current_item_id = INVALID_OBJECT_ID;
    if (m_items_it != m_items.end()) {
        try {
            current_item_id = boost::lexical_cast<int>(m_items_it->second);
        } catch (...) {
        }
    }
    if (object_id == current_item_id)
        return;
    AddItem(UNIVERSE_OBJECT, boost::lexical_cast<std::string>(object_id));
}

void EncyclopediaDetailPanel::SetObject(const std::string& object_id) {
    if (m_items_it != m_items.end() && object_id == m_items_it->second)
        return;
    AddItem(UNIVERSE_OBJECT, object_id);
}

void EncyclopediaDetailPanel::SetEmpire(int empire_id) {
    int current_item_id = ALL_EMPIRES;
    if (m_items_it != m_items.end()) {
        try {
            current_item_id = boost::lexical_cast<int>(m_items_it->second);
        } catch (...) {
        }
    }
    if (empire_id == current_item_id)
        return;
    AddItem("ENC_EMPIRE", boost::lexical_cast<std::string>(empire_id));
}

void EncyclopediaDetailPanel::SetEmpire(const std::string& empire_id) {
    if (m_items_it != m_items.end() && empire_id == m_items_it->second)
        return;
    AddItem("ENC_EMPIRE", empire_id);
}

void EncyclopediaDetailPanel::SetDesign(int design_id) {
    int current_item_id = ShipDesign::INVALID_DESIGN_ID;
    if (m_items_it != m_items.end()) {
        try {
            current_item_id = boost::lexical_cast<int>(m_items_it->second);
        } catch (...) {
        }
    }
    if (design_id == current_item_id)
        return;
    AddItem("ENC_SHIP_DESIGN", boost::lexical_cast<std::string>(design_id));
}

void EncyclopediaDetailPanel::SetDesign(const std::string& design_id) {
    if (m_items_it != m_items.end() && design_id == m_items_it->second)
        return;
    AddItem("ENC_SHIP_DESIGN", design_id);
}

void EncyclopediaDetailPanel::SetIncompleteDesign(boost::weak_ptr<const ShipDesign> incomplete_design) {
    m_incomplete_design = incomplete_design;

    if (m_items_it == m_items.end() ||
        m_items_it->first != INCOMPLETE_DESIGN) {
        AddItem(INCOMPLETE_DESIGN, EMPTY_STRING);
    } else {
        Refresh();
    }
}

void EncyclopediaDetailPanel::SetIndex()
{ AddItem(TextLinker::ENCYCLOPEDIA_TAG, "ENC_INDEX"); }

void EncyclopediaDetailPanel::SetItem(const Planet* planet)
{ SetPlanet(planet ? planet->ID() : INVALID_OBJECT_ID); }

void EncyclopediaDetailPanel::SetItem(const Tech* tech)
{ SetTech(tech ? tech->Name() : EMPTY_STRING); }

void EncyclopediaDetailPanel::SetItem(const PartType* part)
{ SetPartType(part ? part->Name() : EMPTY_STRING); }

void EncyclopediaDetailPanel::SetItem(const HullType* hull_type)
{ SetHullType(hull_type ? hull_type->Name() : EMPTY_STRING); }

void EncyclopediaDetailPanel::SetItem(const BuildingType* building_type)
{ SetBuildingType(building_type ? building_type->Name() : EMPTY_STRING); }

void EncyclopediaDetailPanel::SetItem(const Special* special)
{ SetSpecial(special ? special->Name() : EMPTY_STRING); }

void EncyclopediaDetailPanel::SetItem(const Species* species)
{ SetSpecies(species ? species->Name() : EMPTY_STRING); }

void EncyclopediaDetailPanel::SetItem(const FieldType* field_type)
{ SetFieldType(field_type ? field_type->Name() : EMPTY_STRING); }

void EncyclopediaDetailPanel::SetItem(const UniverseObject* obj)
{ SetObject(obj ? obj->ID() : INVALID_OBJECT_ID); }

void EncyclopediaDetailPanel::SetItem(const Empire* empire)
{ SetEmpire(empire ? empire->EmpireID() : ALL_EMPIRES); }

void EncyclopediaDetailPanel::SetItem(const ShipDesign* design)
{ SetDesign(design ? design->ID() : ShipDesign::INVALID_DESIGN_ID); }

void EncyclopediaDetailPanel::OnIndex()
{ AddItem(TextLinker::ENCYCLOPEDIA_TAG, "ENC_INDEX"); }

void EncyclopediaDetailPanel::OnBack() {
    if (m_items_it != m_items.begin())
        m_items_it--;

    if (m_items_it == m_items.begin())              // disable Back button, if the beginning is reached
        m_back_button->Disable(true);
    if (m_next_button->Disabled())                  // enable Next button
        m_next_button->Disable(false);

    Refresh();
}

void EncyclopediaDetailPanel::OnNext() {
    std::list<std::pair <std::string, std::string> >::iterator end = m_items.end();
    end--;
    if (m_items_it != end && !m_items.empty())
        m_items_it++;

    if (m_items_it == end)                          // disable Next button, if the end is reached;
        m_next_button->Disable(true);
    if (m_back_button->Disabled())                  // enable Back button
        m_back_button->Disable(false);

    Refresh();
}

void EncyclopediaDetailPanel::CloseClicked()
{ ClosingSignal(); }

Encyclopedia::Encyclopedia() :
    articles()
{
    parse::encyclopedia_articles(GetResourceDir() / "encyclopedia.txt", *this);
    if (GetOptionsDB().Get<bool>("verbose-logging")) {
        Logger().debugStream() << "(Category) Encyclopedia Articles:";
        for (std::map<std::string, std::vector<EncyclopediaArticle> >::const_iterator
             category_it = articles.begin(); category_it != articles.end(); ++category_it)
        {
            const std::string& category = category_it->first;
            const std::vector<EncyclopediaArticle>& article_vec = category_it->second;
            for (std::vector<EncyclopediaArticle>::const_iterator article_it = article_vec.begin();
                 article_it != article_vec.end(); ++article_it)
            { Logger().debugStream() << "(" << UserString(category) << ") : " << UserString(article_it->name); }
        }
    }
}
