#ifndef HANDLERBASE_H
#define HANDLERBASE_H
/*****************************************************************************
 * Copyright (C) 2010 S.R.Haque <srhaque@theiet.org>                         *
 * Christoph Thielecke <crissi99@gmx.de> (broken out from kvpnc.h)           *
 *                                                                           *
 * 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 <KProcess>
#include <KPtyProcess>
#include <QtCore/QObject>
#include <QtCore/QTimer>
#include "kvpncconfig.h"

/**
 * This is the base class for all VPN protocol handlers. Apart from certain
 * pure-virtual functions, it also supports functionality which is common to
 * multiple protocol handlers.
 *
 * We manage connection retries and timeout logic for all derived protocol
 * handlers. We expect the caller to manage the UI state in all cases except
 * for the asynchronous notification of:
 *
 * - a successful connect, initiated by @ref vpnConnect(), and signalled by
 *   @ref setStatusConnected().
 * - a subsequent unexpected disconnect signalled by @ref setStatusDisconnected().
 */
class HandlerBase:
        protected QObject
{
protected:
    Q_OBJECT
public:
    HandlerBase(
        class KVpnc *ui,
        class KVpncConfig *config,
        QString type,
        QString daemons,
        bool _useL2tpAfterFirstConnection);
    virtual ~HandlerBase();

    /**
     * Initiate a connection attempt. Derived protocol handlers simply
     * implement the @ref vpnStart(bool authenticationRetry) we will call.
     *
     * In the event of an error, @ref vpnStop() will be called to clean up
     * before we hand control back to the caller.
     */
    void vpnConnect();
    void vpnDisconnectConnected();
    void vpnDisconnectConnecting();
    virtual QString pingDevice() const = 0;
    QString type() const;
    QString daemons() const;
    void addAdditionalNetworkRoutes();
    void removeAdditionalNetworkRoutes();
    void setPingTest();
    void connectCountZero();
    int connectCountIncrement();
    void connectSecondsZero();
    int connectSecondsIncrement();
    int connectSeconds() const;
    void stopTimer();
    bool timerIsActive();
    void timerStop();
protected:
    class KVpnc *gui;
    class KVpncConfig *GlobalConfig;

    /**
     * Version details of helper programs.
     */
    class HelperProgram {
    public:
        /**
         * Name and minimum acceptable version of a helper program.The version
         * string is compared numerically, section-by-section, separated by
         * the given regular expression.
         */
        HelperProgram(QString name, QString minimumVersion, QString regExp);
        const QString name() const;
        const QString minimumVersion() const;
        const QString actualVersion() const;
        void setActualVersion(QString actualVersion);

        /**
         * Available, and of viable version.
         */
        bool isGe() const;
        bool actualIsGe(QString againstVersion) const;
    private:
        QString m_name;
        QString m_minimumVersion;
        QString m_actualVersion;
        QString m_regExp;
        const char *m_processOutputSlot;
        const char *m_processErrorSlot;
        const char *m_ptyOutputSlot;
        const char *m_startedSlot;
        const char *m_finishedSlot;

        /**
         * Return true iff reference >= against.
         */
        bool isGe(QString reference, QString against) const;
    };

    /**
     * Add the entries to a named file.
     */
    bool pppdSecretsAdd(const QString &secretsFilename);

    /**
     * Add the entries to /etc/ppp/chap-secrets and /etc/ppp/pap-secrets.
     */
    bool pppdSecretsAdd();

    /**
     * Remove the entries from a named file.
     */
    void pppdSecretsRemove(const QString &secretsFilename);

    /**
     * Remove the entries from /etc/ppp/chap-secrets and /etc/ppp/pap-secrets.
     */
    void pppdSecretsRemove();

    /**
     * Load a kernel module.
     */
    bool kernelModuleLoad(const QString &module);

    /**
     * Unload a kernel module.
     */
    void kernelModuleUnload(const QString &module);

    /**
     * We got an error from the server we are connecting to.
     */
    void serverError(const QString &message);

    /**
     * Start a helper process. This routine runs synchronously to simplify the
     * programming model, but continues to process events when it is waiting.
     */
    class HelperProcess:
            private KProcess
    {
    public:
        HelperProcess(HandlerBase *parent,
                      const QString &program, QStringList &args, QStringList &env,
                      const char *processOutputSlot, const char *processErrorSlot,
                      const char *startedSlot, const char *finishedSlot);
        ~HelperProcess();
        QByteArray readAllStandardOutput();
        QByteArray readAllStandardError();
        bool start(unsigned long timeoutMicroseconds = 3000000);

        /**
         * Stop the given process. We give it a chance to shutdown cleanly, but in the
         * event of a timeout, we'll just kill it dead.
         */
        void waitForFinished(unsigned long timeoutMicroseconds);
        qint64 write(const QByteArray &byteArray);
    private:
        class HandlerBase *m_parent;
        QString m_program;
        const char *m_processOutputSlot;
        const char *m_processErrorSlot;
        const char *m_ptyOutputSlot;
        const char *m_startedSlot;
        const char *m_finishedSlot;
    };

    /**
     * Start a helper process. This routine runs synchronously to simplify the
     * programming model, but continues to process events when it is waiting.
     */
    class HelperPtyProcess:
            private KPtyProcess
    {
    public:
        HelperPtyProcess(HandlerBase *parent,
                         const QString &program, QStringList &args, QStringList &env,
                         const char *processOutputSlot, const char *processErrorSlot,
                         const char *ptyOutputSlot,
                         const char *startedSlot, const char *finishedSlot);
        ~HelperPtyProcess();
        KPtyDevice *pty();
        QByteArray readAllStandardOutput();
        QByteArray readAllStandardError();
        bool start(unsigned long timeoutMicroseconds = 3000000);

        /**
         * Stop the given process. We give it a chance to shutdown cleanly, but in the
         * event of a timeout, we'll just kill it dead.
         */
        void waitForFinished(unsigned long timeoutMicroseconds);
        qint64 write(const QByteArray &byteArray);
    private:
        class HandlerBase *m_parent;
        QString m_program;
        const char *m_processOutputSlot;
        const char *m_processErrorSlot;
        const char *m_ptyOutputSlot;
        const char *m_startedSlot;
        const char *m_finishedSlot;
    };

    bool enableTunnelSupport();


    /**
     * Initiate a connection attempt. If a connection is established, that will
     * be signalled later using @ref setStatusConnected().
     *
     * Where possible, implementations are recommended to do things in this
     * order:
     *
     *  1. All things that don't affect system state.
     *  2. All things that affect transient system state (i.e. fixed by reboot).
     *  3. All things that affect persistent system state (e.g. creating a file).
     *
     * @return False if any errors were detected, else true.
     */
    virtual bool vpnStart(bool authenticationRetry) = 0;

    /**
     * Teardown a connection, or connection attempt.
     */
    virtual void vpnStop() = 0;

    /**
     * Get any needed authentication data.
     */
    virtual bool getAuthenticationData(bool AuthRetry);

    QStringList *env;
    bool useL2tpAfterFirstConnection;
    QTimer L2tpWaitForPppUpTimer; //< getting ppp device
    bool pppdHasReplacedefaultrouteSupport;
    QString OldDefaultroute;
    QString tmpPath;
    HelperProcess *RouteProcess;
    bool pingtest;
    unsigned connectCounter; //< counter for connecting
    QString IpsecType; //< openswan, strongswan, freeswan
    bool stopContinue; //< if something happens that causes to abort
    HelperProcess *DisconnectProcess;
    bool IpsecStartingInProgress;
    QTimer timer;
    QString m_daemons;

    void debugMessage(unsigned int level, const QString &message);
    void errorMessage(const QString &message);
    void successMessage(const QString &message);
    void reportError(const QString &text, const QString &caption = QString(), KVpncEnum::LogType type = KVpncEnum::error);
    void addRoute();
    void delRoute();
    void readFromStderr();
    void readFromStderrCtl();
    void readFromStderrHelper();
    void readFromStderrStatus();
    void readFromStdout();
    void readFromStdoutCtl();
    void readFromStdoutHelper();
    void readFromStdoutStatus();
    void start();
    void started();
    void stop();
    //void stopped(int, QProcess::ExitStatus);
    void disconnectProcess(QString task, QString script);
    void processStdout_l2tpd(QString msg);
    void processStderr_l2tpd(QString msg);
    void doPingKickupTunnel();
    void setFirewallAfterConnect();
    void startConnectionStatusCheck();
    void executeCommandBeforeConnect();
    void executeCommandAfterConnect();
    void backupIpsecSystemConfig(); // backup ipsec.conf and  ipsec.secrets
    bool backupDefaultRoute();
    QString getX509CertificateID(QString Cert, bool user);
    void readFromStdout_getX509CertificateID();
    void l2tpPrepare(bool do_auth=false); //< write l2tpd conf etc.
    void l2tpStart();
    void l2tpStop();
    void startStopOpenl2tp(bool stop = false, bool silent = false);
    void doAddRemoveVirtualIp(QString action);
    void waitForFinished(QProcess *process, int refreshtime, QString refreshmsg=""); //< this waits while process process is running refreshes every refreshtime, if refreshmsg is not empty its displayed

    /**
     * Reasons for disconnection. All reasons before FATAL_ERROR can be retried.
     * For example, if the reason was an authentication failure, a retry with
     * new credentials might be worthwhile!
     */
    typedef enum {
        DISCONNECTION_REASON_AUTHENTICATION_FAILED,
        DISCONNECTION_REASON_CONFIGURATION_CHANGED,
        DISCONNECTION_REASON_FATAL_ERROR,
        DISCONNECTION_REASON_USER_REQUEST,
    } DisconnectionReason;

protected slots:
    /**
     * Asynchronous notification of disconnection.
     */
    void setStatusDisconnected(unsigned line, DisconnectionReason reason);

    /**
     * Asynchronous notification of connection.
     */
    void setStatusConnected(unsigned line);
    void slotConnectTimedOut();

private slots:
    void slotDurationEvent();
    void connectionEnded();
    void readFromStdout_disconnect();
    void readFromStderr_disconnect();
    void readFromStdout_openl2tp();
    void readFromStderr_openl2tp();
    void readFromStdout_startstopOpenl2tp();
    void readFromStderr_startstopOpenl2tp();
    void readFromStdout_l2tpd();
    void readFromStderr_l2tpd();
    void readFromStdout_route();
    void readFromStderr_route();
private:
    HelperProcess *OpenL2tpProcess;
    HelperProcess *L2tpdProcess;
    HelperProcess *StartStopOpenl2tpProcess;
    QTimer durationTimer; //< timer for counting connection time
    int connectedSeconds;
    void setGuiStatus(int status);


signals:
    // TBD: who emits these?
    void L2tpdStdoutRecieved();
    void L2tpdStderrRecieved();
};
#endif
