
// protApp.cpp - Simple example application using Protean protLib
//               command-line dispatcher, timer, and socket classes

#include "protoLib.h"

#include <stdio.h>   // for stdout/stderr printouts
#include <signal.h>  // for SIGTERM/SIGINT handling

class ProtoApp
{
    public:
        ProtoApp();
        bool OnStartup();
        int MainLoop() {return dispatcher.Run();}
        void Stop(int exitCode) {dispatcher.Stop(exitCode);}
        void OnShutdown();
        
    private:
        bool OnTxTimeout();   
        bool OnSocketRecv(UdpSocket* theSocket);
        static void SignalHandler(int sigNum);
        EventDispatcher dispatcher;
        UdpSocket       socket;
        ProtocolTimer   tx_timer;
    
}; // end class ProtoApp

ProtoApp::ProtoApp()
{
    // Init tx_timer for 1.0 second interval, infinite repeats
    tx_timer.Init(1.0, -1, 
                 (ProtocolTimerOwner*)this, 
                 (ProtocolTimeoutFunc)&ProtoApp::OnTxTimeout);  
    
    // Init socket, specifying async recv owner/handler, async installer
    socket.Init((UdpSocketOwner*)this, 
                (UdpSocketRecvHandler)&ProtoApp::OnSocketRecv, 
                EventDispatcher::SocketInstaller, 
                &dispatcher);
}

bool ProtoApp::OnStartup()
{
#ifdef WIN32
    if (!dispatcher.Win32Init())
    {
        fprintf(stderr, "protoApp:: Win32Init() error!\n");
        return false;
    }
#endif // WIN32
    
    NetworkAddress x;
    x.LookupLocalHostAddress();
    DMSG(0, "protoApp:: local EndIdentifier: %0x\n", x.EndIdentifier());

    // Open a udp socket
    if (UDP_SOCKET_ERROR_NONE != socket.Open(5003))
    {
        fprintf(stderr, "protoApp: Error opening UDP socket!\n");
        return false;
    }    
    // Install our transmit timer
    dispatcher.InstallTimer(&tx_timer);
    signal(SIGTERM, SignalHandler);
    signal(SIGINT, SignalHandler);
    return true;
}  // end ProtoApp::OnStartup()

void ProtoApp::OnShutdown()
{
    socket.Close();
    tx_timer.Deactivate();
}  // end ProtoApp::OnShutdown()

bool ProtoApp::OnTxTimeout()
{
    NetworkAddress addr;
    addr.LookupHostAddress("127.0.0.1");
    addr.SetPort(5003);
    char* buffer = "Hello, Proteus";
    unsigned int len = strlen(buffer) + 1;
    socket.SendTo(&addr, buffer, len);
    return true;
}  // end ProtoApp::OnTxTimeout()

bool ProtoApp::OnSocketRecv(UdpSocket* /*theSocket*/)
{
    char buffer[512];
    unsigned int len = 512;
    NetworkAddress addr;
    socket.RecvFrom(buffer, &len, &addr);
    fprintf(stderr, "protoApp:: Received \"%s\" from \"%s\"\n",
            buffer, addr.HostAddressString());
    return true;
}  // end ProtoApp::OnSocketRecv()
        
// Out application instance (global for SignalHandler)
ProtoApp theApp; 


// Use "main()" for UNIX and WIN32 console apps, 
// "WinMain()" for non-console WIN32
// (VC++ uses the "_CONSOLE_ macro to indicate build type)

#if defined(WIN32) && !defined(_CONSOLE)
int PASCAL WinMain(HINSTANCE instance, HINSTANCE prevInst, LPSTR cmdline, int cmdshow)
#else
int main(int argc, char* argv[])
#endif
{
#ifdef WIN32
    // Hack to determine if Win32 console application was launched
    // independently or from a pre-existing console window
    bool pauseForUser = false;
    HANDLE hConsoleOutput = GetStdHandle(STD_OUTPUT_HANDLE);
    if (INVALID_HANDLE_VALUE != hConsoleOutput)
    {
        CONSOLE_SCREEN_BUFFER_INFO csbi;
        GetConsoleScreenBufferInfo(hConsoleOutput, &csbi);
        pauseForUser = ((csbi.dwCursorPosition.X==0) && (csbi.dwCursorPosition.Y==0));
        if ((csbi.dwSize.X<=0) || (csbi.dwSize.Y <= 0)) pauseForUser = false;
    }
    else
    {
        // We're not a "console" application, so create one
        // This could be commented out or made a command-line option
        OpenDebugWindow();
        pauseForUser = true;
    }
    
    
#endif // WIN32

    int exitCode = 0;
    if (theApp.OnStartup())
    {
        exitCode = theApp.MainLoop();
        theApp.OnShutdown();
        fprintf(stderr, "protoApp: Done.\n");
#ifdef WIN32
        // If Win32 console is going to disappear, pause for user before exiting
        if (pauseForUser)
        {
            printf ("Program Finished - Hit <Enter> to exit");
            getchar();
        }
#endif // WIN32
    }
    else
    {
         fprintf(stderr, "protoApp: Error initializing application!\n");
         return -1;  
    }      
    return exitCode;  // exitCode contains "signum" causing exit
}  // end main();

void ProtoApp::SignalHandler(int sigNum)
{
    switch(sigNum)
    {
        case SIGTERM:
        case SIGINT:
            theApp.Stop(sigNum);  // causes theApp's main loop to exit
            break;
            
        default:
            fprintf(stderr, "protoApp: Unexpected signal: %d\n", sigNum);
            break; 
    }  
}  // end ProtoApp::SignalHandler()
