// $Id: win32_threads.c,v 1.10 2011/05/07 01:12:34 lpoulsen Exp $
//
// Copyright (c) 2008 by Afar Communications Inc, Santa Barbara, CA 93117 USA
//
// Thread management for the Econsole utility program (Windows version)
//
// $Log: win32_threads.c,v $
// Revision 1.10  2011/05/07 01:12:34  lpoulsen
// Add another include for ECON 04.07
//
// Revision 1.9  2011/03/22 00:26:10  lpoulsen
// Resolve sequence errors on very weak links
// Resolve hangs when Ctrl-C between "dir" and "download"
// Resolve conflicting build date information
//
// Revision 1.8  2011/03/02 18:19:35  lpoulsen
// Allow Ctrl-C to close a session
//
// Revision 1.7  2010/12/28 20:04:41  lpoulsen
// Do not ask Windows to name the events, because that makes them global
// (i.e. shared between multiple instantiations of the program.)
//
// Revision 1.6  2008/08/07 01:54:20  lpoulsen
// Fix hang in script processing (unitialized event flag)
//
// Revision 1.5  2008/07/28 22:54:26  lpoulsen
// Tight handshake for V4 scripting
//
// Revision 1.4  2008/07/16 23:18:46  lpoulsen
// Add missing include statement
//
// Revision 1.3  2008/07/11 16:28:46  lpoulsen
// Econ 4.00 release candidate supports UPDATE and SELECT commands.
//
// Revision 1.2  2008/06/10 00:06:10  lpoulsen
// New -X option is now working
// Updated -I and -O implementation
//
// Revision 1.1  2008/05/31 00:39:19  lpoulsen
// New file replaces windows_threads.c
//
// ----------------------------------------------------------------------------
//
// This version repleaces the older windows_threads.c module which
// has been abandoned as too buggy.
//
// ----------------------------------------------------------------------------

#include <windows.h>
#include <process.h>
#include "afartype.h"
#include "epacket.h"
#include "ucon.h"

BOOL WINAPI econ_kill_handler(const DWORD dwCtrlType);

//
// External interfaces:
//
//  Entry points:
// - start_all_threads()
// - self_init_thread()
// - self_terminate_thread()
// - terminate_all_threads()
// - wait_for_all_threads_to_terminate()
// - readThreadInit()
// - readThreadTerminate()
//
// Global variables referenced:

extern enum econ_phases econ_phase;

enum {
    THR_READ=0,
    THR_SEND,
    THR_RECV,
    THR_FILE
} thr_enum;

//
//  External functions called (non-system-provided):

extern ThreadReturnType ReadThread(void *ec_s);
extern ThreadReturnType ReceiveThread(void *ec_s);
extern ThreadReturnType SendThread(void *ec_s);
extern ThreadReturnType FileThread(void *ec_s);

//
// Global variables defined:

HANDLE ReadThreadHandle;
HANDLE ReceiveThreadHandle;
HANDLE SendThreadHandle;
HANDLE FileThreadHandle;

HANDLE evtEndOfSession;
HANDLE evtThreadTermination;

//
// Global variables referenced:
extern struct ether_session active_session;
extern HANDLE available_data,
              buffer_space,
              command_Completed,
              download_Completed,
              File_ACK,
              Receive_ACK,
              never_set,
              server_flags_updated;

// ----------------------------------------------------------------------------

void
start_all_threads()
{
    struct ether_session *s_id = &active_session;

    if (!SetConsoleCtrlHandler(econ_kill_handler, TRUE))
    {
        printf("SetConsoleCtrlHandler(,%s) failed - error %d\n",
               1, GetLastError() );
    }

    available_data     = (HANDLE) CreateEvent(NULL, FALSE, FALSE, NULL);
                                                    //   "available_data");
    buffer_space       = (HANDLE) CreateEvent(NULL, FALSE, FALSE, NULL);
                                                    //   "buffer_space");
    command_Completed  = (HANDLE) CreateEvent(NULL, FALSE, FALSE, NULL);
                                                    //   "command_Completed");
    download_Completed = (HANDLE) CreateEvent(NULL, FALSE, FALSE, NULL);
                                                    //   "download_Completed");
    File_ACK           = (HANDLE) CreateEvent(NULL, FALSE, FALSE, NULL);
                                                    //   "File_ACK");
    if (File_ACK == NULL) {
        printf("LJP14 Failed to create event File_ACK: %d\n", GetLastError);
    }
    Receive_ACK        = (HANDLE) CreateEvent(NULL, FALSE, FALSE, NULL);
                                                    //   "Receive_ACK");
    evtEndOfSession    = (HANDLE) CreateEvent(NULL, FALSE, FALSE, NULL);
                                                    // "evtEndOfSession");
    never_set          = (HANDLE) CreateEvent(NULL, FALSE, FALSE,
                                                    "never_set");
    server_flags_updated=(HANDLE) CreateEvent(NULL, FALSE, FALSE,
                                                    "server_flags_updated");
    evtThreadTermination = CreateEvent(NULL, FALSE, FALSE,
                                                    "evtThreadTermination");


    ReadThreadHandle = (HANDLE) _beginthreadex(NULL, 0, &ReadThread,
                                               (void *)s_id, 0, NULL);
    ReceiveThreadHandle = (HANDLE) _beginthreadex(NULL, 0, &ReceiveThread,
                                                  (void *)s_id, 0, NULL);
    SendThreadHandle = (HANDLE) _beginthreadex(NULL, 0, &SendThread,
                                               (void *)s_id, 0, NULL);
    FileThreadHandle = (HANDLE) _beginthreadex(NULL, 0, &FileThread,
                                               (void *)s_id, 0, NULL);
}

void
self_init_thread()
{   // a dummy on Windows
}

void
self_terminate_thread()
{
    SetEvent(evtThreadTermination);
    _endthreadex(0);
}

void
kill_thread(HANDLE handle)
{
    DWORD wait_result;
    DWORD win32_error;
    BOOL  result;
    wait_result = SuspendThread(handle);
    if (wait_result == 0xFFFFFFFF)
    {
        win32_error = GetLastError();
        // Probably 0x0005 - EACCESS - thread has been terminated already ?

		return;
    }
    if (WaitForSingleObject(handle, 0) == WAIT_TIMEOUT)
    {
        CONTEXT context;
        context.ContextFlags = CONTEXT_CONTROL;
        result = GetThreadContext(handle, &context);
        if (result == 0)
        {
            win32_error = GetLastError();
        }
        context.Eip = (DWORD) self_terminate_thread;
        SetThreadContext(handle, &context);
        ResumeThread(handle);
    }
    else printf("kill_thread Error\n");
}

// terminate_all_threads() is called from one of the worker threads
//      when it decides the current work session is over.
//      We don't actually do any work ... we just tell the main
//      program to wake up and kill all the workers.

void
terminate_all_threads(int dummy)
{
    econ_phase = ECON_CLOSE_THREADS;

    SetEvent(evtEndOfSession);
    self_terminate_thread();
}

void
wait_for_all_threads_to_terminate()
{
    DWORD two_seconds = 2000;
    DWORD wait_result;

	do {
		wait_result = WaitForSingleObject(evtEndOfSession, 1000L);
	} while (econ_phase != ECON_CLOSE_THREADS);

    // We come back when any one of the threads says we want to wrap up
    //  by calling terminate_all_threads() which sets
    //  econ_phase = ECON_CLOSE_THREADS.
    // Let's encourage everyone to clean THEMSELVES up. Each thread
    //  should exit when it sees that value in econ_phase.
    // What are they waiting for:
    // - ReadThread() - waiting in a_getch() polling every 5 ms
    // - SendThread() - waiting for event flag available_data
    // - ReceiveThread() - waiting in adapter_receive() for up to 100 ms
    // - FileThread() - waiting for event File_ACK

    SetEvent(available_data);   // wake up SendThread()
    SetEvent(File_ACK);         // wake up FileThread()
    Sleep(101);                 // let ReadThread() and ReceiveThread() wake up

    if (!SetConsoleCtrlHandler(econ_kill_handler, FALSE))
    {
        printf("SetConsoleCtrlHandler(,%d) failed - error %d\n",
               0, GetLastError() );
    }
    // Now all the threads should have terminated. We would like to
    //  verify that they have, but apparently we can't: The thread object
    //  persists until the handle is closed, but the thread itself cannot
    //  easily close it ... or even know what it is.
    // The code below would seem to be right, but who knows.

    // Make sure ReadThread really got killed
    wait_result = WaitForSingleObject(ReadThreadHandle, 0);
    if (wait_result == WAIT_FAILED)
    {
        CloseHandle(ReadThreadHandle);
        ReadThreadHandle = 0;
    } else {
        kill_thread(ReadThreadHandle);
        Sleep(1);
        CloseHandle(ReadThreadHandle);
        ReadThreadHandle = 0;
    }

    // Make sure ReceiveThread really got killed
    wait_result = WaitForSingleObject(ReceiveThreadHandle, 0);
    if (wait_result == WAIT_FAILED)
    {
        CloseHandle(ReceiveThreadHandle);
        ReceiveThreadHandle = 0;
    } else {
        kill_thread(ReceiveThreadHandle);
        Sleep(1);
        CloseHandle(ReceiveThreadHandle);
        ReceiveThreadHandle = 0;
    }

    // Make sure SendThread really got killed
    wait_result = WaitForSingleObject(SendThreadHandle, 0);
    if (wait_result == WAIT_FAILED)
    {
        CloseHandle(SendThreadHandle);
        SendThreadHandle = 0;
    } else {
        kill_thread(SendThreadHandle);
        Sleep(1);
        CloseHandle(SendThreadHandle);
        SendThreadHandle = 0;
    }

    // Make sure FileThread really got killed
    wait_result = WaitForSingleObject(FileThreadHandle, 0);
    if (wait_result == WAIT_FAILED)
    {
        CloseHandle(FileThreadHandle);
        FileThreadHandle = 0;
    } else {
        kill_thread(FileThreadHandle);
        Sleep(1);
        CloseHandle(FileThreadHandle);
        FileThreadHandle = 0;
    }
}

BOOL WINAPI econ_kill_handler(const DWORD dwCtrlType)
{
    if (dwCtrlType == 0) printf("[Ctrl-C]");
    else                 printf("[Ctrl-C] (%d)\n", dwCtrlType);

    session_result = ES_CTRL_C;
    econ_phase     = ECON_CLOSE_THREADS;
    return TRUE;
}
