#include "Sketch.h"

#include "GenerateCodeDlg.h"
#include "Workspace.h"
#include "SaveLoad.h"

#include <GG/Layout.h>
#include <GG/MultiEdit.h>
#include <GG/Scroll.h>
#include <GG/Slider.h>
#include <GG/Spin.h>
#include <GG/StyleFactory.h>
#include <GG/WndEditor.h>
#include <GG/dialogs/FileDlg.h>
#include <GG/dialogs/ThreeButtonDlg.h>

#include <GG/config.h>

#include <boost/algorithm/string.hpp>
#include <boost/archive/xml_oarchive.hpp>
#include <boost/archive/xml_iarchive.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/filesystem/operations.hpp>
#include <boost/type_traits.hpp>

#include <iostream>
#include <fstream>

enum WndType {
    WNDTYPE_BUTTON,
    WNDTYPE_DROPDOWNLIST,
    WNDTYPE_DYNAMICGRAPHIC,
    WNDTYPE_EDIT,
    WNDTYPE_LISTBOX,
    WNDTYPE_MENUBAR,
    WNDTYPE_MULTIEDIT,
    WNDTYPE_POPUPMENU,
    WNDTYPE_RADIOBUTTONGROUP,
    WNDTYPE_V_SCROLL,
    WNDTYPE_H_SCROLL,
    WNDTYPE_V_SLIDER,
    WNDTYPE_H_SLIDER,
    WNDTYPE_INT_SPIN,
    WNDTYPE_DOUBLE_SPIN,
    WNDTYPE_STATEBUTTON,
    WNDTYPE_STATICGRAPHIC,
    WNDTYPE_TEXTCONTROL,
    WNDTYPE_LAYOUT
};

char* g_wnd_type_names[] = {
    "Button",
    "DropDownList",
    "DynamicGraphic",
    "Edit",
    "ListBox",
    "MenuBar",
    "MultiEdit",
    "PopupMenu",
    "RadioButtonGroup",
    "VerticalScroll",
    "HorizontalScroll",
    "VerticalSlider",
    "HorizontalSlider",
    "IntSpin",
    "DoubleSpin",
    "StateButton",
    "StaticGraphic",
    "TextControl",
    "Layout"
};

char* g_wnd_formal_type_strings[] = {
    "GG::Button",
    "GG::DropDownList",
    "GG::DynamicGraphic",
    "GG::Edit",
    "GG::ListBox",
    "GG::MenuBar",
    "GG::MultiEdit",
    "GG::PopupMenu",
    "GG::RadioButtonGroup",
    "GG::Scroll",
    "GG::Scroll",
    "GG::Slider",
    "GG::Slider",
    "GG::Spin<int>",
    "GG::Spin<double>",
    "GG::StateButton",
    "GG::StaticGraphic",
    "GG::TextControl",
    "GG::Layout"
};


namespace {
    std::string DEFAULT_FONT_NAME = "Vera.ttf";
    int DEFAULT_FONT_SIZE = 12;
    const std::string FILE_EXTENSION = ".sketch";
    bool AcceptAllNameCheck(const std::string&) {return true;}
    GG::Wnd* NewControl(WndType type, int x, int y, int w, int h, boost::shared_ptr<GG::Font> font, boost::shared_ptr<GG::StyleFactory> style)
    {
        GG::Wnd* retval = 0;
        switch (type) {
        case WNDTYPE_BUTTON:
            retval = style->NewButton(x, y, w, h, "Button", font, GG::CLR_GRAY, GG::CLR_BLACK);
            break;
        case WNDTYPE_DROPDOWNLIST: {
            GG::DropDownList* drop_down_list = style->NewDropDownList(x, y, w, h, 150, GG::CLR_GRAY);
            drop_down_list->SetInteriorColor(GG::CLR_ZERO);
            retval = drop_down_list;
            break;
        }
        case WNDTYPE_DYNAMICGRAPHIC:
            throw std::runtime_error("GG::DynamicGraphic creation is unimplemented.");
#if 0 // TODO
            retval = style->NewDynamicGraphic(x, y, w, h, true, x, y, 0, texture, 0, -1);
#endif
            break;
        case WNDTYPE_EDIT:
            retval = style->NewEdit(x, y, w, "", font, GG::CLR_GRAY, GG::CLR_BLACK, GG::CLR_ZERO);
            break;
        case WNDTYPE_LISTBOX: 
            retval = style->NewListBox(x, y, w, h, GG::CLR_GRAY, GG::CLR_ZERO);
            break;
        case WNDTYPE_MENUBAR:
            retval = style->NewMenuBar(x, y, w, font, GG::CLR_WHITE, GG::CLR_BLACK, GG::CLR_SHADOW);
            break;
        case WNDTYPE_MULTIEDIT:
            retval = style->NewMultiEdit(x, y, w, h, "", font, GG::CLR_GRAY, GG::TF_LINEWRAP, GG::CLR_BLACK, GG::CLR_ZERO);
            break;
        case WNDTYPE_POPUPMENU:
            throw std::runtime_error("Attempted to insert a GG::PopupMenu into a GG::Wnd.");
            break;
        case WNDTYPE_RADIOBUTTONGROUP:
            retval = style->NewRadioButtonGroup(x, y, w, h, GG::VERTICAL);
            break;
        case WNDTYPE_V_SCROLL:
            retval = style->NewScroll(x, y, w, h, GG::VERTICAL, GG::CLR_GRAY, GG::CLR_SHADOW);
            break;
        case WNDTYPE_H_SCROLL:
            retval = style->NewScroll(x, y, w, h, GG::HORIZONTAL, GG::CLR_GRAY, GG::CLR_SHADOW);
            break;
        case WNDTYPE_V_SLIDER:
            retval = style->NewSlider(x, y, w, h, 0, 100, GG::VERTICAL, GG::RAISED, GG::CLR_GRAY, 8, 5);
            break;
        case WNDTYPE_H_SLIDER:
            retval = style->NewSlider(x, y, w, h, 0, 100, GG::HORIZONTAL, GG::RAISED, GG::CLR_GRAY, 8, 5);
            break;
        case WNDTYPE_INT_SPIN:
            retval = style->NewIntSpin(x, y, w, 0, 1, -10, 10, true, font, GG::CLR_GRAY, GG::CLR_BLACK, GG::CLR_ZERO);
            break;
        case WNDTYPE_DOUBLE_SPIN:
            retval = style->NewDoubleSpin(x, y, w, 0.0, 1.0, -10.0, 10.0, true, font, GG::CLR_GRAY, GG::CLR_BLACK, GG::CLR_ZERO);
            break;
        case WNDTYPE_STATEBUTTON:
            retval = style->NewStateButton(x, y, w, h, "State Button", font, 0, GG::CLR_GRAY, GG::CLR_BLACK, GG::CLR_ZERO, 
                                        GG::SBSTYLE_3D_XBOX);
            break;
        case WNDTYPE_STATICGRAPHIC:
            throw std::runtime_error("GG::DynamicGraphic creation is unimplemented.");
#if 0 // TODO
            retval = style->NewStaticGraphic(x, y, w, h, texture, 0);
#endif
            break;
        case WNDTYPE_TEXTCONTROL:
            retval = style->NewTextControl(x, y, w, h, "Text", font, GG::CLR_BLACK, 0);
            break;
        case WNDTYPE_LAYOUT:
            retval = new GG::Layout(x, y, w, h, 2, 2);
            break;
        default:
            throw std::runtime_error("Attempted to create an unknown type of GG::Wnd.");
            break;
        }
        return retval;
    }
    class ControlSelector : public GG::Wnd
    {
    public:
        ControlSelector(WndType type, int w, int h, boost::shared_ptr<GG::Font> font, boost::shared_ptr<GG::StyleFactory> style) :
            Wnd(0, 0, 1, 1, GG::CLICKABLE),
            m_control(0),
            m_size(40, 40),
            m_background_color(255, 255, 255, 127)
        {
            switch (type) {
            case WNDTYPE_BUTTON: 
            case WNDTYPE_DROPDOWNLIST:
            case WNDTYPE_EDIT:
            case WNDTYPE_INT_SPIN:
            case WNDTYPE_DOUBLE_SPIN:
            case WNDTYPE_STATEBUTTON:
            case WNDTYPE_TEXTCONTROL:
                m_size.y = 30;
                m_size.x = w - 4;
                break;
            case WNDTYPE_MENUBAR:
                m_size.x = w - 4;
                break;
            case WNDTYPE_V_SCROLL:
            case WNDTYPE_V_SLIDER:
                m_size.y = h - 4;
                m_size.x = 14;
                break;
            case WNDTYPE_H_SCROLL:
            case WNDTYPE_H_SLIDER:
                m_size.x = w - 4;
                m_size.y = 14;
                break;
            default:
                break;
            }
            SetDragDropDataType(g_wnd_type_names[type]);
            if (type == WNDTYPE_RADIOBUTTONGROUP) {
                m_control = NewControl(WNDTYPE_TEXTCONTROL, 0, 0, w, h, font, style);
                m_control->SetText("Radio Grp");
            } else {
                m_control = NewControl(type, 0, 0, w, h, font, style);
            }
            if (type == WNDTYPE_MENUBAR) {
                GG::MenuBar* menu_bar = static_cast<GG::MenuBar*>(m_control);
                menu_bar->AddMenu(GG::MenuItem("Menu", 0, false, false));
            } else if (type == WNDTYPE_EDIT) {
                m_control->SetText("Edit");
            } else if (type == WNDTYPE_MULTIEDIT) {
                GG::MultiEdit* multi_edit = static_cast<GG::MultiEdit*>(m_control);
                multi_edit->SetStyle(GG::MultiEdit::NO_SCROLL);
                multi_edit->SetText("Multi-\nedit");
            } else if (type == WNDTYPE_LAYOUT) {
                GG::Layout* layout = static_cast<GG::Layout*>(m_control);
                layout->RenderOutline(true);
            }
        }
        ~ControlSelector() {delete m_control;}
        virtual void Render()
        {
            GG::Pt ul = UpperLeft(), lr = LowerRight();
            GG::FlatRectangle(ul.x, ul.y, lr.x, lr.y, m_background_color, GG::CLR_ZERO, 0);
            GG::Pt control_ul = ul + GG::Pt((Width() - m_size.x) / 2, (Height() - m_size.y) / 2);
            m_control->SizeMove(control_ul, control_ul + m_size);
            GG::GUI::RenderWindow(m_control);
        }
    private:
        GG::Wnd* m_control;
        GG::Pt   m_size;
        GG::Clr  m_background_color;
    };
}

SketchApp::SketchApp() : 
    SDLGUI(1024, 768, false, "GG_Sketch"),
    m_current_plugin_idx(-1),
    m_total_num_wnds(0)
{
    SetSaveWndFunction(::SaveWnd);
    SetLoadWndFunction(::LoadWnd);
}

int SketchApp::NewWndIndex()
{
    return m_total_num_wnds++;
}

void SketchApp::Enter2DMode()
{
    glPushAttrib(GL_ENABLE_BIT | GL_PIXEL_MODE_BIT | GL_TEXTURE_BIT);
    glDisable(GL_DEPTH_TEST);
    glDisable(GL_LIGHTING);
    glDisable(GL_CULL_FACE);
    glEnable(GL_TEXTURE_2D);

    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    glViewport(0, 0, AppWidth(), AppHeight());

    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();

    // This sets up the world coordinate space with the origin in the upper-left corner and +x and +y directions right
    // and down, respectively.  Note that this call leaves the depth of the viewing volume is only 1 (from 0.0 to 1.0),
    // which is fine for GG's purposes.
    glOrtho(0.0, AppWidth(), AppHeight(), 0.0, 0.0, AppWidth());

    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadIdentity();

    //glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
}

void SketchApp::Exit2DMode()
{
    glMatrixMode(GL_MODELVIEW);
    glPopMatrix();

    glMatrixMode(GL_PROJECTION);
    glPopMatrix();

    glPopAttrib();
}

GG::Wnd* SketchApp::Clone(GG::Wnd* wnd)
{
    GG::Wnd* retval = 0;
    {
        std::ofstream ofs(".clone_file");
        boost::archive::xml_oarchive oa(ofs);
        SaveWnd(wnd, "wnd", oa);
    }
    {
        std::ifstream ifs(".clone_file");
        boost::archive::xml_iarchive ia(ifs);
        LoadWnd(retval, "wnd", ia);
    }
    boost::filesystem::path clone_file(".clone_file", AcceptAllNameCheck);
    if (boost::filesystem::exists(clone_file))
        boost::filesystem::remove(clone_file);
    return retval;
}

void SketchApp::CreateWnd(const std::string& wnd_type_str, const GG::Pt& pt)
{
    WndType type = WNDTYPE_BUTTON;
    while (type <= WNDTYPE_LAYOUT &&
           g_wnd_type_names[type] != wnd_type_str) {
        type = WndType(type + 1);
    }
    if (WNDTYPE_BUTTON <= type && type <= WNDTYPE_LAYOUT)
        InsertWnd(type, pt);
}

SketchApp* SketchApp::GetSketchApp()
{
    return static_cast<SketchApp*>(GetGUI());
}

void SketchApp::GLInit()
{
    double ratio = AppWidth() / (float)(AppHeight());

    glEnable(GL_BLEND);
    glEnable(GL_CULL_FACE);
    glEnable(GL_DEPTH_TEST);
    glClearColor(0, 0, 0, 0);
    glViewport(0, 0, AppWidth(), AppHeight());
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(50.0, ratio, 1.0, 10.0);
    gluLookAt(0.0, 0.0, 5.0,
              0.0, 0.0, 0.0,
              0.0, 1.0, 0.0);
    glMatrixMode(GL_MODELVIEW);
}

void SketchApp::Initialize()
{
    SDL_WM_SetCaption("GG Sketch", "GG Sketch");

    // load plugins, if available
    namespace fs = boost::filesystem;
    GG::PluginManager::InitDynamicLoader();
    const std::string PLUGIN_DIR_NAME = "plugins";
    const std::string PLUGIN_EXTENSION(LTDL_SHLIB_EXT);
    fs::path plugin_dir = fs::initial_path() / PLUGIN_DIR_NAME;
    if (fs::exists(plugin_dir)) {
        GG::PluginManager::AddSearchDirectory(PLUGIN_DIR_NAME);
        fs::directory_iterator end_it;
        for (fs::directory_iterator it(plugin_dir); it != end_it; ++it) {
            if (fs::exists(*it) && it->leaf().find(PLUGIN_EXTENSION) == it->leaf().size() - PLUGIN_EXTENSION.size()) {
                boost::shared_ptr<GG::PluginInterface> plugin = GG::GetPluginManager().GetPlugin(it->leaf());
                if (*plugin)
                    m_plugins.push_back(plugin);
            }
        }
    }

    const int TOOLBAR_HEIGHT = 50;

    boost::shared_ptr<GG::Font> font = GetFont(DEFAULT_FONT_NAME, DEFAULT_FONT_SIZE);
    m_menu_bar = new GG::MenuBar(0, 0, 1, font, GG::CLR_BLACK, GG::CLR_WHITE, GG::CLR_WHITE);
    m_wnd_editor = new GG::WndEditor(AppHeight() - m_menu_bar->Height() - TOOLBAR_HEIGHT, font);
    m_workspace = new Workspace();
    m_dialog = new Dialog(300, 400);
    m_workspace->AttachChild(m_dialog);
    GG::Connect(m_dialog->EditableWndSignal, &GG::WndEditor::SetWnd, m_wnd_editor);
    GG::Connect(m_dialog->DroppedWndSignal, &SketchApp::CreateWnd, this);
    GG::Connect(m_wnd_editor->WndChangedSignal, &Dialog::WndEditedSlot, m_dialog);
    GG::Connect(m_wnd_editor->WndNameChangedSignal, &Dialog::WndRenamedSlot, m_dialog);
    GG::Connect(m_workspace->ClickedSignal, &Dialog::WorkspaceClickedSlot, m_dialog);

    const int NUM_CONTROLS = WNDTYPE_LAYOUT - WNDTYPE_BUTTON + 1;
    GG::Layout* toolbar = new GG::Layout(0, 0, AppWidth(), TOOLBAR_HEIGHT, 1, 1);
    const int CONTROL_WIDTH = AppWidth() / NUM_CONTROLS;
    const GG::PluginInterface& plugin = CurrentPlugin();
    boost::shared_ptr<GG::StyleFactory> style = plugin ? plugin.GetStyleFactory() : GetStyleFactory();
    int slot = 0;
    for (WndType i = WNDTYPE_BUTTON; i <= WNDTYPE_LAYOUT; i = WndType(i + 1)) {
        try {
            GG::Wnd* w = new ControlSelector(i, CONTROL_WIDTH, TOOLBAR_HEIGHT, font, style);
            toolbar->Add(w, 0, slot++);
        } catch (...) {}
    }

    m_layout = new GG::Layout(0, 0, AppWidth(), AppHeight(), 3, 2);
    m_layout->SetMinimumRowHeight(0, m_menu_bar->Height());
    m_layout->SetMinimumRowHeight(2, TOOLBAR_HEIGHT);
    m_layout->SetMinimumColumnWidth(0, m_wnd_editor->Width());
    m_layout->SetRowStretch(1, 1);
    m_layout->SetColumnStretch(1, 1);
    m_layout->Add(m_menu_bar, 0, 0, 1, 2);
    m_layout->Add(m_wnd_editor, 1, 0);
    m_layout->Add(m_workspace, 1, 1);
    m_layout->Add(toolbar, 2, 0, 1, 2);

    GG::MenuItem file_menu;
    file_menu.label = "File";
    file_menu.next_level.push_back(GG::MenuItem("New", 0, false, false, &SketchApp::New, this));
    file_menu.next_level.push_back(GG::MenuItem("Open...", 0, false, false, &SketchApp::Open, this));
    file_menu.next_level.push_back(GG::MenuItem("Save...", 0, false, false, &SketchApp::Save, this));
    file_menu.next_level.push_back(GG::MenuItem("Save as...", 0, false, false, &SketchApp::SaveAs, this));
    file_menu.next_level.push_back(GG::MenuItem("Generate code...", 0, false, false, &SketchApp::Export, this));
    m_menu_bar->AddMenu(file_menu);

    GG::MenuItem plugin_menu = GG::MenuItem();
    plugin_menu.label = "Plugins";
    if (!m_plugins.empty()) {
        plugin_menu.next_level.push_back(GG::MenuItem("No Plugin", -1, false, true, &SketchApp::SetPlugin, this));
        for (unsigned int i = 0; i < m_plugins.size(); ++i) {
            plugin_menu.next_level.push_back(GG::MenuItem(m_plugins[i]->PluginName(), i, false, false, &SketchApp::SetPlugin, this));
        }
    } else {
        plugin_menu.disabled = true;
    }
    m_menu_bar->AddMenu(plugin_menu);

    Register(m_layout);
}

void SketchApp::FinalCleanup()
{}

void SketchApp::New()
{
    delete m_dialog;
    m_dialog = new Dialog(300, 400);
    m_workspace->AttachChild(m_dialog);
    GG::Connect(m_dialog->EditableWndSignal, &GG::WndEditor::SetWnd, m_wnd_editor);
    GG::Connect(m_dialog->DroppedWndSignal, &SketchApp::CreateWnd, this);
    GG::Connect(m_wnd_editor->WndChangedSignal, &Dialog::WndEditedSlot, m_dialog);
    GG::Connect(m_wnd_editor->WndNameChangedSignal, &Dialog::WndRenamedSlot, m_dialog);
    GG::Connect(m_workspace->ClickedSignal, &Dialog::WorkspaceClickedSlot, m_dialog);
    m_open_filename = "";
}

void SketchApp::Open()
{
    std::vector<std::pair<std::string, std::string> > types(1, std::pair<std::string, std::string>("GG Sketch Files", "*" + FILE_EXTENSION));
    GG::FileDlg file_dlg("", "", false, false, GetFont(DEFAULT_FONT_NAME, DEFAULT_FONT_SIZE), GG::CLR_GRAY, GG::CLR_GRAY);
    file_dlg.SetFileFilters(types);
    file_dlg.Run();
    if (!file_dlg.Result().empty()) {
        std::string filename = *file_dlg.Result().begin();
        m_dialog->Load(filename);
        SetFilename(filename, m_open_filename);
    }
}

void SketchApp::Save()
{
    if (m_open_filename == "") {
        std::vector<std::pair<std::string, std::string> > types(1, std::pair<std::string, std::string>("GG Sketch Files", "*" + FILE_EXTENSION));
        GG::FileDlg file_dlg("", "", true, false, GetFont(DEFAULT_FONT_NAME, DEFAULT_FONT_SIZE), GG::CLR_GRAY, GG::CLR_GRAY);
        file_dlg.SetFileFilters(types);
        file_dlg.Run();
        if (!file_dlg.Result().empty()) {
            std::string filename = *file_dlg.Result().begin();
            if (filename.find(FILE_EXTENSION) != filename.size() - FILE_EXTENSION.size())
                filename += FILE_EXTENSION;
            SetFilename(filename, m_open_filename);
        }
    }

    if (m_open_filename != "")
        m_dialog->Save(m_open_filename);
}

void SketchApp::SaveAs()
{
    std::vector<std::pair<std::string, std::string> > types(1, std::pair<std::string, std::string>("GG Sketch Files", "*" + FILE_EXTENSION));
    GG::FileDlg file_dlg("", m_open_filename, true, false, GetFont(DEFAULT_FONT_NAME, DEFAULT_FONT_SIZE), GG::CLR_GRAY, GG::CLR_GRAY);
    file_dlg.SetFileFilters(types);
    file_dlg.Run();
    if (!file_dlg.Result().empty()) {
        SetFilename(*file_dlg.Result().begin(), m_open_filename);
        m_dialog->Save(m_open_filename);
    }
}

void SketchApp::Export()
{
    GenerateCodeDlg dlg(m_open_filename);
    dlg.Run();
    if (dlg.EndedWithOk()) {
        m_dialog->Export(dlg.BaseFilename(), dlg.ExportedFiles(), dlg.HeaderExt(), dlg.ImplExt(), dlg.SketchExt());
    }
}

void SketchApp::SetFilename(const std::string& str, std::string& filename)
{
    filename = str;
    std::string base_of_path = boost::filesystem::initial_path().native_directory_string();
    if (filename.find(base_of_path) == 0) {
        filename = filename.substr(base_of_path.size() + 1); // the +1 eats the slash character
    }
}

void SketchApp::InsertWnd(int type)
{
    InsertWnd(type, GG::Pt());
}

void SketchApp::InsertWnd(int type, const GG::Pt& pt)
{
    const GG::PluginInterface& plugin = CurrentPlugin();

    static int x = 0;
    static int y = 0;
    x += 5;
    y += 5;
    if (50 < x)
        x = 5;
    if (50 < y)
        y = 5;

    int w = 100;
    int h = 100;

    switch (type) {
    case WNDTYPE_BUTTON: 
    case WNDTYPE_DROPDOWNLIST:
    case WNDTYPE_EDIT:
    case WNDTYPE_INT_SPIN:
    case WNDTYPE_DOUBLE_SPIN:
    case WNDTYPE_STATEBUTTON:
    case WNDTYPE_TEXTCONTROL:
        h = 30;
        break;
    case WNDTYPE_V_SCROLL:
    case WNDTYPE_V_SLIDER:
        h = 100;
        w = 14;
        break;
    case WNDTYPE_H_SCROLL:
    case WNDTYPE_H_SLIDER:
        w = 100;
        h = 14;
        break;
    default:
        break;
    }

    boost::shared_ptr<GG::StyleFactory> style = plugin ? plugin.GetStyleFactory() : GetStyleFactory();
    GG::Wnd* wnd = NewControl(WndType(type), x, y, w, h, GetFont(DEFAULT_FONT_NAME, DEFAULT_FONT_SIZE), style);
    std::string name;
    do {
        name = g_wnd_type_names[type] + boost::lexical_cast<std::string>(m_total_num_wnds++);
    } while (m_wnd_names.find(name) != m_wnd_names.end());

    m_wnd_names.insert(name);

    if (pt != GG::Pt())
        wnd->MoveTo(pt - GG::Pt(wnd->Width() / 2, wnd->Height() / 2) - m_dialog->ClientUpperLeft());

    m_dialog->AddWnd(wnd, name, g_wnd_formal_type_strings[type]);
}

void SketchApp::SetPlugin(int plugin_idx)
{
    m_current_plugin_idx = plugin_idx;
    GG::MenuItem& plugin_menu = m_menu_bar->GetMenu("Plugins");
    for (unsigned int i = 0; i < plugin_menu.next_level.size(); ++i) {
        plugin_menu.next_level[i].checked = plugin_menu.next_level[i].item_ID == plugin_idx;
    }
    if (0 <= m_current_plugin_idx && m_current_plugin_idx < static_cast<int>(m_plugins.size()) && *m_plugins[m_current_plugin_idx]) {
        SetSaveLoadFunctions(*m_plugins[m_current_plugin_idx]);
    } else {
        SetSaveWndFunction(::SaveWnd);
        SetLoadWndFunction(::LoadWnd);
    }
}

const GG::PluginInterface& SketchApp::CurrentPlugin()
{
    static GG::PluginInterface null_plugin;
    return (m_current_plugin_idx < 0 || static_cast<int>(m_plugins.size()) <= m_current_plugin_idx) ?
        null_plugin :
        *m_plugins[m_current_plugin_idx];
}

extern "C"
int main(int argc, char* argv[])
{
    SketchApp app;

    try {
        app();
    } catch (const std::invalid_argument& e) {
        std::cout << "main() caught exception(std::invalid_arg): " << e.what();
    } catch (const std::runtime_error& e) {
        std::cout << "main() caught exception(std::runtime_error): " << e.what();
    } catch (const std::exception& e) {
        std::cout << "main() caught exception(std::exception): " << e.what();
    }
    return 0;
}
