/* 
 * Copyright (C) 2002-2004 Morten Brix Pedersen <morten@wtf.dk>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
 */

#include <algorithm>
#include <functional>
#include <cstdlib>
#include <config.h>
#include <gtkmm/box.h>
#include <gtkmm/messagedialog.h>
#include <gtkmm/actiongroup.h>
#include <gdk/gdkkeysyms.h>
#include <sigc++/retype_return.h>
#include "DCCList.h"
#include "MainWindow.h"
#include "Tab.h"

using std::vector;
using Glib::ustring;

MainWindow* AppWin = 0;

MainWindow::MainWindow(bool autoconnect)
    : Gtk::Window(), _app(this)
{
    AppWin = this;
    set_title("LostIRC");

    int width = _app.options.window_width;
    int height = _app.options.window_height;
    if (width && height)
          set_default_size(width, height);
    else
          set_default_size(600, 400);

    int x = _app.options.window_x;
    int y = _app.options.window_y;

    if (x >= 0 && y >= 0)
          move(x, y);
    
    setupMenus();
    initializeTagTable();
    Gtk::VBox *vbox = manage(new Gtk::VBox());

    vbox->pack_start(*_uimanager->get_widget("/MenuBar"), Gtk::PACK_SHRINK);
    vbox->pack_start(_notebook, Gtk::PACK_EXPAND_WIDGET);
    vbox->pack_start(_statusbar, Gtk::PACK_SHRINK);

    add(*vbox);
    show_all();

    if (_app.options.hidemenu)
          _uimanager->get_widget("/MenuBar")->hide();

    if (_app.options.hidestatusbar)
          _statusbar.hide();

    if (!_app.cfgservers.hasAutoConnects() || !autoconnect) {
        // Construct initial tab
        newServerTab();
        openServerWindow();
    } else {
        // Auto-connect to servers.
        _app.autoConnect();
    }
    _notebook.getCurrent()->getEntry().grab_focus();
}

MainWindow::~MainWindow()
{
    // Save the width and height of the windows
    int width, height;

    get_size(width, height);
    if (width && height) {
        _app.options.window_width = width;
        _app.options.window_height = height;
    }

    int x, y;
    get_window()->get_root_origin(x, y);
    if (x >= 0 && y >= 0) {
        _app.options.window_x = x;
        _app.options.window_y = y;
    }

    AppWin = 0;
}

void MainWindow::displayMessage(const ustring& msg, FE::Destination d, ServerConnection *conn, bool shouldHighlight)
{
    if (d == FE::CURRENT) {
        Tab *tab = _notebook.getCurrent(conn);

        if (tab) {
            tab->getText() << msg;

            if (shouldHighlight)
                  tab->highlightActivity();
        }

    } else if (d == FE::ALL) {
        vector<Tab*> tabs;
        vector<Tab*>::const_iterator i;
        _notebook.findTabs(tabs, conn);

        for (i = tabs.begin(); i != tabs.end(); ++i) {
            (*i)->getText() << msg;

            if (shouldHighlight)
                  (*i)->highlightActivity();
        }
    }
}

void MainWindow::displayMessage(const ustring& msg, ChannelBase& chan, ServerConnection *conn, bool shouldHighlight)
{
    Tab *tab = _notebook.findTab(chan.getName(), conn);

    // if the channel doesn't exist, it's probably a query. (the channel is
    // created on join) - there is also a hack here to ensure that it's not
    // a channel
    char p = chan.getName().at(0);
    if (!tab && !conn->isChannelPrefix(p))
        tab = _notebook.addTab(Tab::QUERY, chan.getName(), conn);

    if (tab) {
        tab->getText() << msg;

        if (shouldHighlight)
              tab->highlightActivity();
    }
}

void MainWindow::join(const ustring& nick, Channel& chan, ServerConnection *conn)
{
    Tab *tab = _notebook.findTab(chan.getName(), conn);
    if (!tab) {
        tab = _notebook.addTab(Tab::CHANNEL, chan.getName(), conn);
        _notebook.updateTitle();
        return;
    }
    tab->insertUser(nick);
}

void MainWindow::part(const ustring& nick, Channel& chan, ServerConnection *conn)
{
    Tab *tab = _notebook.findTab(chan.getName(), conn);
    if (tab) {
        if (nick == conn->Session.nick) {
            // It's us who's parting
            tab->setInActive();
        }
        tab->removeUser(nick);
    }
}

void MainWindow::kick(const ustring& kicker, Channel& chan, const ustring& nick, const ustring& msg, ServerConnection *conn)
{
    Tab *tab = _notebook.findTab(chan.getName(), conn);
    if (nick == conn->Session.nick) {
        // It's us who's been kicked
        tab->setInActive();
    }
    tab->removeUser(nick);
}


void MainWindow::quit(const ustring& nick, vector<ChannelBase*> chans, ServerConnection *conn)
{
    vector<ChannelBase*>::const_iterator i;

    for (i = chans.begin(); i != chans.end(); ++i) {
        if (Tab *tab = _notebook.findTab((*i)->getName(), conn))
            tab->removeUser(nick);
    }
}

void MainWindow::nick(const ustring& nick, const ustring& to, vector<ChannelBase*> chans, ServerConnection *conn)
{
    vector<ChannelBase*>::const_iterator i;

    for (i = chans.begin(); i != chans.end(); ++i) {
        if (Tab *tab = _notebook.findTab((*i)->getName(), conn))
              tab->renameUser(nick, to);
    }
    _notebook.updateStatus();
}

void MainWindow::CUMode(const ustring& nick, Channel& chan, const std::vector<User>& users, ServerConnection *conn)
{
    Tab *tab = _notebook.findTab(chan.getName(), conn);

    std::vector<User>::const_iterator i;
    for (i = users.begin(); i != users.end(); ++i) {
        tab->removeUser(i->nick);
        tab->insertUser(i->nick, i->getMode());
    }
}

void MainWindow::names(Channel& c, ServerConnection *conn)
{
    Tab *tab = _notebook.findTab(c.getName(), conn);

    std::vector<User*> users = c.getUsers();
    std::vector<User*>::const_iterator i;

    for (i = users.begin(); i != users.end(); ++i)
          tab->insertUser((*i)->nick, (*i)->getMode());
}

void MainWindow::highlight(ChannelBase& chan, ServerConnection* conn)
{
    Tab *tab = _notebook.findTab(chan.getName(), conn);

    if (tab)
          tab->highlightNick();
}

void MainWindow::away(bool away, ServerConnection* conn)
{
    _notebook.updateStatus();
    _notebook.updateTitle();
}

void MainWindow::connected(ServerConnection* conn)
{
    _notebook.updateStatus();
    _notebook.updateTitle();

    vector<Tab*> tabs;
    vector<Tab*>::const_iterator i;

    _notebook.findTabs(tabs, conn);

    for (i = tabs.begin(); i != tabs.end(); ++i)
          if ((*i)->isType(Tab::QUERY))
                (*i)->setActive();
}

void MainWindow::disconnected(ServerConnection* conn)
{
    vector<Tab*> tabs;

    _notebook.findTabs(tabs, conn);

    std::for_each(tabs.begin(), tabs.end(), std::mem_fun(&Tab::setInActive));
}

void MainWindow::newTab(ServerConnection *conn)
{
    ustring name = _("server");
    conn->Session.servername = name;
    Tab *tab = 0;
    Tab *currenttab = getNotebook().getCurrent();
    if (currenttab != 0 &&
            !currenttab->getConn()->Session.isConnected &&
            !currenttab->getConn()->Session.isConnecting &&
            currenttab->isType(Tab::SERVER)) {
        tab = currenttab;
        tab->setName(name);
        tab->setConn(conn);
    } else {
        tab = _notebook.addTab(Tab::SERVER, name, conn);
    }

    tab->setType(Tab::SERVER);
}

Tab* MainWindow::newServerTab()
{
    ustring name = _("server");
    ServerConnection *conn = _app.newServer();
    conn->Session.servername = name;
    Tab *tab = _notebook.addTab(Tab::SERVER, name, conn);
    tab->setInActive();
    return tab;
}
void MainWindow::newDCC(DCC *dcc)
{
    DCCList *dcclist = DCCList::Instance();
    dcclist->add(dcc); 
}

void MainWindow::dccStatusChanged(DCC *dcc)
{
    DCCList *dcclist = DCCList::Instance();
    dcclist->statusChange(dcc); 
}

void MainWindow::localeError(bool tried_custom_encoding)
{
    Glib::ustring msg;
    if (!tried_custom_encoding) {
        msg = _("Locale conversion error. An error occured while converting text from UTF-8 to your current locale.\n\nThis is most likely because your locale is set to a value which doesn't support the character(s) converting to.\n\nIf you believe this is a bug, please report it to the application author.");

        char *locale = std::getenv("LANG");
        if (locale != NULL) {
            msg += _("\n\nYour current locale (seems) to be: ");
            msg += locale;
        }
    } else {
        msg = _("Encoding conversion error. An error occured while converting text from UTF-8 to the user-defined encoding.\n\nThis is most likely because the encoding you have chosen doesn't support the character(s) converting to.\n\nIf you believe this is a bug, please report it to the application author.");

        msg += _("\n\nI was trying to convert to: ");
        msg += App->options.encoding;
    }

    msg += _("\n\n(Note: You'll only see this warning once per LostIRC session)");


    Gtk::MessageDialog mdialog(*this, msg, false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK);
    mdialog.run();
}

void MainWindow::openPrefs()
{
    if (_prefswin.get()) {
          _prefswin->present();
    } else {
        std::auto_ptr<Prefs> dialog(new Prefs());

        dialog->show();

        _prefswin = dialog;
    }
}

void MainWindow::openDccWindow()
{
    if (_dccwin.get()) {
          _dccwin->present();
    } else {
        std::auto_ptr<DCCWindow> dialog(new DCCWindow(*this));

        dialog->show();

        _dccwin = dialog;
    }
}

void MainWindow::setupMenus()
{
    _uimanager = Gtk::UIManager::create();
    Glib::RefPtr<Gtk::ActionGroup> group = Gtk::ActionGroup::create();

    group->add(Gtk::Action::create("LostIRCMenu", _("_LostIRC")));
    group->add(Gtk::Action::create("NewServerTab", _("_New Server Tab")),
            Gtk::AccelKey("<control>n"),
            sigc::hide_return(sigc::mem_fun(*this, &MainWindow::newServerTab)));
    group->add(Gtk::Action::create("ClearWindow", _("Clear Window")),
            sigc::mem_fun(_notebook, &MainNotebook::clearWindow));
    group->add(Gtk::Action::create("ClearAllWindows", _("Clear All Windows")),
            sigc::mem_fun(_notebook, &MainNotebook::clearAll));
    group->add(Gtk::Action::create("CloseCurrentTab", _("Close Current Tab")),
            Gtk::AccelKey("<control>w"),
            sigc::mem_fun(*this, &MainWindow::closeCurrentTab));
    group->add(Gtk::Action::create("Quit", Gtk::Stock::QUIT),
            sigc::mem_fun(*this, &Gtk::Window::hide));

    group->add(Gtk::Action::create("ViewMenu", _("_View")));
    group->add(Gtk::Action::create("MenuItem", _("_Menubar")), Gtk::AccelKey("<control>m"), sigc::mem_fun(*this, &MainWindow::hideMenu));
    group->add(Gtk::Action::create("StatusBar", _("Status_bar")), Gtk::AccelKey("<control>b"), sigc::mem_fun(*this, &MainWindow::hideStatusbar));
    group->add(Gtk::Action::create("UserList", _("_User List")), Gtk::AccelKey("<control>l"), sigc::mem_fun(*this, &MainWindow::hideNickList));
    group->add(Gtk::Action::create("ServerList", _("_Server List")), Gtk::AccelKey("<control>s"), sigc::mem_fun(*this, &MainWindow::openServerWindow));
    group->add(Gtk::Action::create("DCCTransfers", _("_DCC Transfers")), Gtk::AccelKey("<control>d"), sigc::mem_fun(*this, &MainWindow::openDccWindow));
    group->add(Gtk::Action::create("Preferences", Gtk::Stock::PREFERENCES), Gtk::AccelKey("<control>p"), sigc::mem_fun(*this, &MainWindow::openPrefs));

    group->add(Gtk::Action::create("HelpMenu", _("_Help")));
    group->add(Gtk::Action::create("Introduction", _("_Introduction")), sigc::mem_fun(*this, &MainWindow::openHelpIntro));
    group->add(Gtk::Action::create("About", _("_About")), sigc::mem_fun(*this, &MainWindow::openAboutWindow));

    _uimanager->insert_action_group(group);
    add_accel_group(_uimanager->get_accel_group());

    try {

        Glib::ustring ui_info = 
                "<ui>"
                "  <menubar name='MenuBar'>"
                "    <menu action='LostIRCMenu'>"
                "      <menuitem action='NewServerTab'/>"
                "      <menuitem action='ClearWindow'/>"
                "      <menuitem action='ClearAllWindows'/>"
                "      <menuitem action='CloseCurrentTab'/>"
                "      <separator/>"
                "      <menuitem action='Quit'/>"
                "    </menu>"
                "    <menu action='ViewMenu'>"
                "      <menuitem action='MenuItem'/>"
                "      <menuitem action='StatusBar'/>"
                "      <separator/>"
                "      <menuitem action='UserList'/>"
                "      <menuitem action='ServerList'/>"
                "      <menuitem action='DCCTransfers'/>"
                "      <menuitem action='Preferences'/>"
                "    </menu>"
                "    <menu action='HelpMenu'>"
                "      <menuitem action='Introduction'/>"
                "      <separator/>"
                "      <menuitem action='About'/>"
                "    </menu>"
                "  </menubar>"
                "</ui>";

        _uimanager->add_ui_from_string(ui_info);

    } catch (const Glib::Error& ex) {
        std::cerr << "Building menus failed: " << ex.what() << std::endl;
    }
}


void MainWindow::hideMenu()
{
    _app.options.hidemenu = !_app.options.hidemenu;
    if (_app.options.hidemenu)
          _uimanager->get_widget("/MenuBar")->hide();
    else
          _uimanager->get_widget("/MenuBar")->show();
}

void MainWindow::hideStatusbar()
{
    _app.options.hidestatusbar = !_app.options.hidestatusbar;
    if (_app.options.hidestatusbar)
          _statusbar.hide();
    else
          _statusbar.show();
}

void MainWindow::hideNickList()
{
    _app.options.hidenicklist = !_app.options.hidenicklist;
    vector<Tab*> tabs;

    _notebook.findTabs(tabs);
    std::for_each(tabs.begin(), tabs.end(), std::mem_fun(&Tab::toggleNickList));
}

void MainWindow::openServerWindow()
{
    if (_serverwin.get()) {
          _serverwin->present();
    } else {
        std::auto_ptr<ServerWindow> dialog(new ServerWindow(*this));

        dialog->show();

        _serverwin = dialog;
    }
}

void MainWindow::openHelpIntro()
{
    if (_helpwin.get()) {
          _helpwin->present();
    } else {
        std::auto_ptr<Gtk::MessageDialog> dialog(new Gtk::MessageDialog(_("LostIRC Quick Introduction\n\nThis help window is a quick guide to get you going with LostIRC.\nMove this window away from the LostIRC window, and use it as a quick reference window until you know the general idea.\n\nYou can connect to a server using:\n    /SERVER <hostname / ip>\n\n...and then join a channel:\n    /JOIN <channel-name>\n\nA list of all commands are available with:\n    /COMMANDS\n\nAnd you should really check out the list of key bindings:\n    /KEYBINDINGS"), false, Gtk::MESSAGE_INFO, Gtk::BUTTONS_CLOSE, false));

        dialog->signal_response().connect(sigc::mem_fun(*this, &MainWindow::hideHelpIntro));
        dialog->show();

        _helpwin = dialog;
    }
}

void MainWindow::openAboutWindow()
{
    if (_aboutwin.get()) {
          _aboutwin->present();
    } else {
        std::auto_ptr<Gtk::MessageDialog> dialog(new Gtk::MessageDialog(_("LostIRC "VERSION), false, Gtk::MESSAGE_INFO, Gtk::BUTTONS_OK, false));

        dialog->signal_response().connect(sigc::mem_fun(*this, &MainWindow::hideAboutWindow));
        dialog->show();

        _aboutwin = dialog;
    }
}

void MainWindow::hideHelpIntro(int response)
{
    _helpwin->hide();
}

void MainWindow::hideAboutWindow(int response)
{
    _aboutwin->hide();
}

void MainWindow::closeCurrentTab()
{
    Tab *tab = _notebook.getCurrent();
    if (tab->isType(Tab::CHANNEL) && tab->getConn()->Session.isConnected && tab->isActive()) {
        // It's a channel, so we need to part it
        tab->getConn()->sendPart(tab->getName(), "");
    } else {
        // Query
        tab->getConn()->removeChannel(tab->getName());
    }
    _notebook.closeCurrent();
}

bool MainWindow::on_key_press_event(GdkEventKey* e)
{
    if (e->state & GDK_CONTROL_MASK) {
        // CTRL key.
        if (e->keyval == GDK_Page_Up) {
            _notebook.prev_page();

        } else if (e->keyval == GDK_Page_Down) {
            _notebook.next_page();
        } else if (e->keyval == GDK_h) {
            _notebook.getCurrent()->getText().scrollToHighlightMark();
        } else if (e->keyval == GDK_End) {
            _notebook.getCurrent()->getText().scrollToBottom();
        } else if (e->keyval == GDK_Home) {
            _notebook.getCurrent()->getText().scrollToTop();
        }

    } else if (e->state & GDK_MOD1_MASK) {
        // ALT key.
        if (e->keyval == GDK_0) {
            _notebook.set_current_page(9);
        } else if (e->keyval == GDK_1) {
            _notebook.set_current_page(0);
        } else if (e->keyval == GDK_2) {
            _notebook.set_current_page(1);
        } else if (e->keyval == GDK_3) {
            _notebook.set_current_page(2);
        } else if (e->keyval == GDK_4) {
            _notebook.set_current_page(3);
        } else if (e->keyval == GDK_5) {
            _notebook.set_current_page(4);
        } else if (e->keyval == GDK_6) {
            _notebook.set_current_page(5);
        } else if (e->keyval == GDK_7) {
            _notebook.set_current_page(6);
        } else if (e->keyval == GDK_8) {
            _notebook.set_current_page(7);
        } else if (e->keyval == GDK_9) {
            _notebook.set_current_page(8);
        } else if (e->keyval == GDK_Left) {
              _notebook.prev_page();
        } else if (e->keyval == GDK_Right) {
              _notebook.next_page();
        }
    } else {
        if (e->keyval == GDK_Page_Up) {
            _notebook.getCurrent()->getText().scrollUpPage();

        } else if (e->keyval == GDK_Page_Down) {
            _notebook.getCurrent()->getText().scrollDownPage();
        }
    }

    Gtk::Window::on_key_press_event(e);
    return false;
}

void MainWindow::addToTable(Glib::ustring name, Glib::RefPtr<Gtk::TextTagTable> table, const Glib::ustring& colorname)
{
    Glib::RefPtr<Gtk::TextTag> tag_fg = Gtk::TextTag::create(Glib::ustring("f")+name);
    tag_fg->property_foreground() = colorname;
    table->add(tag_fg);

    Glib::RefPtr<Gtk::TextTag> tag_bg = Gtk::TextTag::create(Glib::ustring("b")+name);
    tag_bg->property_background() = colorname;
    table->add(tag_bg);
}

void MainWindow::initializeTagTable()
{
    _tag_table1 = Gtk::TextTagTable::create();
    _tag_table2 = Gtk::TextTagTable::create();

    addToTable("0", _tag_table1, App->colors1.color0);
    addToTable("1", _tag_table1, App->colors1.color1);
    addToTable("2", _tag_table1, App->colors1.color2);
    addToTable("3", _tag_table1, App->colors1.color3);
    addToTable("4", _tag_table1, App->colors1.color4);
    addToTable("5", _tag_table1, App->colors1.color5);
    addToTable("6", _tag_table1, App->colors1.color6);
    addToTable("7", _tag_table1, App->colors1.color7);
    addToTable("8", _tag_table1, App->colors1.color8);
    addToTable("9", _tag_table1, App->colors1.color9);
    addToTable("10", _tag_table1, App->colors1.color10);
    addToTable("11", _tag_table1, App->colors1.color11);
    addToTable("12", _tag_table1, App->colors1.color12);
    addToTable("13", _tag_table1, App->colors1.color13);
    addToTable("14", _tag_table1, App->colors1.color14);
    addToTable("15", _tag_table1, App->colors1.color15);
    addToTable("16", _tag_table1, App->colors1.color16);
    addToTable("17", _tag_table1, App->colors1.color17);
    addToTable("18", _tag_table1, App->colors1.color18);
    addToTable("19", _tag_table1, App->colors1.color19);

    addToTable("0", _tag_table2, App->colors2.color0);
    addToTable("1", _tag_table2, App->colors2.color1);
    addToTable("2", _tag_table2, App->colors2.color2);
    addToTable("3", _tag_table2, App->colors2.color3);
    addToTable("4", _tag_table2, App->colors2.color4);
    addToTable("5", _tag_table2, App->colors2.color5);
    addToTable("6", _tag_table2, App->colors2.color6);
    addToTable("7", _tag_table2, App->colors2.color7);
    addToTable("8", _tag_table2, App->colors2.color8);
    addToTable("9", _tag_table2, App->colors2.color9);
    addToTable("10", _tag_table2, App->colors2.color10);
    addToTable("11", _tag_table2, App->colors2.color11);
    addToTable("12", _tag_table2, App->colors2.color12);
    addToTable("13", _tag_table2, App->colors2.color13);
    addToTable("14", _tag_table2, App->colors2.color14);
    addToTable("15", _tag_table2, App->colors2.color15);
    addToTable("16", _tag_table2, App->colors2.color16);
    addToTable("17", _tag_table2, App->colors2.color17);
    addToTable("18", _tag_table2, App->colors2.color18);
    addToTable("19", _tag_table2, App->colors2.color19);

    // Create a underlined-tag.
    Glib::RefPtr<Gtk::TextTag> underlinetag1 = Gtk::TextTag::create("U");
    underlinetag1->property_underline() = Pango::UNDERLINE_SINGLE;
    underlinetag1->property_foreground() = App->colors1.color0;
    _tag_table1->add(underlinetag1);

    Glib::RefPtr<Gtk::TextTag> underlinetag2 = Gtk::TextTag::create("U");
    underlinetag2->property_underline() = Pango::UNDERLINE_SINGLE;
    underlinetag2->property_foreground() = App->colors1.color0;
    _tag_table2->add(underlinetag2);

    // Create a bold-tag
    Glib::RefPtr<Gtk::TextTag> boldtag1 = Gtk::TextTag::create("B");
    boldtag1->property_weight() = Pango::WEIGHT_BOLD;
    _tag_table1->add(boldtag1);

    Glib::RefPtr<Gtk::TextTag> boldtag2 = Gtk::TextTag::create("B");
    boldtag2->property_weight() = Pango::WEIGHT_BOLD;
    _tag_table2->add(boldtag2);

    if (_app.options.colorscheme == 1) {
        background_color = _app.colors1.bgcolor;
        _current_tag_table = _tag_table1;
    } else {
        background_color = _app.colors2.bgcolor;
        _current_tag_table = _tag_table2;
    }

}


syntax highlighted by Code2HTML, v. 0.9.1