/*****
*
* TOra - An Oracle Toolkit for DBA's and developers
* Copyright (C) 2003-2005 Quest Software, Inc
* Portions Copyright (C) 2005 Other Contributors
* 
* 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;  only version 2 of
* the License is valid for this program.
* 
* 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.
*
*      As a special exception, you have permission to link this program
*      with the Oracle Client libraries and distribute executables, as long
*      as you follow the requirements of the GNU GPL in regard to all of the
*      software in the executable aside from Oracle client libraries.
*
*      Specifically you are not permitted to link this program with the
*      Qt/UNIX, Qt/Windows or Qt Non Commercial products of TrollTech.
*      And you are not permitted to distribute binaries compiled against
*      these libraries without written consent from Quest Software, Inc.
*      Observe that this does not disallow linking to the Qt Free Edition.
*
*      You may link this product with any GPL'd Qt library such as Qt/Free
*
* All trademarks belong to their respective owners.
*
*****/

#include "toalert.h"

#include "utils.h"
#include "toconf.h"
#include "tomain.h"
#include "tomemoeditor.h"
#include "toresultview.h"

#ifndef WIN32
#include <unistd.h>
#else
#include <windows.h>
#endif

#include <algorithm>
#include <stdio.h>

#include <qcombobox.h>
#include <qlabel.h>
#include <qlineedit.h>
#include <qmenubar.h>
#include <qpopupmenu.h>
#include <qtoolbar.h>
#include <qtoolbutton.h>
#include <qworkspace.h>

#ifdef TO_KDE
#  include <kmenubar.h>
#endif

#include "toalert.moc"

#include "icons/commit.xpm"
#include "icons/return.xpm"
#include "icons/toalert.xpm"
#include "icons/toworksheet.xpm"
#include "icons/trash.xpm"

class toAlertTool : public toTool
{
protected:
    std::map<toConnection *, QWidget *> Windows;

    virtual const char **pictureXPM(void)
    {
        return const_cast<const char**>(toalert_xpm);
    }
public:
    toAlertTool()
            : toTool(330, "Alert Messenger")
    { }
    virtual const char *menuItem()
    {
        return "Alert Messenger";
    }
    virtual QWidget *toolWindow(QWidget *parent, toConnection &connection)
    {
        std::map<toConnection *, QWidget *>::iterator i = Windows.find(&connection);
        if (i != Windows.end())
        {
            (*i).second->setFocus();
            return NULL;
        }
        else
        {
            QWidget *window = new toAlert(parent, connection);
            Windows[&connection] = window;
            return window;
        }
    }
    void closeWindow(toConnection &connection)
    {
        std::map<toConnection *, QWidget *>::iterator i = Windows.find(&connection);
        if (i != Windows.end())
            Windows.erase(i);
    }
};

static toAlertTool AlertTool;

#define TIMEOUT 1

static toSQL SQLHost("toAlert:Hostname",
                     "SELECT machine\n"
                     "  FROM v$session\n"
                     " WHERE sid=(SELECT MIN(sid)\n"
                     "       FROM v$mystat)",
                     "Get hostname of current connection");

toAlert::toAlert(QWidget *main, toConnection &connection)
        : toToolWidget(AlertTool, "alert.html", main, connection), Connection(connection)
{
    QToolBar *toolbar = toAllocBar(this, tr("Alert Messenger"));

    QString def;
    try
    {
        toQList lst = toQuery::readQuery(connection, SQLHost);
        def += toShift(lst);
        if (!def.isEmpty())
            def += QString::fromLatin1(":");
    }
    catch (...)
    {}
    def += connection.user();

    new QLabel(tr("Registered") + " ", toolbar, TO_KDE_TOOLBAR_WIDGET);
    Registered = new QComboBox(toolbar, TO_KDE_TOOLBAR_WIDGET);
    Registered->insertItem(def);
    Registered->setEditable(true);
    Registered->setDuplicatesEnabled(false);
    Registered->setCurrentItem(0);
    connect(Registered, SIGNAL(activated(int)), this, SLOT(add
            ()));

    AddNames.insert(AddNames.end(), def);

    new QToolButton(QPixmap(const_cast<const char**>(commit_xpm)),
                    tr("Register current"),
                    tr("Register current"),
                    this, SLOT(add
                               ()),
                    toolbar);

    new QToolButton(QPixmap(const_cast<const char**>(trash_xpm)),
                    tr("Remove registered"),
                    tr("Remove registered"),
                    this, SLOT(remove
                               ()),
                    toolbar);

    toolbar->addSeparator();

    new QLabel(tr("Name") + " ", toolbar, TO_KDE_TOOLBAR_WIDGET);
    Name = new QLineEdit(toolbar, TO_KDE_TOOLBAR_WIDGET);
    Name->setText(def);
    connect(Name, SIGNAL(returnPressed()), this, SLOT(send()));
    new QLabel(tr("Message") + " ", toolbar, TO_KDE_TOOLBAR_WIDGET);
    Message = new QLineEdit(toolbar, TO_KDE_TOOLBAR_WIDGET);
    connect(Message, SIGNAL(returnPressed()), this, SLOT(send()));
    new QToolButton(QPixmap(const_cast<const char**>(toworksheet_xpm)),
                    tr("Edit message in memo"),
                    tr("Edit message in memo"),
                    this, SLOT(memo()),
                    toolbar);
    toolbar->setStretchableWidget(Message);
    new QToolButton(QPixmap(const_cast<const char**>(return_xpm)),
                    tr("Send alert"),
                    tr("Send alert"),
                    this, SLOT(send()),
                    toolbar);

    connect(&Timer, SIGNAL(timeout()), this, SLOT(poll()));
    Timer.start(TIMEOUT*1000);

    Alerts = new toListView(this);
    Alerts->addColumn(tr("Time"));
    Alerts->addColumn(tr("Name"));
    Alerts->addColumn(tr("Message"));

    ToolMenu = NULL;
    connect(toMainWidget()->workspace(), SIGNAL(windowActivated(QWidget *)),
            this, SLOT(windowActivated(QWidget *)));

    State = Started;
    try
    {
        toThread *thread = new toThread(new pollTask(*this));
        thread->start();
    }
    catch (...)
    {
        toStatusMessage(tr("Failed to start polling thread, try closing some other tools and restart Alert Messenger"));
    }

    setFocusProxy(Message);
}

void toAlert::windowActivated(QWidget *widget)
{
    if (widget == this)
    {
        if (!ToolMenu)
        {
            ToolMenu = new QPopupMenu(this);
            ToolMenu->insertItem(tr("&Add name"), Registered, SLOT(setFocus()), toKeySequence(tr("Alt+R", "Alert|Add Name")));
            ToolMenu->insertItem(QPixmap(const_cast<const char**>(trash_xpm)), tr("&Remove name"),
                                 this, SLOT(remove
                                            (void)), toKeySequence(tr("Ctrl+Backspace", "Alert|Remove name")));
            ToolMenu->insertSeparator();
            ToolMenu->insertItem(tr("Edit &name"), Name, SLOT(setFocus()), toKeySequence(tr("Alt+N", "Alert|Edit name")));
            ToolMenu->insertItem(tr("Edit &message"), Message, SLOT(setFocus()), toKeySequence(tr("Alt+M", "Alert|Message")));
            ToolMenu->insertItem(QPixmap(const_cast<const char**>(toworksheet_xpm)), tr("&Message in memo..."),
                                 this, SLOT(memo(void)), toKeySequence(tr("Ctrl+M", "Alert|Message in memo")));
            ToolMenu->insertItem(QPixmap(const_cast<const char**>(return_xpm)), tr("&Send alert"),
                                 this, SLOT(send(void)), toKeySequence(tr("Ctrl+Return", "Alert|Send alert")));

            toMainWidget()->menuBar()->insertItem(tr("&Alert"), ToolMenu, -1, toToolMenuIndex());
        }
    }
    else
    {
        delete ToolMenu;
        ToolMenu = NULL;
    }
}

toAlert::~toAlert()
{
    try
    {
        Lock.lock();
        State = Quit;
        while (State != Done)
        {
            Lock.unlock();
            Semaphore.down();
            Lock.lock();
        }
        Lock.unlock();
        AlertTool.closeWindow(connection());
    }
    TOCATCH
}

static toSQL SQLRegister("toAlert:Register",
                         "BEGIN SYS.DBMS_ALERT.REGISTER(:name<char[4000]>); END;",
                         "Register an alert name, must have same binds");
static toSQL SQLRemove("toAlert:Remove",
                       "BEGIN SYS.DBMS_ALERT.REMOVE(:name<char[4000]>); END;",
                       "Remove a registered an alert name, must have same binds");
static toSQL SQLRemoveAll("toAlert:RemoveAll",
                          "BEGIN SYS.DBMS_ALERT.REMOVEALL; END;",
                          "Remove all registered alerts");
static toSQL SQLPoll("toAlert:PollAlert",
                     "BEGIN\n"
                     "  SYS.DBMS_ALERT.WAITANY(:name<char[4000],out>,:msg<char[4000],out>,\n"
                     "                         :stat<in,out>,:tim<char[10],in>);\n"
                     "END;",
                     "Poll for alert, must have same binds");
static toSQL SQLSignal("toAlert:Signal",
                       "BEGIN\n"
                       "  SYS.DBMS_ALERT.SIGNAL(:name<char[4000],in>,:msg<char[4000],in>);\n"
                       "END;",
                       "Signal alert, must have same binds");

void toAlert::pollTask::run(void)
{
    Parent.Lock.lock();
    bool fatal = false;
    while (Parent.State != Quit && !fatal)
    {
        Parent.Lock.unlock();
        try
        {
            {
                toLocker lock (Parent.Lock)
                    ;
                fatal = true;
                {
                    for (std::list<QString>::iterator i = Parent.AddNames.begin();
                            i != Parent.AddNames.end();
                            i++)
                    {
                        Parent.Names.insert(Parent.Names.end(), *i);
                        Parent.Connection.execute(SQLRegister, *i);
                    }
                }
                Parent.AddNames.clear();
                for (std::list<QString>::iterator i = Parent.DelNames.begin();
                        i != Parent.DelNames.end();
                        i++)
                {
                    std::list<QString>::iterator j = std::find(Parent.Names.begin(),
                                                     Parent.Names.end(),
                                                     *i);
                    if (j != Parent.Names.end())
                    {
                        Parent.Names.erase(j);
                        Parent.Connection.execute(SQLRemove, *i);
                    }
                }
                Parent.DelNames.clear();
                fatal = false;
            }

            {
                toLocker lock (Parent.Lock)
                    ;
                std::list<QString>::iterator i = Parent.SendAlerts.begin();
                std::list<QString>::iterator j = Parent.SendMessages.begin();
                while (i != Parent.SendAlerts.end() && j != Parent.SendMessages.end())
                {
                    Parent.Connection.execute(SQLSignal, *i, *j);
                    i++;
                    j++;
                }
                Parent.SendAlerts.clear();
                Parent.SendMessages.clear();
                Parent.Connection.commit();
            }

            Parent.Lock.lock();
            if (Parent.Names.size())
            {
                Parent.Lock.unlock();
                toQuery query(Parent.Connection, SQLPoll, QString::number(TIMEOUT));
                QString name = query.readValue();
                QString msg = query.readValue();
                if (query.readValue().toInt() == 0)
                {
                    toLocker lock (Parent.Lock)
                        ;
                    Parent.NewAlerts.insert(Parent.NewAlerts.end(), name);
                    Parent.NewMessages.insert(Parent.NewMessages.end(), msg);
                }
            }
            else
            {
                Parent.Lock.unlock();
#ifndef WIN32

                sleep(TIMEOUT);
#else

                Sleep(TIMEOUT*1000);
#endif

            }
        }
        catch (const QString &str)
        {
            Parent.Lock.lock();
            Parent.Error.sprintf("Exception in alert polling:\n%s", (const char *)str.latin1());
            fprintf(stderr, "%s\n", (const char *)Parent.Error);
            Parent.Lock.unlock();
        }
        catch (...)
        {
            Parent.Lock.lock();
            Parent.Error.sprintf("Unexpected exception in alert in polling.");
            fprintf(stderr, "%s\n", (const char *)Parent.Error);
            Parent.Lock.unlock();
        }

        Parent.Lock.lock();
    }
    if (Parent.Names.size() > 0)
        try
        {
            Parent.Connection.execute(SQLRemoveAll);
        }
        catch (...)
        {}
    Parent.State = Done;
    Parent.Semaphore.up();
    Parent.Lock.unlock();
}

void toAlert::poll(void)
{
    try
    {
        toLocker lock (Lock)
            ;
        if (Error)
        {
            toStatusMessage(Error);
            Error = QString::null;
        }
        std::list<QString>::iterator i = NewAlerts.begin();
        std::list<QString>::iterator j = NewMessages.begin();
        while (i != NewAlerts.end() && j != NewMessages.end())
        {
            QListViewItem *item = new toResultViewMLine(Alerts, NULL, toNow(connection()));
            item->setText(1, *i);
            item->setText(2, *j);
            i++;
            j++;
        }
        NewAlerts.clear();
        NewMessages.clear();
    }
    TOCATCH
}

void toAlert::send(void)
{
    if (!Name->text().isEmpty() && !Message->text().isEmpty())
    {
        toLocker lock (Lock)
            ;
        SendAlerts.insert(SendAlerts.end(), Name->text());
        SendMessages.insert(SendMessages.end(), Message->text());
        Message->clear();
    }
}

void toAlert::memo(void)
{
    toMemoEditor *memo = new toMemoEditor(this, Message->text(), 0, 0);
    connect(memo, SIGNAL(changeData(int, int, const QString &)),
            this, SLOT(changeMessage(int, int, const QString &)));
}

void toAlert::changeMessage(int, int, const QString &str)
{
    Message->setText(str);
}

void toAlert::remove
    (void)
{
    toLocker lock (Lock)
        ;
    QString name = Registered->currentText();
    if (!name.isEmpty())
    {
        std::list<QString>::iterator i = std::find(AddNames.begin(), AddNames.end(), name);
        if (i != AddNames.end())
            AddNames.erase(i);
        i = std::find(Names.begin(), Names.end(), name);
        if (i != Names.end())
        {
            if (std::find(DelNames.begin(), DelNames.end(), name) == DelNames.end())
                DelNames.insert(DelNames.end(), name);
        }
    }

    if (Registered->count() > 0)
        Registered->removeItem(Registered->currentItem());
    if (Registered->count() > 0)
        Registered->setCurrentItem(0);
}

void toAlert::add
    (void)
{
    Registered->lineEdit()->selectAll();
    QString name = Registered->currentText();
    if (!name.isEmpty())
    {
        std::list<QString>::iterator i = std::find(DelNames.begin(), DelNames.end(), name);
        if (i == DelNames.end())
        {
            i = std::find(Names.begin(), Names.end(), name);
            if (i == Names.end())
            {
                i = std::find(AddNames.begin(), AddNames.end(), name);
                if (i == AddNames.end())
                {
                    AddNames.insert(AddNames.end(), name);
                }
            }
        }
        else
        {
            DelNames.erase(i);
        }
    }

    for (int i = 0;i < Registered->count();i++)
        if (Registered->text(i) == name)
            return ;

    Registered->insertItem(name);
    Name->setText(name);
}
