#define STRICT
#include <windows.h>
#include <windowsx.h>
#include <stdio.h>
#include "resource.h"
#include "afxres.h"
#include "semmdi.h"

// Имена классов окна
char const szFrameClassName[] = "MDISemAppClass";
char const szChildClassName[] = "MDISemChildAppClass";

// Заголовок окна
char const szWindowTitle[] = 
  "Multithread MDI Application with Semaphores";

HINSTANCE hInst;

HWND hwndFrame;  // окно Frame Window
HWND hwndClient; // окно Client Window
HWND hwndChild;  // окно Child Window

// Структура, которая создается для каждого дочернего окна
typedef struct _CHILD_WINDOW_TAG
{
  // Признак активности задачи
  BOOL fActive;

  // Флаг ожидания семафора
  BOOL fWaiting;
  
  // Критическая секция для рисования в окне
  CRITICAL_SECTION csChildWindowPaint; 

  // Идентификатор задачи
  HANDLE hThread;
} CHILD_WINDOW_TAG;

typedef CHILD_WINDOW_TAG *LPCHILD_WINDOW_TAG;

// Семафор для ограничения количества работающих задач
HANDLE hSemaphore;

// Имя семафора
char const lpSemaphoreName[] =
 "$MyVerySpecialSemaphoreName$For$SemMDI$Application$";

// =====================================
// Функция WinMain
// =====================================
int APIENTRY 
WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
        LPSTR lpCmdLine, int nCmdShow)
{
  MSG  msg;          // структура для работы с сообщениями
  hInst = hInstance; // сохраняем идентификатор приложения

  // Инициализируем приложение
  if(!InitApp(hInstance))
    return FALSE;

  // Создаем семафор для ограничения количества
  // задач, работающих одновременно
  hSemaphore = CreateSemaphore(NULL, 2, 2, lpSemaphoreName);
  if(hSemaphore == NULL)
  {
    MessageBox(NULL,
      "Ошибка при создании семафора",
       szWindowTitle, MB_OK | MB_ICONEXCLAMATION);
    return FALSE;
  }
  
  // Создаем главное окно приложения - Frame Window
  hwndFrame = CreateWindow(
    szFrameClassName,    // имя класса окна
    szWindowTitle,       // заголовок окна
    WS_OVERLAPPEDWINDOW, // стиль окна
    CW_USEDEFAULT, 0,    // задаем размеры и расположение
    CW_USEDEFAULT, 0,    // окна, принятые по умолчанию
    0,                   // идентификатор родительского окна
    0,                   // идентификатор меню
    hInstance,           // идентификатор приложения
    NULL);   // указатель на дополнительные параметры

  // Если создать окно не удалось, завершаем приложение
  if(!hwndFrame)
    return FALSE;

  // Рисуем главное окно
  ShowWindow(hwndFrame, nCmdShow);
  UpdateWindow(hwndFrame);

  // Запускаем цикл обработки сообщений
  while(GetMessage(&msg, NULL, 0, 0))
  {
    // Трансляция для MDI-приложения
    if(!TranslateMDISysAccel(hwndClient, &msg))
    {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
    }
  }
  
  CloseHandle(hSemaphore);
  return msg.wParam;
}

// =====================================
// Функция InitApp
// Выполняет регистрацию класса окна
// =====================================

BOOL InitApp(HINSTANCE hInstance)
{
  ATOM aWndClass; // атом для кода возврата
  WNDCLASS wc;    // структура для регистрации

  // Регистрируем класс для главного окна приложения
  // (для окна Frame Window)
  memset(&wc, 0, sizeof(wc));
  wc.lpszMenuName  = "APP_MENU";
  wc.style         = CS_HREDRAW | CS_VREDRAW;
  wc.lpfnWndProc   = (WNDPROC)FrameWndProc;
  wc.cbClsExtra    = 0;
  wc.cbWndExtra    = 0;
  wc.hInstance     = hInstance;
  wc.hIcon         = LoadIcon(hInstance, "APP_ICON");
  wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
  wc.hbrBackground = (HBRUSH)(COLOR_APPWORKSPACE + 1);
  wc.lpszClassName = (LPSTR)szFrameClassName;
  aWndClass = RegisterClass(&wc);

  if(!aWndClass)
    return FALSE;

  // Регистрируем класс окна для 
  //дочернего окна Document Window
  memset(&wc, 0, sizeof(wc));
  wc.lpszMenuName  = 0;
  wc.style         = CS_HREDRAW | CS_VREDRAW;
  wc.lpfnWndProc   = (WNDPROC)ChildWndProc;
  wc.cbClsExtra    = 0;
  wc.cbWndExtra    = 0;
  wc.hInstance     = hInstance;
  wc.hIcon         = LoadIcon(hInstance, "APPCLIENT_ICON");
  wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
  wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
  wc.lpszClassName = (LPSTR)szChildClassName;
  aWndClass = RegisterClass(&wc);

  if(!aWndClass)
    return FALSE;

  return TRUE;
}

// =====================================
// Функция FrameWndProc
// =====================================

LRESULT CALLBACK 
FrameWndProc(HWND hwnd, UINT msg, 
             WPARAM wParam, LPARAM lParam)
{
  HWND   hwndChild;
  HANDLE hThread;
  DWORD  dwIDThread;
  CHAR   szBuf[255];
  LPCHILD_WINDOW_TAG lpMyWndTag;
  
  // Структура для создания окна Client Window
  CLIENTCREATESTRUCT clcs;

  // Указатель на структуру для хранения 
  // состояния дочернего окна
  LPCHILD_WINDOW_TAG lpTag;

  switch (msg)
  {
    // При создании окна Frame Window создаем
    // окно Client Window, внутри которого будут создаваться
    // дочерние окна Document Window
    case WM_CREATE:
    {
      clcs.hWindowMenu = GetSubMenu(GetMenu(hwnd), 2);
      clcs.idFirstChild = 500;
      
      // Создаем окно Client Window 
      hwndClient = CreateWindow(
        "MDICLIENT",    // имя класса окна
        NULL,           // заголовок окна
        WS_CHILD | WS_CLIPCHILDREN | WS_VISIBLE | 
          WS_HSCROLL | WS_VSCROLL,  
        0, 0, 0, 0,
        hwnd,           // идентификатор родительского окна
        (HMENU)1,       // идентификатор меню
        hInst,          // идентификатор приложения
        (LPSTR)&clcs);  // указатель на дополнительные параметры

      break;
    }

    // Обработка сообщений от главного меню приложения
    case WM_COMMAND:
    {
      switch (wParam)
      {
        // Создание нового окна Document Window
        case CM_FILENEW:
        {
          hwndChild = CreateMDIWindow(
            (LPSTR)szChildClassName,              // класс окна
            "MDI Child Window",                   // заголовок окна
            0,                              // дополнительные стили
            CW_USEDEFAULT, CW_USEDEFAULT,   // размеры окна 
            CW_USEDEFAULT, CW_USEDEFAULT,   //    Document Window
            hwndClient,
            hInst,                      // идентификатор приложения
            0);                         // произвольное значение

          // Получаем память для структуры, в которой будет
          // хранится состояние окна
          lpTag = malloc(sizeof(CHILD_WINDOW_TAG));
  
          // Устанавливаем признак активности
          lpTag->fActive = 1;

          // Инициализируем критическую секцию
          InitializeCriticalSection(&(lpTag->csChildWindowPaint));
          
          // Устанавливаем адрес структуры состояния в
          // памяти окна
          SetWindowLong(hwndChild, GWL_USERDATA, (LONG)lpTag);

          // Создаем задачу для дочернего окна
          hThread = CreateThread(NULL, 0, 
            (LPTHREAD_START_ROUTINE)ThreadRoutine,
            (LPVOID)hwndChild, 0,(LPDWORD)&dwIDThread);

          if(hThread == NULL)
          {
            MessageBox(hwnd,"Ошибка при создании задачи",
	           szWindowTitle, MB_OK | MB_ICONEXCLAMATION);
          }

          // Сохраняем идентификатор созданной задачи
          lpTag->hThread = hThread;
          
          // Отображаем идентификатор задачи в заголовке
          // дочернего окна
          sprintf(szBuf, "Thread ID = %lX", dwIDThread);
          SetWindowText(hwndChild, szBuf);
   
          break;
        }

        // Увеличиваем содержимое счетчика семафора
        // на единицу
        case ID_SEMAPHORE_INCREMENT:
        {
          ReleaseSemaphore(hSemaphore, 1, NULL);
          break;
        }
        
        // Размещение окон Document Window рядом друг с другом
        case CM_WINDOWTILE:
        {
          SendMessage(hwndClient, WM_MDITILE, 0, 0);
          break;
        }

        // Размещение окон Document Window с перекрытием
        case CM_WINDOWCASCADE:
        {
          SendMessage(hwndClient, WM_MDICASCADE, 0, 0);
          break;
        }

        // Размещение пиктограм минимизированых окон 
        // Document Window в нижней части окна Client Window
        case CM_WINDOWICONS:
        {
          SendMessage(hwndClient, WM_MDIICONARRANGE, 0, 0);
          break;
        }

        // Уничтожение всех окон Document Window
        case CM_WINDOWCLOSEALL:
        {
          HWND hwndTemp;

          ShowWindow(hwndClient, SW_HIDE);

          while(TRUE) 
          {
            // Получаем идентификатор дочернего окна 
            // для окна Client Window
            hwndTemp = GetWindow(hwndClient, GW_CHILD);
            // Если дочерних окон больше нет, выходим из цикла
            if(!hwndTemp)
              break;

            // Пропускаем окна-заголовки
            while(hwndTemp && GetWindow(hwndTemp, GW_OWNER))
              hwndTemp = GetWindow(hwndTemp, GW_HWNDNEXT);

            // Удаляем дочернее окно Document Window
            if(hwndTemp)
            {
              // Завершаем задачу, запущенную для окна
              lpMyWndTag = 
                 (LPCHILD_WINDOW_TAG)GetWindowLong(
                 hwndTemp, GWL_USERDATA);
              lpMyWndTag->fActive = 0;  

              // Посылаем сообщение, в ответ на которое
              // окно будет удалено
              SendMessage(hwndClient, WM_MDIDESTROY, 
                (WPARAM)hwndTemp, 0);
            }
            else
              break;
          }

          // Отображаем окно Client Window
          ShowWindow(hwndClient, SW_SHOW);

          break;
        }

        // Устанавливаем классы приоритета процесса
        case ID_PRIORITYCLASS_REALTIME:
        {
          SetPriorityClass(GetCurrentProcess(),
            REALTIME_PRIORITY_CLASS);
          break;
        }
        case ID_PRIORITYCLASS_HIGH:
        {
          SetPriorityClass(GetCurrentProcess(),
            HIGH_PRIORITY_CLASS);
          break;
        }
        case ID_PRIORITYCLASS_NORMAL:
        {
          SetPriorityClass(GetCurrentProcess(),
            NORMAL_PRIORITY_CLASS);
          break;
        }
        case ID_PRIORITYCLASS_IDLE:
        {
          SetPriorityClass(GetCurrentProcess(),
            IDLE_PRIORITY_CLASS);
          break;
        }

        case CM_HELPABOUT:
        {
	        MessageBox(hwnd,
	          "Демонстрация использования мультизадачности\n"
            "в MDI-приложениях\n"
            "(C) Alexandr Frolov, 1996\n"
            "Email: frolov@glas.apc.org",
            szWindowTitle, MB_OK | MB_ICONINFORMATION);
          break;
        }

        // Завершаем работу приложения
        case CM_FILEEXIT:
        {
          DestroyWindow(hwnd);
          break;
        }

        default:
          break;
      }

      // Определяем идентификатор активного окна 
      // Document Window
      hwndChild =
        (HWND)LOWORD(SendMessage(hwndClient, 
        WM_MDIGETACTIVE, 0, 0l));

      // Если это окно, посылаем ему сообщение WM_COMMAND
      if(IsWindow(hwndChild))
        SendMessage(hwndChild, WM_COMMAND, wParam, lParam);

      return DefFrameProc(
        hwnd, hwndClient, msg, wParam, lParam);
    }

    case WM_DESTROY:
    {
      PostQuitMessage(0);
      break;
    }

    default:
      break;
  }
  return DefFrameProc(hwnd, hwndClient, msg, wParam, lParam);
}

// =====================================
// Функция ChildWndProc
// =====================================

LRESULT CALLBACK
ChildWndProc(HWND hwnd, UINT msg, 
             WPARAM wParam, LPARAM lParam)
{
  HDC hdc;
  PAINTSTRUCT ps;
  RECT rc;
  LPCHILD_WINDOW_TAG lpMyWndTag;
  HMENU hmenuPopup;
  POINT pt;
  CHAR szBuf[256];

  switch (msg)
  {
    case WM_PAINT:
    {
      // Получаем адрес структуры состояния окна
      lpMyWndTag = 
        (LPCHILD_WINDOW_TAG)GetWindowLong(hwnd, GWL_USERDATA);

      // Входим в критическую секцию
      EnterCriticalSection(&(lpMyWndTag->csChildWindowPaint));

      // Перерисовываем внутреннюю область дочернего окна
      hdc = BeginPaint(hwnd, &ps);
      GetClientRect(hwnd, &rc);

      if(lpMyWndTag->fWaiting)
        DrawText(hdc, "Waiting...", -1, &rc,
          DT_SINGLELINE | DT_CENTER | DT_VCENTER);
      else
        DrawText(hdc, "Child Window. Running", -1, &rc,
          DT_SINGLELINE | DT_CENTER | DT_VCENTER);

      EndPaint(hwnd, &ps);

      // Выходим из критической секции
      LeaveCriticalSection(&(lpMyWndTag->csChildWindowPaint));
      break;
    }

    case WM_CLOSE:
    {
      // Сбрасываем признак активности задачи
      lpMyWndTag = 
        (LPCHILD_WINDOW_TAG)GetWindowLong(hwnd, GWL_USERDATA);

      // Если задача находится в состоянии ожидания 
      // семафора, запрещаем удаление окна
      if(lpMyWndTag->fWaiting)
        return 0;
      
      // Если задача не ожидает семафор, завершаем
      // задачу и разрешаем удаление окна
      else
        lpMyWndTag->fActive = 0;  
      
      break;
    }

    // Когда пользователь нажимает правую кнопку мыши 
    // в дочернем окне, отображаем плавающее меню
    case WM_RBUTTONDOWN:
    {
      pt.x = LOWORD(lParam);
      pt.y = HIWORD(lParam);
      ClientToScreen(hwnd, &pt);

      hmenuPopup = GetSubMenu(
        LoadMenu(hInst, "IDR_POPUPMENU"), 0);
      TrackPopupMenu(hmenuPopup, 
        TPM_CENTERALIGN | TPM_LEFTBUTTON,
        pt.x, pt.y, 0, hwnd, NULL);
      DestroyMenu(hmenuPopup);
      break;
    }

    // Обрабатываем команды, поступающие от плавающего меню
    case WM_COMMAND:
    {
      switch (wParam)
      {
        // Приостановка выполнения задачи
        case ID_THREADCONTROL_SUSPEND:
        {
          lpMyWndTag = 
            (LPCHILD_WINDOW_TAG)GetWindowLong(hwnd, GWL_USERDATA);
          
          // Входим в критическую секцию
          EnterCriticalSection(&(lpMyWndTag->csChildWindowPaint));

          SuspendThread(lpMyWndTag->hThread);

		      // Выходим из критической секции
          LeaveCriticalSection(&(lpMyWndTag->csChildWindowPaint));

          break;
        }
        
        // Возобновление выполнения задачи
        case ID_THREADCONTROL_RESUME:
        {
   
          lpMyWndTag = 
            (LPCHILD_WINDOW_TAG)GetWindowLong(hwnd, GWL_USERDATA);
          ResumeThread(lpMyWndTag->hThread);
          break;
        }

        // Изменение относительного приоритета
        case ID_THREADCONTROL_PRIORITYLOWEST:
        {
          lpMyWndTag = 
            (LPCHILD_WINDOW_TAG)GetWindowLong(hwnd, GWL_USERDATA);
          SetThreadPriority(lpMyWndTag->hThread, 
            THREAD_PRIORITY_LOWEST);
          break;
        }
        case ID_THREADCONTROL_PRIORITYNORMAL:
        {
          lpMyWndTag = 
            (LPCHILD_WINDOW_TAG)GetWindowLong(hwnd, GWL_USERDATA);
          SetThreadPriority(lpMyWndTag->hThread, 
            THREAD_PRIORITY_NORMAL);
          break;
        }
        case ID_THREADCONTROL_PRIORITYHIGHEST:
        {
          lpMyWndTag = 
            (LPCHILD_WINDOW_TAG)GetWindowLong(hwnd, GWL_USERDATA);
          SetThreadPriority(lpMyWndTag->hThread, 
            THREAD_PRIORITY_HIGHEST);
          break;
        }

        // Определение и отображение относительного приоритета
        case ID_THREADCONTROL_GETPRIORITY:
        {
          lpMyWndTag = 
            (LPCHILD_WINDOW_TAG)GetWindowLong(hwnd, GWL_USERDATA);
          
          strcpy(szBuf, "Thread priority: ");
          
          switch (GetThreadPriority(lpMyWndTag->hThread))
          {
            case THREAD_PRIORITY_LOWEST:
            {
              strcat(szBuf, "THREAD_PRIORITY_LOWEST");
              break;
            }
            case THREAD_PRIORITY_BELOW_NORMAL:
            {
              strcat(szBuf, "THREAD_PRIORITY_BELOW_NORMAL");
              break;
            }
            case THREAD_PRIORITY_NORMAL:
            {
              strcat(szBuf, "THREAD_PRIORITY_NORMAL");
              break;
            }
            case THREAD_PRIORITY_ABOVE_NORMAL:
            {
              strcat(szBuf, "THREAD_PRIORITY_ABOVE_NORMAL");
              break;
            }
            case THREAD_PRIORITY_HIGHEST:
            {
              strcat(szBuf, "THREAD_PRIORITY_HIGHEST");
              break;
            }
          }

	        MessageBox(hwnd, szBuf,
            szWindowTitle, MB_OK | MB_ICONINFORMATION);
          
          break;
        }

        // Удаление задачи
        case ID_THREADCONTROL_KILLTHREAD:
        {
          lpMyWndTag = 
            (LPCHILD_WINDOW_TAG)GetWindowLong(hwnd, GWL_USERDATA);
          TerminateThread(lpMyWndTag->hThread, 5);
          break;
        }
        
        // Уничтожение дочернего окна
        case ID_THREADCONTROL_CLOSEWINDOW:
        {
          lpMyWndTag = 
            (LPCHILD_WINDOW_TAG)GetWindowLong(hwnd, GWL_USERDATA);

          // Если задача не находится в состоянии ожидания
          // семафора, удаляем окно и завершаем работу 
          // задачи, запущенной для него
          if(!lpMyWndTag->fWaiting)
          {
            SendMessage(hwndClient, WM_MDIDESTROY, 
                (WPARAM)hwnd, 0);

            lpMyWndTag->fActive = 0;  
          }
          break;
        }
        default:
          break;
      }
      break;
    }
    default:
      break;
  }
  return DefMDIChildProc(hwnd, msg, wParam, lParam);
}

// =====================================
// Функция ThreadRoutine
// Задача, которая выполняется для каждого
// дочернего окна
// =====================================

DWORD ThreadRoutine(HWND hwnd) 
{
  LONG lThreadWorking = 1L;
  HDC hDC;
  RECT rect;
  LONG xLeft, xRight, yTop, yBottom;
  short nRed, nGreen, nBlue;
  HBRUSH hBrush, hOldBrush;
  LPCHILD_WINDOW_TAG lpMyWndTag;

  // Получаем указатель на структуру параметров окна 
  lpMyWndTag = 
    (LPCHILD_WINDOW_TAG)GetWindowLong(hwnd, GWL_USERDATA);

  // Если произошла ошибка, завершаем работу задачи
  if(lpMyWndTag == NULL)
  {
    return 0;
  }

  // Устанавливаем флаг ожидания семафора
  lpMyWndTag->fWaiting = TRUE;

  // Выполняем ожидание семафора. Если оно завершилось
  // с ошибкой, завершаем работу задачи
  if(WAIT_FAILED == 
    WaitForSingleObject(hSemaphore, INFINITE))
  {
    return 0;
  }

  // Сбрасываем флаг ожидания семафора
  lpMyWndTag->fWaiting = FALSE;

  srand((unsigned int)hwnd);
  
  while(TRUE) 
  {
    if(!lpMyWndTag->fActive)
      break;

    EnterCriticalSection(&(lpMyWndTag->csChildWindowPaint));

    hDC = GetDC(hwnd);
    nRed   = rand() % 255;
    nGreen = rand() % 255;
    nBlue  = rand() % 255;
    
    GetWindowRect(hwnd, &rect);
    xLeft   = rand() % (rect.left   + 1);
    xRight  = rand() % (rect.right  + 1);
    yTop    = rand() % (rect.top    + 1);
    yBottom = rand() % (rect.bottom + 1);

    hBrush = CreateSolidBrush(RGB(nRed, nGreen, nBlue));
    hOldBrush = SelectObject(hDC, hBrush);

    Ellipse(hDC, min(xLeft, xRight), min(yTop, yBottom),
                 max(xLeft, xRight), max(yTop, yBottom));

    SelectObject(hDC, hOldBrush);
    DeleteObject(hBrush);
    ReleaseDC(hwnd, hDC);
    
    LeaveCriticalSection(&(lpMyWndTag->csChildWindowPaint));
    Sleep(1);
  }

  DeleteCriticalSection(&(lpMyWndTag->csChildWindowPaint));
  free(lpMyWndTag);
  
  // Перед завершением работы задачи увеличиваем
  // на единицу счетчик семафора, разрешая работу
  // других задач, находящихся в состоянии ожидания
  if(hSemaphore != NULL)
    ReleaseSemaphore(hSemaphore, 1, NULL);

  return 0;
}

