/******************************************************************************\
 *  This sample is supplied as is with no implied warranty.  
 *  It is designed to assist you in using the Cisco AnyConnect VPN API. 
 *  It is assumed that you will build a production application and 
 *  refer to this sample as a reference only.
 \*****************************************************************************/

#include "CLIClientImpl.h"

#ifndef _WIN32
#include <unistd.h>
#endif

#ifdef _WIN32
tstring SEP = L"\n\t";
#include "atlbase.h"
#else
tstring SEP = "\n\t";
#endif

/** @file */

CLIClientImpl :: CLIClientImpl()
               : me_RequestType(),
                 ms_user(),
                 ms_pswd(),
                 ms_group(),
                 mo_VPNStats() { }

CLIClientImpl :: ~CLIClientImpl() { }


void
CLIClientImpl :: StateCB(const VPNState state,
                         const VPNSubState subState,
                         const tstring stateString)
{
#ifdef UNICODE
   printf("Current State (%d): %S\n", state, stateString.c_str());
#else
   printf("Current State (%d): %s\n", state, stateString.c_str());
#endif
}


void
CLIClientImpl :: BannerCB(const tstring &banner)
{
	bool bBannerAccepted = true;
	setBannerResponse(bBannerAccepted);
}

void
CLIClientImpl :: PreConnectReminderCB(const tstring &rtstrPreConnectReminder)
{
#ifdef UNICODE
   printf("\n%S", rtstrPreConnectReminder.c_str());
#else
   printf("\n%s", rtstrPreConnectReminder.c_str());
#endif

    printf( "Press enter key to continue... " );
    std::cin.get();
    setPreConnectReminderResponse();
}

void
CLIClientImpl :: NoticeCB(const tstring &notice,
                          const MessageType type,
                          const bool /* bSensitive */)
{
    std::string msgType;
    switch(type)
        {
            case MsgType_Error:
                msgType = "Error";
                break;
            case MsgType_Warn:
                msgType = "Warn";
                break;
            case MsgType_Info:
                msgType = "Info";
                break;
            default:
                msgType = "Unknown";
                break;
        }

#ifdef UNICODE
    printf("Notice (%s): %S\n", msgType.c_str(), notice.c_str());
#else
    printf("Notice (%s): %s\n", msgType.c_str(), notice.c_str());
#endif
}


void
CLIClientImpl :: ExitNoticeCB(const tstring &notice, const int returnCode)
{
#ifdef UNICODE
    printf("ExitNoticeCB (%d): %S\n", returnCode, notice.c_str());
#else
    printf("ExitNoticeCB (%d): %s\n", returnCode, notice.c_str());
#endif
}

void
CLIClientImpl :: showGroups(std::string host)
{
    me_RequestType = REQ_GROUPLIST;
#ifdef UNICODE
    ClientIfc::connect(convertMultiByteToWide(host));
#else
    ClientIfc::connect(host);
#endif
}

void
CLIClientImpl :: connect(std::string host, std::string user,
                         std::string pswd, std::string group)

{
    me_RequestType = REQ_CONNECT;
    // save these user input values until requested by the API.
    //
#ifdef UNICODE
    ms_user = convertMultiByteToWide(user);
    ms_pswd = convertMultiByteToWide(pswd);
    ms_group = convertMultiByteToWide(group);
#else
    ms_user = user;
    ms_pswd = pswd;
    ms_group = group;
#endif

    // This is the main method for initiating a VPN connection.
    // The method can be found in ClientIfc.
    //
#ifdef UNICODE
    ClientIfc::connect(convertMultiByteToWide(host));
#else
    ClientIfc::connect(host);
#endif
}


void
CLIClientImpl :: disconnect()
{
    ClientIfc::disconnect();
	// To ensure VPN service receive the disconnect message, 
    // do a short sleep before CLI terminates.
    //
    // NOTE: This is done only for demonstration purposes and would not be
    // needed in a GUI or other program that runs continuously. A more elegant way
	// to handle this case is to terminate the CLI after checking the disconnect callback message
	// from API.
    //
#ifdef _WIN32
    Sleep(1500);
#else
    sleep(1);
#endif
}

void
CLIClientImpl :: getStats()
{
    if (isConnected())
    {
        // To ensure we receive the first set of stats before we try to print,
        // do a short sleep.
        //
        // NOTE: This is done only for demonstration purposes and would not be
        // needed in a GUI or other program that runs continuously.
        //
#ifdef _WIN32
        Sleep(1500);
#else
        sleep(1);
#endif

        tstring stats = SEP + VPNStats::getTranslatedLabel(VPNStats::State)
                            + mo_VPNStats.getStatValue(VPNStats::State);
        stats += SEP + VPNStats::getTranslatedLabel(VPNStats::TunnelingMode)
                     + mo_VPNStats.getStatValue(VPNStats::TunnelingMode);
        stats += SEP + VPNStats::getTranslatedLabel(VPNStats::TimeConnected)
                     + mo_VPNStats.getStatValue(VPNStats::TimeConnected);
        stats += SEP + VPNStats::getTranslatedLabel(VPNStats::BytesSent)
                     + mo_VPNStats.getStatValue(VPNStats::BytesSent);
        stats += SEP + VPNStats::getTranslatedLabel(VPNStats::BytesReceived)
                     + mo_VPNStats.getStatValue(VPNStats::BytesReceived);

        // Now get the data for the active protocol.
        //
        // NOTE: There is also Secure and non-secure route information.  This
        //       data can be retrieved in a manner similar to protocolInfo
        //       using the methods getSecureRoutes() and getNonsecureRoutes()
        //
        std::list<ProtocolInfo *> protInfo = mo_VPNStats.getProtocolInfo();
        for (std::list<ProtocolInfo *>::iterator iter = protInfo.begin();
                                                 iter != protInfo.end();
                                                 iter++)
        {
            if ((*iter)->isActive())
            {
                stats += SEP + VPNStats::getTranslatedLabel(ProtocolInfo::Cipher)
                             +(*iter)->getProtocolValue(ProtocolInfo::Cipher);
                stats += SEP + VPNStats::getTranslatedLabel(ProtocolInfo::Protocol)
                             +(*iter)->getProtocolValue(ProtocolInfo::Protocol);
                stats += SEP + VPNStats::getTranslatedLabel(ProtocolInfo::Compression)
                             +(*iter)->getProtocolValue(ProtocolInfo::Compression);
            }
        }

#ifdef UNICODE
        printf("VPN Stats:%S\n",stats.c_str());
#else
        printf("VPN Stats:%s\n",stats.c_str());
#endif
    }
    else
    {
        printf("Tunnel not up, no stats to print.\n");
    }
}

// In an application, this method would be used to signal to the program that
// the VPN service is available to this application.  Once this method is
// received, the application will be ready to call method slike connect.
//
// For this example and to keep the code simple, the method is not utilized
// as described.
//

void
CLIClientImpl :: ServiceReadyCB() { }

void
CLIClientImpl :: UserPromptCB(ConnectPromptInfo &ConnectPrompt)
{
    if (me_RequestType == REQ_CONNECT)
    {
        // For example purposes, only allow one try at setting user data.
        //
        me_RequestType = REQ_END;
        // Look for requested user input fields and fill in previously
        // stored values.
        //
        setUserData(ConnectPrompt);
        // Now that the user data has been entered, submit the response.
        //
        UserSubmit();
    }
    else if (me_RequestType == REQ_GROUPLIST)
    {
        printGroupList(ConnectPrompt);
    }

}

/*! \fn
 *
 */

void
CLIClientImpl :: setUserData(ConnectPromptInfo &ConnectPrompt)
{
#ifdef UNICODE
    printf("User Message: %S\n", ConnectPrompt.getMessage().c_str());
#else
    printf("User Message: %s\n", ConnectPrompt.getMessage().c_str());
#endif

    // Create a list to hold the names of the individual PromptEntry objects.
    //
    std::list<tstring> promptNames;
    // Get the list of names associated with the PromptEntry objects.
    //
    ConnectPrompt.getListPromptNames(promptNames);
    // This set of code cycles through the list of names that reference the
    // individual PromptEntry objects.  As each name is accessed it can be used
    // to retrieve a specific instance of a PromptEntry object.  There is also a
    // method (ConnectPromptInfo::getListPromptEntry()) to retrieve the list of
    // PromptEntry objects directly, allowing the individual PromtpEntry object
    // to be accessed in a different manner if desired.
    //
    std::list<tstring> :: iterator name_iter;
    for (name_iter = promptNames.begin();
                     name_iter != promptNames.end(); ++name_iter)
    {
        // name_iter represents a single name from the promptNames list.
        //
        tstring promptName = *name_iter;
        PromptEntry *entry = ConnectPrompt.getPromptEntry(promptName);

        // For this demo program, we'll assume any combo box is for
        // group selection.
        //
        if (entry->getPromptType() == Prompt_Combo)
        {
            entry->setValue(ms_group);
        }
        tstring entryName = entry->getPromptName();

        // PromptEntry::Username and PromptEntry::Password are string currently
        // resolving to the wide char values "username" and "password".
        // 
        if (entryName == PromptEntry::Username)
        {
            entry->setValue(ms_user);
        }
        else if (entryName == PromptEntry::Password)
        {
            entry->setValue(ms_pswd);
        }
    }
}


void
CLIClientImpl :: printHostList()
{
    // getHostNames can be found in ClientIfc
    //
    std::list<tstring> hostList = getHostNames();
    std::list<tstring> :: iterator host_iter;
    tstring hosts;
    for (host_iter = hostList.begin();
         host_iter != hostList.end(); ++host_iter)
    {
        hosts += *host_iter + SEP;
    }

#ifdef UNICODE
    printf("Host List:\n\t%S\n", hosts.c_str());
#else
    printf("Host List:\n\t%s\n", hosts.c_str());
#endif
}


void
CLIClientImpl :: printDefaultHost()
{
#ifdef UNICODE
    printf("Default host: %S\n", getDefaultHostName().c_str());
#else
    printf("Default host: %s\n", getDefaultHostName().c_str());
#endif
}


void
CLIClientImpl :: printGroupList(ConnectPromptInfo &ConnectPrompt)
{
    std::list<tstring> promptNames;
    // get the list of PromptEntry received.
    //
    ConnectPrompt.getListPromptNames(promptNames);

    std::list<tstring> :: iterator name_iter;
    for (name_iter = promptNames.begin();
                     name_iter != promptNames.end(); ++name_iter)
    {
        PromptEntry *entry = ConnectPrompt.getPromptEntry(*name_iter);
        // For this demo program, we'll assume any combo box is for
        // group selection.
        //
        if (entry->getPromptType() == Prompt_Combo)
        {
            tstring groupList;
            std::list<tstring>::const_iterator lo_Citer;

            for (lo_Citer = entry->getValueOptions().begin();
                 lo_Citer != entry->getValueOptions().end(); ++lo_Citer)
            {
                tstring value = *lo_Citer;
                groupList += SEP + (*lo_Citer);
            }


#ifdef UNICODE
            printf("Group List: %S\n", groupList.c_str());
#else
            printf("Group List: %s\n", groupList.c_str());
#endif
        }

    }
}


void
CLIClientImpl :: StatsCB(VPNStats &stats)
{
    mo_VPNStats = stats;
}

void
CLIClientImpl :: CertBlockedCB(const tstring &rtstrUntrustedServer)
{
    setCertBlockedResponse(false);
}

void
CLIClientImpl :: CertWarningCB(const tstring &rtstrUntrustedServer,
                               const std::list<tstring> &rltstrCertErrors,
                               bool bAllowImport)
{
    setCertWarningResponse(false, false);
}

// These methods provide a means to convert strings from wide to multibyte
// and vice-versa.
//

std::wstring
CLIClientImpl :: convertMultiByteToWide(const std::string &sInputData)
{
    std::wstring wsDataBuffer;

    // determine number of bytes that will be written
    //
#ifdef _WIN32
    int bufSize = MultiByteToWideChar(CP_UTF8, 0, sInputData.c_str(),
                                      -1, NULL, 0);
#else
    int bufSize = mbstowcs(NULL, sInputData.c_str(), 0);
#endif

    if (bufSize > 0)
    {
        wchar_t *wzTmpBuf = new wchar_t[bufSize];
#ifdef _WIN32
        int converted = MultiByteToWideChar(CP_UTF8, 0, sInputData.c_str(),
                                            -1, wzTmpBuf, bufSize);
        converted--;  // drop the terminating NULL.
#else
        int converted = mbstowcs(wzTmpBuf, sInputData.c_str(), bufSize);
#endif
        wsDataBuffer.assign(wzTmpBuf, converted);
        delete[] wzTmpBuf;
    }
    else
    {
        printf("convertMultiByteToWide : Conversion failed\n");
    }

    return wsDataBuffer;
}
