/*****************************************************************************
 * Copyright (C) 2010 S.R.Haque <srhaque@theiet.org>                         *
 * Christoph Thielecke <crissi99@gmx.de> (contains code from kvpnc.cpp)      *
 *                                                                           *
 * 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 package 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 package; if not, write to the Free Software               *
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA *
 *****************************************************************************/

#include <QtCore/QProcess>
#include <kmessagebox.h>
#include <klocale.h>
#include <kmessagebox.h>
#include <kstandarddirs.h>
#include <unistd.h>
#include "VtunHandler.h"
#include "enterpassworddialog.h"
#include "kvpnc.h"
#include "kvpncconfig.h"
#include "utils.h"

VtunHandler::VtunHandler(
    KVpnc *ui,
    KVpncConfig *config):
        HandlerBase(ui, config, "Vtun", "vtun", false),
        VtundProcess(0)
{
}

bool VtunHandler::vpnStart(bool authenticationRetry)
{
    unsigned errorLine = 0;

    if (!errorLine)
    {
        //setGuiStatus(connecting);
        tmpPath = KStandardDirs::locateLocal("data", "kvpnc/");

        // looking for vtund
        if (!Utils(GlobalConfig).getToolInfo(QString("vtund"))->found) {
            reportError(i18n("Unable to find \"%1\" at \"%2\"." , QString("vtund") , GlobalConfig->pathToVtund));
            errorLine = __LINE__;
        }
    }

    if (!errorLine)
    {
        if (!enableTunnelSupport()) {
            errorLine = __LINE__;
        }
    }

//    GlobalConfig->TmpGatewayIP = Utils( GlobalConfig, parent ).resolveName(GlobalConfig->currentProfile->getGateway());
//    if (GlobalConfig->TmpGatewayIP=="0.0.0.0" || GlobalConfig->TmpGatewayIP.isEmpty())
//    {
//     KMessageBox::error( this, i18n( "Host \"%1\" could not be resolved." ,GlobalConfig->currentProfile->getGateway()) );
//     logMessage(i18n( "Host \"%1\" could not be resolved." ,GlobalConfig->currentProfile->getGateway()),KVpncEnum::error);
//     ok =false;
//    }
//    else
//    {
//      debugMessage(0,  i18n( "Gateway hostname (%1) resolved to \"%2\"." ,GlobalConfig->currentProfile->getGateway(),GlobalConfig->TmpGatewayIP) );
//    }

    if (!errorLine) {
        if (!getAuthenticationData(authenticationRetry)) {
            errorMessage(i18n("Authentication data dialog cancelled."));
            errorLine = __LINE__;
        }
    }

    if (!errorLine)
    {
        // here we go
        if (GlobalConfig->currentProfile->getExecuteCmdBeforeConnect())
            executeCommandBeforeConnect();
    }

    if (!errorLine)
    {

        // write vtun.<profile>.conf
        QFile vtun_file(tmpPath + "vtun." + GlobalConfig->currentProfile->getName() + ".conf");
        QTextStream vtun_stream(&vtun_file);
        if (vtun_file.open(QIODevice::WriteOnly)) {
            vtun_stream << "# generated by kvpnc. Do not edit it." << "\n";
            vtun_stream << "# profile: " + GlobalConfig->currentProfile->getName() << "\n\n";
            vtun_stream << "options {" << "\n";

            if (GlobalConfig->currentProfile->getUseLocalPort())
                vtun_stream << "        port " << GlobalConfig->currentProfile->getLocalPort() << ";" << "\n";
            else
                vtun_stream << "        port 5000;" << "\n";
            vtun_stream << "\n";
            vtun_stream << "        # path to programs" << "\n";
            vtun_stream << "        ifconfig " << GlobalConfig->pathToIfconfig << ";" << "\n";
            vtun_stream << "        route " << GlobalConfig->pathToRoute << ";" << "\n";
            vtun_stream << "        ppp " << GlobalConfig->pathToPppd << ";" << "\n";
            vtun_stream << "}" << "\n";
            vtun_stream << "" << "\n";
            //      vtun_stream << "default {" << "\n";
            //      vtun_stream << "}" << "\n";
            vtun_stream << "" << "\n";
            vtun_stream <<  GlobalConfig->currentProfile->getVtunProfile() << " {" << "\n";
            vtun_stream << "" << "\n";
            vtun_stream << " # Password" << "\n";
            vtun_stream << " passwd " << GlobalConfig->TmpPassword << ";" << "\n";
            vtun_stream << "" << "\n";
            //        vtun_stream << " # IP tunnel" << "\n";
            //      vtun_stream << " type  tun;" << "\n";
            //      vtun_stream << "" << "\n";
            vtun_stream << "        up {" << "\n";
            if (GlobalConfig->currentProfile->getUseVirtualIP()) {
                vtun_stream << "                # use virtual ip addresses" << "\n";
                vtun_stream << "                ifconfig \"%% "  << GlobalConfig->currentProfile->getLocalVirtualIP() <<  " pointopoint " << GlobalConfig->currentProfile->getRemoteVirtualIP() << "\";" << "\n";
            } else {
                vtun_stream << "                # IP address will be assigned by the server" << "\n";
                vtun_stream << "                ppp \"noauth noipdefault\";" << "\n";
            }
            //TODO implement pipe

            vtun_stream << "#               route \"add 192.168.10.200 gw 192.168.10.253\";" << "\n";
            vtun_stream << "        };" << "\n";
            vtun_stream << "" << "\n";
            vtun_stream << "        down {" << "\n";
            vtun_stream << "" << "\n";
            vtun_stream << "        };" << "\n";

            vtun_stream << "}" << "\n";

            vtun_file.close();
        } else {
            errorMessage(i18n("Creating %1 has been failed.", vtun_file.fileName()));
            errorLine = __LINE__;
        }
    }

    if (!errorLine) {

        QStringList args;
        args.append("-n");
        args.append("-f");
        args.append(tmpPath + "vtun." + GlobalConfig->currentProfile->getName() + ".conf");    // default
        args.append(GlobalConfig->currentProfile->getVtunProfile());
        args.append(GlobalConfig->currentProfile->getGateway());
        VtundProcess = new HelperProcess(this, GlobalConfig->pathToVtund, args, *env,
                                         SLOT(readFromStdout_vtun()),
                                         SLOT(readFromStderr_vtun()),
                                         0, SLOT(vtun_exited(int, QProcess::ExitStatus)));
        if (!VtundProcess || !VtundProcess->start()) {
            reportError(i18n("Failed to start \"%1\"." , QString("vtund")), QString(), KVpncEnum::info);
            errorLine = __LINE__;
        }
    }

    if (!errorLine) {
        debugMessage(0, i18n("Trying to connect to server \"%1\" with ...\n" , GlobalConfig->currentProfile->getGateway()) );

    } else {
        errorMessage(i18n("Error at line %1.", errorLine));
    }
    return 0 == errorLine;
}


void VtunHandler::vpnStop()
{
    if (VtundProcess) {
        delete VtundProcess;
        VtundProcess = 0;
    }

//     terminateConnection( VtundProcess );
//     VtundProcess = new QProcess(this);
//  QString proc =  GlobalConfig->pathToKillall ;
//      QStringList args;
//     args.apppend( "vtund" );
//
//     connect( VtundProcess, SIGNAL( readyReadStandardOutput() ), this, SLOT( readFromStdout_disconnect() ) );
//     connect( VtundProcess, SIGNAL( readyReadStandardError() ), this, SLOT( readFromStderr_disconnect() ) );
//
//  VtundProcess->setEnvironment(*env);
//  VtundProcess.start(proc,args);
//     if ( !VtundProcess->waitForStarted() ) {
    //      KMessageBox::sorry( this, i18n( "Failed to start \"%1\"." , QString( "killall vtund" )) );
    //     }
    //         else
    //         {
    //          sleep(1);
    //         }
    //         delete VtundProcess;
    //         VtundProcess=0L;
}
bool VtunHandler::getAuthenticationData(bool AuthRetry)
{
    bool askUsernamePass = false;
    bool requestUsername = false;
    bool requestGrouppassword = false;

    if (!GlobalConfig->haveUserData) {

        //  if ( GlobalConfig->currentProfile->getAuthWithUsernameAndPassword() )
        {
            if (GlobalConfig->currentProfile->getUserPassword().isEmpty())
                askUsernamePass = true;
            else
                GlobalConfig->TmpPassword = GlobalConfig->currentProfile->getUserPassword();

            if (AuthRetry) {
                askUsernamePass = true;
                requestUsername = false;
            }
        }
    }

    if (GlobalConfig->currentProfile->getAskUserPasswordOnEachConnect()) {
        debugMessage(4, i18n("User password on each connect forced."));

        askUsernamePass = true;
        requestUsername = true;
    }

    if (askUsernamePass || requestUsername) {
        EnterUsernamePasswordDialog dlg(gui, i18n("Enter Account Data"), requestGrouppassword, requestUsername);


        if (GlobalConfig->currentProfile->getSaveUserPassword())
            dlg.PasswordEdit->setText(GlobalConfig->currentProfile->getUserPassword());

        dlg.UsernameEdit->setText(GlobalConfig->currentProfile->getUserName());
        if (!dlg.UsernameEdit->text().isEmpty())                  //go straight to pw if username present
            dlg.PasswordEdit->setFocus();

        if (dlg.exec()) {
            GlobalConfig->TmpUsername = dlg.UsernameEdit->text();
            GlobalConfig->TmpPassword = dlg.PasswordEdit->text();


            if (GlobalConfig->TmpPassword.isEmpty()) {
                errorMessage(i18n("Password is empty"));
                return false;
            }

            if (requestUsername && GlobalConfig->TmpUsername.isEmpty()) {
                errorMessage(i18n("Username is empty."));
                return false;
            }

            bool needSave = false;

            if (dlg.SaveUsernameCheckBox->isChecked()) {
                GlobalConfig->currentProfile->setDontSaveUsername(false);
                GlobalConfig->currentProfile->setUserName(dlg.UsernameEdit->text());
                needSave = true;
            } else {
                GlobalConfig->TmpUsername = dlg.UsernameEdit->text();
                GlobalConfig->currentProfile->setDontSaveUsername(true);
                needSave = true;
            }


            if (dlg.SavePskCheckBox->isChecked()) {
                GlobalConfig->currentProfile->setSavePsk(true);
                GlobalConfig->currentProfile->setPreSharedKey(GlobalConfig->TmpGroupPassword);
                needSave = true;
            } else {
                GlobalConfig->currentProfile->setSavePsk(false);
                needSave = true;
            }

            if (dlg.SavePasswordCheckBox->isChecked()) {
                GlobalConfig->currentProfile->setSaveUserPassword(true);
                GlobalConfig->currentProfile->setUserPassword(GlobalConfig->TmpPassword);
                needSave = true;
            } else {
                GlobalConfig->currentProfile->setSaveUserPassword(false);
                needSave = true;
            }

            if (needSave)
                GlobalConfig->saveOptions(true, GlobalConfig->currentProfile->getName());

        } else {
            AuthRetry = false;
            debugMessage(0, i18n("Connect canceled because password enter dialog aborted."));
            return false;
        }

    } else {
        if (!GlobalConfig->currentProfile->getSaveUserPassword() && ! GlobalConfig->currentProfile->getUserPassword().isEmpty()) {
            GlobalConfig->TmpPassword =  GlobalConfig->currentProfile->getUserPassword() ;
        }


        if (!GlobalConfig->currentProfile->getDontSaveUsername() && ! GlobalConfig->currentProfile->getUserName().isEmpty()) {                //go straight to pw if username present
            GlobalConfig->TmpUsername = GlobalConfig->currentProfile->getUserName();
        }
    }

    debugMessage(0, i18n("Some passwords which were needed were obtained from a password dialog."));

    AuthRetry = false;
    GlobalConfig->haveUserData = true;
    return true;
}


QString VtunHandler::pingDevice() const
{
    return "";
}


void VtunHandler::readFromStdout_vtun()
{
    unsigned errorLine = 0;
    DisconnectionReason reason = DISCONNECTION_REASON_FATAL_ERROR;

    QStringList msg_list = QString(VtundProcess->readAllStandardOutput()).split('\n');
    for (int i = 0; i < msg_list.size(); ++i) {
        QString msg = msg_list.at(i);
        bool NormalMsg = true;
        bool ErrorMsg = false;
        bool connectSuccess = false;



        /*
        if ( msg.indexOf ( "init_network: Unable to bind socket. Terminating", 0, Qt::CaseInsensitive ) > -1  )
        {

        }*/

        if (msg.indexOf("Connection refused(111)", 0, Qt::CaseInsensitive) > -1) {
            reportError(i18n("Connection refused."));

            errorLine = __LINE__;
            ErrorMsg = true;
        } else if (msg.indexOf(" Exit ", 0, Qt::CaseInsensitive) > -1) {
            errorMessage(i18n("%1 has been exited."));
            errorLine = __LINE__;
            ErrorMsg = true;
        }


        if (errorLine) {
            debugMessage(0, i18n("There is a reason to stop connecting, terminating \"%1\" process." , QString("vtund")) );
            //terminateConnection ( L2tpdProcess );
            setStatusDisconnected(errorLine, reason);
        }
        // success
        if (connectSuccess) {
            setStatusConnected(__LINE__);

            //    //FIXME make it better
            //    PppdDevice = "ppp0";
            //    NetworkInterface iface ( GlobalConfig, parent );
            //    if ( iface.interfaceExist ( PppdDevice ) )
            //    {
            //     TunnelInterfaceIP = iface.getInterfaceIP ( PppdDevice );
            //     logMessage ( i18n ( "Tunnel device: %1\n"  , QString(PppdDevice )), KVpncEnum::debug );
            //     if ( !TunnelInterfaceIP.isEmpty() )
            //      logMessage ( i18n ( "Tunnel interface IP address: %1\n"  , QString( TunnelInterfaceIP )), KVpncEnum::debug );
            // }
        }
        //   // normal
        if (NormalMsg) {
            debugMessage(1, "[vtun] " + msg);
        } else
            //   if ( ErrorMsg )
        {
            errorMessage("[vtun err] " + msg);
        }
    }
}

void VtunHandler::readFromStderr_vtun()
{
    unsigned errorLine = 0;
    DisconnectionReason reason = DISCONNECTION_REASON_FATAL_ERROR;

    QStringList msg_list = QString(VtundProcess->readAllStandardError()).split('\n');
    for (int i = 0; i < msg_list.size(); ++i) {
        QString msg = msg_list.at(i);
        bool NormalMsg = true;
        bool ErrorMsg = false;
        bool connectSuccess = false;

        if (msg.indexOf("Session ", 0, Qt::CaseInsensitive) > -1 && msg.indexOf("opened", 0, Qt::CaseInsensitive) > -1) {
            debugMessage(1, i18n("Session opened."));
        }
        if (msg.indexOf("compression ", 0, Qt::CaseInsensitive) > -1 && msg.indexOf("initialized", 0, Qt::CaseInsensitive) > -1) {
            debugMessage(1, i18n("Compression initialized."));
        }
        if (msg.indexOf("UDP ", 0, Qt::CaseInsensitive) > -1 && msg.indexOf("initialized", 0, Qt::CaseInsensitive) > -1) {
            debugMessage(1, i18n("UDP initialized."));
        }
        if (msg.indexOf("encryption ", 0, Qt::CaseInsensitive) > -1 && msg.indexOf("initialized", 0, Qt::CaseInsensitive) > -1) {
            debugMessage(1, i18n("Encryption initialized."));

            // FIXME only if encryption is activated (always!?)
            connectSuccess = true;
        }
        if (msg.indexOf("Connection refused(111)", 0, Qt::CaseInsensitive) > -1) {
            reportError(i18n("Connection refused."));

            errorLine = __LINE__;
            ErrorMsg = true;
        }
        // //   if ( msg.indexOf(" Exit ",0, Qt::CaseInsensitive) > -1 )
        // //   {
        // //    logMessage(i18n("%1 has been exited." , QString("vtund")),KVpncEnum::error);
        // //    abort=true;
        // //    ErrorMsg=true;
        // //   }
        if (msg.indexOf("Connection denied by ", 0, Qt::CaseInsensitive) > -1) {
            errorMessage(i18n("Connection denied. Password wrong?"));
            errorLine = __LINE__;
            ErrorMsg = true;
        }
        if (msg.indexOf("Connection closed by other side", 0, Qt::CaseInsensitive) > -1) {
            errorMessage(i18n("Connection was closed by the remote host. Please check your settings."));
            errorLine = __LINE__;
            ErrorMsg = true;
        }
        if (msg.indexOf("syntax error", 0, Qt::CaseInsensitive) > -1 || msg.indexOf("No hosts defined", 0, Qt::CaseInsensitive) > -1) {
            reportError(i18n("Syntax error in config detected. Please report that to the KVpnc maintainer."));

            errorLine = __LINE__;
            ErrorMsg = true;
        }
        if (msg.indexOf("allocate pseudo tty", 0, Qt::CaseInsensitive) > -1) {
            reportError(i18n("Cannot allocate pseudo tty.\nPlease check that your kernel has LEGACY PTY=y or recompile patched vtun. You can get the patch for UNIX98 PTS here: %1" , QString(QString("http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=451931"))));

            errorLine = __LINE__;
            ErrorMsg = true;
        }

        //    else if ( GlobalConfig->status != KVpncEnum::connected && msg.indexOf ( "check_control: control, cid =", 0, Qt::CaseInsensitive ) > -1 )
        //    {
        //     NormalMsg=true;
        //     // cid is greather than 0
        //     if ( msg.section ( ',', 1, 1 ).section ( '=', 1, 1 ).trimmed() != "0" )       // check_control: control, cid = 17467, Ns = 2, Nr = 4
        //     {
        //      logMessage ( QString ( "[l2tpd] " + i18n ( "L2TP tunnel to %1 established." , QString( GlobalConfig->currentProfile->getGateway() )) ) , KVpncEnum::success );
        //
        //      abort = false;
        //      connectSuccess = true;
        //     }
        //    }


        // success
        if (connectSuccess) {

            successMessage(i18n("Connection established."));
            setStatusConnected(__LINE__);
            connectSuccess = false;
        }

        //   // normal
        if (NormalMsg) {
            debugMessage(1, "[vtun] " + msg);
        } else
            //   if ( ErrorMsg )
        {
            errorMessage("[vtun err] " + msg);
        }

        if (errorLine) {
            debugMessage(0, i18n("There is a reason to stop connecting, terminating \"%1\" process." , QString("vtund")) );
            setStatusDisconnected(errorLine, reason);
            return ;
        }
        GlobalConfig->appPointer->processEvents();
    }
}




void VtunHandler::vtun_exited(int, QProcess::ExitStatus)
{
    //  logMessage(i18n("%1 has been exited.","vtund"),KVpncEnum::error);
    //  sleep(2);
    //  if (GlobalConfig->status == KVpncEnum::connecting || KVpncEnum::connected)
    //   setStatusDisconnected(__LINE__);
}
