/*

                          Firewall Builder

                 Copyright (C) 2003 NetCitadel, LLC

  Author:  Vadim Kurland     vadim@fwbuilder.org

  $Id$

  This program is free software which we release under the GNU General Public
  License. You may redistribute and/or modify this program under the terms
  of that 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.

  To get a copy of the GNU General Public License, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

*/


#include "../../config.h"
#include "global.h"
#include "utils.h"
#include "utils_no_qt.h"
#include "platforms.h"

#include "newHostDialog.h"
#include "FWWindow.h"
#include "ObjConflictResolutionDialog.h"
#include "upgradePredicate.h"
#include "events.h"

#include "fwbuilder/Library.h"
#include "fwbuilder/Host.h"
#include "fwbuilder/Resources.h"
#include "fwbuilder/Policy.h"
#include "fwbuilder/BackgroundOp.h"
#include "fwbuilder/IPv4.h"
#include "fwbuilder/IPv6.h"

#include <qlineedit.h>
#include <qtextedit.h>
#include <qcombobox.h>
#include <qpushbutton.h>
#include <qradiobutton.h>
#include <qcheckbox.h>
#include <qlistview.h>
#include <qtextbrowser.h>
#include <qmessagebox.h>
#include <qtimer.h>
#include <qregexp.h>
#include <qapplication.h>
#include <qcursor.h>
#include <qpixmapcache.h>
#include <qfiledialog.h>
#include <qdir.h>

#include <iostream>

// must be the last for win
#include "fwbuilder/snmp.h"

using namespace libfwbuilder;
using namespace std;

#define OBJECT_NAME_PAGE 0
#define SNMP_PAGE        1
#define MANUAL_PAGE      2
#define TEMPLATES_PAGE   3

newHostDialog::newHostDialog() : QDialog()
{
    m_dialog = new Ui::newHostDialog_q;
    m_dialog->setupUi(this);

    setControlWidgets(this, m_dialog->stackedWidget,
                      m_dialog->nextButton,
                      m_dialog->finishButton,
                      m_dialog->backButton,
                      m_dialog->cancelButton,
                      m_dialog->titleLabel);

    nhst=NULL;
    tmpldb = NULL;
    snmpPollCompleted=false;
    q=NULL;
    unloadTemplatesLib = false;
    getInterfacesBusy = false;

    timer = new QTimer(this);
    connect( timer, SIGNAL(timeout()), this, SLOT(monitor()) );
    connect( m_dialog->templaterBrowseButton, SIGNAL(pressed()),this,SLOT(browseTemplate()));
    connect( m_dialog->templateUseStandart, SIGNAL(pressed()),this,SLOT(useStandartTemplate()));
    connect( m_dialog->useTemplate, SIGNAL(released()),this,SLOT(showHideTemplatePanel()));
    m_dialog->templaterFilePath->setText(tempfname.c_str());
    m_dialog->templaterFrame->setVisible(false);

    setNextEnabled( OBJECT_NAME_PAGE, false );

    m_dialog->iface_list->setAllColumnsShowFocus( true );

    m_dialog->obj_name->setFocus();

    showPage(0);
}

void newHostDialog::browseTemplate()
{
    QString fileName = QFileDialog::getOpenFileName(this, tr("FWBuilder template files"), "", tr("FWBuilder template files (*.xml *.fwb)"));
    if (fileName=="")
        return ;
    QDir dir (fileName);
//    if (dir.exists ())
//    {
        m_dialog->templaterFilePath->setText(fileName);
//    }
}

void newHostDialog::useStandartTemplate()
{
    m_dialog->templaterFilePath->setText(tempfname.c_str());
}

void newHostDialog::showHideTemplatePanel()
{
    if (m_dialog->useTemplate->checkState()==Qt::Checked)
    {
            m_dialog->templaterFrame->setVisible(true);
    }
    else
    {
            m_dialog->templaterFrame->setVisible(false);
    }
}


newHostDialog::~newHostDialog()
{
    delete m_dialog;
    if (timer!=NULL) delete timer;
#ifdef HAVE_LIBSNMP
    if (q!=NULL) delete q;
#endif
}

void newHostDialog::nextClicked()
{
    if (nextRelevant( currentPage() ) > -1)
        showPage(nextRelevant( currentPage() ));
}

void newHostDialog::backClicked()
{
    if (previousRelevant( currentPage() ) > -1)
        showPage(previousRelevant( currentPage() ));
}


void newHostDialog::changed()
{
    int p = currentPage();
    if (p==OBJECT_NAME_PAGE)
    {
        setNextEnabled( p, !m_dialog->obj_name->text().isEmpty() );
    }

    if (p==SNMP_PAGE)
    {

        bool f;

#ifdef HAVE_LIBSNMP
        f = m_dialog->use_snmp->isChecked();
#else
        f = false;
        m_dialog->use_snmp->setEnabled( f );
#endif

        m_dialog->snmp_community->setEnabled( f );
        m_dialog->snmpQuery->setEnabled( f );
        m_dialog->snmpProgress->setEnabled( f );
        if (f) m_dialog->snmp_community->setFocus();

        f = m_dialog->use_manual->isChecked() || snmpPollCompleted;
        setNextEnabled( SNMP_PAGE, f );
    }

    if (p==MANUAL_PAGE)
    {
        if (m_dialog->iface_dyn->isChecked() || m_dialog->iface_unnum->isChecked())
        {
            m_dialog->iface_addr->clear();
            m_dialog->iface_addr->setEnabled(false);
            m_dialog->iface_netmask->clear();
            m_dialog->iface_netmask->setEnabled(false);
        } else
        {
            m_dialog->iface_addr->setEnabled(true);
            m_dialog->iface_netmask->setEnabled(true);
        }
    }
}

void  newHostDialog::monitor()
{
    if (logger==NULL || q==NULL) return;

#ifdef HAVE_LIBSNMP

    if( logger->ready() )
    {
        QString str = logger->getLine().c_str();
        m_dialog->snmpProgress->moveCursor( QTextCursor::End );
        m_dialog->snmpProgress->insertPlainText( str );
        return;
    }

    if (q->isRunning()) return;

    timer->stop();

    map<int, InterfaceData>* intf = q->getInterfaces();
    map<int, InterfaceData>::iterator i;
    for(i=intf->begin(); i!=intf->end(); ++i)
    {
        InterfaceData* idata = &(i->second);
        if ( idata->ostatus )
        {
            idata->guessLabel("");

            QString dn;
            if (idata->isDyn)        dn+="dyn";
            if (idata->isUnnumbered) dn+="unn";

            QStringList qsl;
            qsl << idata->name.c_str() << idata->label.c_str();
            // TODO: redesign the way we pass information from the
            // snmp crawler down the line so that multiple ip
            // addresses per interface can be processed.
            if (idata->addr_mask.size())
                qsl << idata->addr_mask.front()->getAddressPtr()->toString().c_str()
                    << idata->addr_mask.front()->getNetmaskPtr()->toString().c_str();
            else
                qsl << "" << "";

            qsl << dn << idata->mac_addr.c_str();
            new QTreeWidgetItem(m_dialog->iface_list, qsl);
        }
    }

    delete q;
    q=NULL;

#endif

    snmpPollCompleted=true;
    setNextEnabled( SNMP_PAGE, true );
}

void newHostDialog::getInterfacesViaSNMP()
{
#ifdef HAVE_LIBSNMP

// need to protect from reentry because getAddrByName processes events
    if (q!=NULL || getInterfacesBusy) return;

    snmpPollCompleted=false;
    m_dialog->iface_list->clear();

    string rcomm=m_dialog->snmp_community->text().toLatin1().constData();

    if ( rcomm.empty() )
    {
        QMessageBox::warning(
            this,"Firewall Builder",
            tr("Missing SNMP community string."),
            "&Continue", QString::null, QString::null, 0, 1 );
        return ;
    }

    getInterfacesBusy = true;

    InetAddr addr;
    QString name=m_dialog->obj_name->text().toLatin1().constData();
    try
    {
        QApplication::setOverrideCursor( QCursor( Qt::WaitCursor) );
        QString a = getAddrByName(name, AF_INET);
        QApplication::restoreOverrideCursor();
        addr = InetAddr(a.toAscii().constData());
    } catch (FWException &ex)
    {
        QMessageBox::warning(
            this,"Firewall Builder",
            tr("Address of %1 could not be obtained via DNS")
            .arg(m_dialog->obj_name->text()),
            "&Continue", QString::null, QString::null, 0, 1 );
        getInterfacesBusy = false;
        return ;
    }

    logger=NULL;
    m_dialog->snmpProgress->clear();

    if (q!=NULL) delete q;
    q=new SNMP_interface_query();
    q->init(addr.toString(),rcomm,SNMP_DEFAULT_RETRIES,SNMP_DEFAULT_TIMEOUT);

    timer->setSingleShot(false);
    timer->start(0);

    try
    {
        logger = q->start_operation();

    } catch(const FWException &ex)
    {
        //do nothing
    }

    getInterfacesBusy = false;

#endif
}

bool newHostDialog::appropriate(const int page) const
{
    int p  = page;

    if (fwbdebug)
    {
        qDebug("newHostDialog::appropriate  p=%d",p);
    }

    switch (p)
    {
    case OBJECT_NAME_PAGE:
    case TEMPLATES_PAGE:
        return true;

    case SNMP_PAGE:
    case MANUAL_PAGE:
        return (!m_dialog->useTemplate->isChecked());
    }
    return true;
}


void newHostDialog::showPage(const int page)
{
    FakeWizard::showPage(page);

    int p = currentPage();

    if (fwbdebug) qDebug("newHostDialog::selected  p=%d",p);

// p is a page number _after_ it changed

    switch (p)
    {
    case SNMP_PAGE:
        changed();  // to properly enable/disable widgets
        break;

    case MANUAL_PAGE:
    {
        m_dialog->iface_name->setFocus();

        setNextEnabled( MANUAL_PAGE, false );
        setFinishEnabled( MANUAL_PAGE, true );
        break;
    }

    case TEMPLATES_PAGE:
    {
        setFinishEnabled( TEMPLATES_PAGE, true );
/* load templates if not loaded */

        if (tmpldb==NULL)
        {

            MessageBoxUpgradePredicate upgrade_predicate(this);

            tmpldb = new FWObjectDatabase();
            tmpldb->setReadOnly( false );
            tmpldb->load( m_dialog->templaterFilePath->text().toAscii().data(),
                          &upgrade_predicate, librespath);
        }
        list<FWObject*> fl;

        FWObjectTypedChildIterator libiter = tmpldb->findByType(Library::TYPENAME);
        for ( ; libiter!=libiter.end(); ++libiter)
            findHosts(*libiter, fl, false);

        QString icn = QString( Resources::global_res->getObjResourceStr(fl.front(), "icon-tree").c_str() );

        m_dialog->templateList->clear();

        int n=0;
        for (list<FWObject*>::iterator m=fl.begin(); m!=fl.end(); m++,n++)
        {
            FWObject *o=*m;

            QPixmap pm;
            if ( ! QPixmapCache::find( icn, pm) )
            {
                pm.load( icn );
                QPixmapCache::insert( icn, pm);
            }

            QListWidgetItem *item = new QListWidgetItem(
                    QIcon(pm), QString(o->getName().c_str()));
            m_dialog->templateList->addItem(item);

            templates[ m_dialog->templateList->item( m_dialog->templateList->count()-1 ) ]=o;
        }
        m_dialog->templateList->setCurrentItem(0);
        m_dialog->templateList->setFocus();
        break;
    }
    }
}

void newHostDialog::templateSelected(QListWidgetItem *itm)
//void newHostDialog::templateSelected(QListWidgetItem *cur)
{
    //QListWidgetItem *itm = cur;
    if (fwbdebug) qDebug("newHostDialog::templateSelected ");

    FWObject *o=templates[itm];
    assert (o!=NULL);

    Host *fw = Host::cast(o);

    m_dialog->templateComment->clear();
    m_dialog->templateComment->append( fw->getComment().c_str() );
    m_dialog->templateComment->moveCursor(QTextCursor::Start);

    bool haveOutside = false;
    bool haveInside  = false;
    bool haveDMZ     = false;
    list<FWObject*> ll = fw->getByType(Interface::TYPENAME);
    for (FWObject::iterator i=ll.begin(); i!=ll.end(); i++)
    {
        Interface *intf = Interface::cast( *i );
        QString     nam = intf->getName().c_str();
        QString     lbl = intf->getLabel().c_str();

        if (lbl=="outside"                ||
            nam.indexOf(QRegExp(".*0$"))!=-1 ||
            nam.indexOf(QRegExp(".*0/0$"))!=-1 )
        {
            haveOutside=true;
            m_dialog->intfOutsideLine->show();
            m_dialog->intfOutsideText->show();
            fillInterfaceData(intf,m_dialog->intfOutsideText);
        }
        if (lbl=="inside"                 ||
            nam.indexOf(QRegExp(".*1$"))!=-1 ||
            nam.indexOf(QRegExp(".*0/1$"))!=-1 )
        {
            haveInside=true;
            m_dialog->intfInsideLine->show();
            m_dialog->intfInsideText->show();
            fillInterfaceData(intf,m_dialog->intfInsideText);
        }
    }

    if (!haveOutside) { m_dialog->intfOutsideLine->hide(); m_dialog->intfOutsideText->hide(); }
    if (!haveInside)  { m_dialog->intfInsideLine->hide();  m_dialog->intfInsideText->hide();  }
    if (!haveDMZ)     { m_dialog->intfDMZLine->hide();     m_dialog->intfDMZText->hide();     }
}

void newHostDialog::fillInterfaceData(Interface *intf, QTextBrowser *qte)
{
    qte->clear();
    QString s;

    s += "<table border='0' cellspacing='0' cellpadding='0'>";

    s += "<tr>";
    s += "<td>";
    s +=  tr("Interface: %1 (%2)")
        .arg(intf->getName().c_str())
        .arg(intf->getLabel().c_str());
    s += "</td>";
    s += "</tr>";

    s += "<tr>";
    s += "<td>";
    if (intf->isDyn()) s +=  tr("Dynamic address");
    else
        if (intf->isUnnumbered()) s +=  tr("Unnumbered interface");
        else
        {
            const InetAddr *addr = intf->getAddressPtr();
            QString addr_str = (addr) ? addr->toString().c_str() : "";
            const InetAddr *netm = intf->getNetmaskPtr();
            QString netm_str = (netm) ? netm->toString().c_str() : "";
            s += QString("%1/%2").arg(addr_str).arg(netm_str);
        }
    s += "</td>";
    s += "</tr>";
    s += "</table>";
    qte->setText(s);
}

bool newHostDialog::validateAddressAndMask(const QString &addr,
                                           const QString &netm)
{
    try
    {
        InetAddr(addr.toLatin1().constData());
    }
    catch (FWException &ex)
    {
        QMessageBox::warning(
            this,"Firewall Builder",
            tr("Illegal address '%1/%2'").arg(addr).arg(netm),
            "&Continue", QString::null, QString::null, 0, 1 );
        return false;
    }
    try
    {
        bool ok = false ;
        int ilen = netm.toInt (&ok);
        if (ok)
        {
            if (ilen < 0 || ilen > 32)
            {
                QMessageBox::warning(
                    this,"Firewall Builder",
                    tr("Illegal address '%1/%2'").arg(addr).arg(netm),
                    "&Continue", QString::null, QString::null, 0, 1 );
                return false;
            }          
        }
        else
        {
            InetAddr(netm.toLatin1().constData());
        }

    }
    catch (FWException &ex)
    {
        QMessageBox::warning(
            this,"Firewall Builder",
            tr("Illegal address '%1/%2'").arg(addr).arg(netm),
            "&Continue", QString::null, QString::null, 0, 1 );
        return false;
    }
    return true;
}


void newHostDialog::addInterface()
{
    QString dn = "";
    if (m_dialog->iface_dyn->isChecked())   dn += "Dyn";
    if (m_dialog->iface_unnum->isChecked()) dn += "Unn";

    QString addr;
    QString netm;

    if (!m_dialog->iface_dyn->isChecked() &&
        !m_dialog->iface_unnum->isChecked())
    {
        addr = m_dialog->iface_addr->text();
        netm = m_dialog->iface_netmask->text();

        if (addr.isEmpty()) 
            addr = QString(InetAddr::getAny().toString().c_str());
        if (netm.isEmpty())
            netm = QString(InetAddr::getAny().toString().c_str());

        if (!validateAddressAndMask(addr, netm)) return;
    }
    QStringList sl;
    sl << m_dialog->iface_name->text()
       << m_dialog->iface_label->text()
       << addr
       << netm
       << dn
       << m_dialog->iface_physaddr->text();

    new QTreeWidgetItem(m_dialog->iface_list, sl);
}


void newHostDialog::selectedInterface(QTreeWidgetItem*cur,QTreeWidgetItem*)
//void newHostDialog::selectedInterface(QTreeWidgetItem *cur)
{
    QTreeWidgetItem *itm = cur;
    if (itm)
    {
        m_dialog->iface_name->setText( itm->text(0) );
        m_dialog->iface_label->setText( itm->text(1) );
        m_dialog->iface_addr->setText( itm->text(2) );
        m_dialog->iface_netmask->setText( itm->text(3) );
        m_dialog->iface_dyn->setChecked( itm->text(4).indexOf("Dyn")!=-1 );
        m_dialog->iface_unnum->setChecked( itm->text(4).indexOf("Unn")!=-1 );
        m_dialog->iface_physaddr->setText( itm->text(5) );
    }
}

void newHostDialog::updateInterface()
{
    QString dn = "";
    if (m_dialog->iface_dyn->isChecked())   dn+="Dyn";
    if (m_dialog->iface_unnum->isChecked()) dn+="Unn";

    QTreeWidgetItem *itm = m_dialog->iface_list->currentItem();
    if (itm==NULL) return;

    QString addr = m_dialog->iface_addr->text();
    QString netm = m_dialog->iface_netmask->text();
    if (!validateAddressAndMask(addr, netm)) return;

    itm->setText( 0 , m_dialog->iface_name->text() );
    itm->setText( 1 , m_dialog->iface_label->text() );
    itm->setText( 2 , addr );
    itm->setText( 3 , netm );
    itm->setText( 4 , dn );
    itm->setText( 5 , m_dialog->iface_physaddr->text() );
}

void newHostDialog::deleteInterface()
{
    QTreeWidgetItem *itm = m_dialog->iface_list->currentItem();
    if (itm==NULL) return;
    m_dialog->iface_list->takeTopLevelItem(
            m_dialog->iface_list->indexOfTopLevelItem(itm) );
}

void newHostDialog::cancelClicked()
{
    QDialog::reject();
}

void newHostDialog::finishClicked()
{
    int p = currentPage();

    if (p==TEMPLATES_PAGE)
    {
        QListWidgetItem *itm = m_dialog->templateList->currentItem();
        FWObject *o=templates[itm];
        assert (o!=NULL);

        FWObject *no = mw->duplicateObject(mw->getCurrentLib(),
                                           o,
                                           m_dialog->obj_name->text(),
                                           false );  // do not ask to autorename
        if (no==NULL)
        {
          QDialog::accept();
          return;
        }
        nhst=Host::cast(no);
    } else
    {
        FWObject *o;
        o=mw->createObject(Host::TYPENAME, m_dialog->obj_name->text() );
        if (o==NULL)
        {
          QDialog::accept();
          return;
        }

        nhst=Host::cast(o);

/* create interfaces */

        for (int i = 0; i < m_dialog->iface_list->topLevelItemCount(); i++)
        {
            QTreeWidgetItem *itm = m_dialog->iface_list->topLevelItem(i);
            QString name    =  itm->text(0);
            QString label   =  itm->text(1);
            QString addr    =  itm->text(2);
            QString netmask =  itm->text(3);
            bool    dyn     =  itm->text(4).indexOf("Dyn")!=-1;
            bool    unnum   =  itm->text(4).indexOf("Unn")!=-1;
            QString physaddr=  itm->text(5);

            Interface *oi = Interface::cast(
                mw->createObject(nhst,Interface::TYPENAME, name)
            );
#ifdef USE_INTERFACE_POLICY
            oi->add(new InterfacePolicy());
#endif
            oi->setLabel( label.toLatin1().constData() );

            if (dyn)   oi->setDyn(true);
            if (unnum) oi->setUnnumbered(true);
            oi->setSecurityLevel(0);
            if (physaddr != "")
            {
                QString addrname=QString("%1:%2:mac")
                    .arg(m_dialog->obj_name->text()).arg(name);
                physAddress * pa =
                    physAddress::cast(mw->createObject(oi,
                                                       physAddress::TYPENAME,
                                                       addrname));
                pa->setPhysAddress(physaddr.toLatin1().constData());
            }
            if (!dyn && !unnum && !addr.isEmpty() && addr!="0.0.0.0")
            {
                if (addr.indexOf(':')!=-1)
                {
                    // ipv6 address
                    QString addrname = QString("%1:%2:ip6")
                        .arg(m_dialog->obj_name->text()).arg(name);
                    IPv6 *oa = IPv6::cast(
                        mw->createObject(oi, IPv6::TYPENAME, addrname)
                    );
                    oa->setAddress(
                        InetAddr(AF_INET6, addr.toLatin1().constData()) );
                    bool ok = false ;
                    int inetmask = netmask.toInt(&ok);
                    if (ok)
                    {
                        oa->setNetmask( InetAddr(AF_INET6, inetmask) );
                    }
                    else
                    {
                        oa->setNetmask(
                            InetAddr(AF_INET6, netmask.toLatin1().constData()));
                    }

                } else
                {
                    // ipv4 address
                    QString addrname = QString("%1:%2:ip")
                        .arg(m_dialog->obj_name->text()).arg(name);
                    IPv4 *oa = IPv4::cast(
                        mw->createObject(oi, IPv4::TYPENAME,addrname)
                    );
                    oa->setAddress( InetAddr(addr.toLatin1().constData()) );
                    bool ok = false ;
                    int inetmask = netmask.toInt(&ok);
                    if (ok)
                    {
                        oa->setNetmask( InetAddr(inetmask) );
                    }
                    else
                    {
                        oa->setNetmask(
                            InetAddr(netmask.toLatin1().constData()));
                    }
                }
            }
        }
        
    }
    if (unloadTemplatesLib)
    {
        delete tmpldb;
        tmpldb = NULL;

        unloadTemplatesLib=false;
    }
    QDialog::accept();
}



