
/**
 * @file fmt.c
 *
 * Ukazka pouziti objektu file mapping (section) na realizaci sdilene pameti a komunikace
 * mezi dvema procesy.
 *
 * Program se chova ruzne v zavislosti na argumentech prikazove radky.
 * Syntaxe je nasledujici:
 * fmt.exe --server
 *   Program se chova jako server. Vytvori oblast sdilene pameti (objekt file mapping)
 *   a potrebna synchronizacni primitiva. Dale ceka na zpravu od klienta. Pokud obdrzi
 *   retezec "END," ukonci svoji cinnost. Jinak klientovi zasle zpatky zpravu "OK."
 * fm.exe --client
 *   Pripoji se na objekty vytvorene serverem. Nasledne ve smycce ceka na vstup od
 *   uzivatele, posila jej serveru a tiskne jeho odpovedi. Pokud uzivatel zada odeslat
 *   zpravu "END," klient ji odesle serveru a ukonci svoji cinnost.
 */

#include <stdio.h>
#include <windows.h>
#include "fmt.h"



/** Tato rutina reprezentuje serverovou cast.
 *
 * Rutina vytvori potrebne objekty exekutivy (section, synchronizacni udalosti) a ve smycce
 * ceka na prichozi zpravy, ktere tiskne na obrazovku a odpovida na ne retezcem "OK."
 * Prijeti zpravy "END" znamena ukonceni.
 */
VOID Server(VOID)
{
  HANDLE hMapping = NULL;
  HANDLE hIncoming = NULL;
  HANDLE hOutcoming = NULL;
  PVOID pMapping = NULL;
  DWORD WaitResult = 0;
  BOOLEAN Terminate = FALSE;

  // Vytvoreni objektu sdilene pameti
  hMapping = CreateFileMappingW(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, FILEMAPPING_SIZE, FILEMAPPING_NAME);
  if (hMapping != NULL) {
    // Vytvoreni udalosti, ktera je v signalnim stavu, pokud se ve sdilene pameti
    // nachazi zprava pro server.
    hIncoming = CreateEventW(NULL, FALSE, FALSE, INCOMING_EVENT_NAME);
    if (hIncoming != NULL) {
      // Vytvoreni udalosti, ktera se nachazi v signalnim stavu, pokud je ve sdilene
      // pameti ulozena zprava pro klienta.
      hOutcoming = CreateEventW(NULL, FALSE, TRUE, OUTCOMING_EVENT_NAME);
      if (hOutcoming != NULL) {
        // Namapovani objektu sdilene pameti
        pMapping = MapViewOfFile(hMapping, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0);
        if (pMapping != NULL) {
          // V teto smycce server ceka, prijima zpravy, tiskne je a odpovida zpravou "OK"
          while (!Terminate) {
            // Cekani na prichozi zpravu
            WaitResult = WaitForSingleObject(hIncoming, TIMEOUT);
            if (WaitResult == WAIT_OBJECT_0) {
              // Zprava opravdu prisla, vytiskneme ji
              printf("\nPrichozi zprava: %s\n", pMapping);
              // Zjistime, zda se nejedna o zpravu "END," ktera znamena ukonceni cinnosti
              Terminate = (strncmp((PCHAR)pMapping, END_SIGNAL, strlen(END_SIGNAL)) == 0);
              // Zasleme zpravu "OK"
              ZeroMemory(pMapping, FILEMAPPING_SIZE);
              CopyMemory(pMapping, RESPONSE_TEXT, strlen(RESPONSE_TEXT));
              SetEvent(hOutcoming);
            } else if (WaitResult == WAIT_TIMEOUT) {
              // Vypresel casovy interval cekani
              putchar('.');
            } else printf("CHYBA: WaitForSingleObject selhala. Navratovy kod %u. Error %u\n", WaitResult, GetLastError());
          }

          UnmapViewOfFile(pMapping);
        } else printf("CHYBA: MapViewOfFile selhala. Chyba %u\n", GetLastError());

        CloseHandle(hOutcoming);
      } else printf("CHYBA: CreateEventW(OutcomingEvent) selhala. Chyba %u\n", GetLastError());

      CloseHandle(hIncoming);
    } else printf("CHYBA: CreateEventW(IncomingEvent) selhala. Chyba %u\n", GetLastError());

    CloseHandle(hMapping);
  } else printf("ERROR: CreateFileMappingW selhala. Chyba %u\n", GetLastError());

  return;
}



/** Tato rutina reprezentuje klientskou cast aplikace.
 * 
 * Rutina se nejprve pripoji k objektum sdilene pameti a synchronizacnich udalosti,
 * cimz navaze spojeni se serverem. Nasledne ve smycce ceka na vstup uzivatele, preposila
 * zpravy serveru a prijima jeho odpovedi.
 */
VOID Client(VOID)
{
  HANDLE hMapping = NULL;
  HANDLE hIncoming = NULL;
  HANDLE hOutcoming = NULL;
  PVOID pMapping = NULL;
  DWORD WaitResult = 0;
  PCHAR Message;
  PCHAR Response = NULL;
  BOOLEAN Terminate = FALSE;
  BOOLEAN FirstTime = TRUE;

  // Alokace pameti pro ulozeni zpravy od uzivatele
  Message = (PCHAR)HeapAlloc(GetProcessHeap(), 0, FILEMAPPING_SIZE);
  if (Message != NULL) {
    // Napojeni na sdilenou pamet
    hMapping = OpenFileMappingW(FILE_MAP_READ | FILE_MAP_WRITE, FALSE, FILEMAPPING_NAME);
    if (hMapping != NULL) {
      // Napojeni na obe synchronizacni udalosti
      hIncoming = OpenEventW(EVENT_ALL_ACCESS, FALSE, INCOMING_EVENT_NAME);
      if (hIncoming != NULL) {
        hOutcoming = OpenEventW(SYNCHRONIZE, FALSE, OUTCOMING_EVENT_NAME);
        if (hOutcoming != NULL) {
          // Mapovani sdilene pameti do adresoveho prostoru
          pMapping = MapViewOfFile(hMapping, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0);
          if (pMapping != NULL) {
            // Hlavni smycka klienta
            while (!Terminate) {
              // Cekani na prazdnou sdilenou pamet (nebo sdilenou pamet s odpovedi)
              WaitResult = WaitForSingleObject(hOutcoming, TIMEOUT);
              if (WaitResult == WAIT_OBJECT_0) {
                // Sdilena pamet je prazdna (v pripade, ze klient jeste neodeslal zadnou zpravu),
                // nebo obsahuje odpoved od serveru
                if (!FirstTime) {
                  // Vytisknout odpoved
                  Response = (PCHAR)pMapping;
                  printf("Odpoved: %s\n", Response);
                } else FirstTime = FALSE;

                // Nacteni zpravy
                printf("Zadejte zpracu:\n");
                ZeroMemory(Message, 256);
                scanf("%s", Message);
                // Umisteni zpravy do sdilene pameti
                ZeroMemory(pMapping, FILEMAPPING_SIZE);
                CopyMemory(pMapping, Message, strlen(Message));
                // Odeslani
                SetEvent(hIncoming);
                // Zjisteni, zda se nejedna o zpravu "END"
                Terminate = (strncmp(Message, END_SIGNAL, strlen(END_SIGNAL)) == 0);
              } else {
                // Cekani selhalo. To by se nemelo nikdy stat.
                printf("CHYBA: WaitForSingleObject selhala. Chybovy kod %u. Error %u\n", WaitResult, GetLastError());
                Terminate = TRUE;
              }
            }

            UnmapViewOfFile(pMapping);
          } else printf("CHYBA: MapViewOfFile selhala. Chyba %u\n", GetLastError());

          CloseHandle(hOutcoming);
        } else printf("CHYBA: OpenEventW(OutcomingEvent) selhala. Chyba %u\n", GetLastError());

        CloseHandle(hIncoming);
      } else printf("CHYBA: OpenEventW(IncomingEvent) selhala. Chyba %u\n", GetLastError());

      CloseHandle(hMapping);
    } else printf("CHYBA: OpenFileMappingW selhala. Chyba %u\n", GetLastError());

    HeapFree(GetProcessHeap(), 0, Message);
  } else printf("CHYBA: HeapAlloc selhala. Chyba %u\n", GetLastError());

  return;
}



/** Hlavni rutina programu.
 *
 * Rutina  na zakldae argumentu prikazove radky zavola bud rutinu Server(), nebo
 * rutinu Client() a podle toho se program chova jako serverova, nebo klientska cast.
 *
 * @param argc Pocet argumentu prikazove radky.
 * @param argv Pole retezcu s argumenty prikazove radky.
 *
 * @return Funkce vraci vzdy nulu.
 */
int main(int argc, char **argv)
{
  if (argc == 2) {
    if (strncmp(argv[1], "--server", strlen("--server")) == 0) {
      Server();
	} else if (strncmp(argv[1], "--client", strlen("--client")) == 0) {
      Client();
	} else printf("Syntaxe: fmt (--server | --client)\n");
  } else printf("Syntaxe: fmt (--server | --client)\n");

  return 0;
}
