#include <windows.h>
#include <windowsx.h>
#include <winsock.h>
#include <commctrl.h>
#include "resource.h"

// -----------------------------------------------------
// Описание функций
// -----------------------------------------------------

// Функция главного окна
LRESULT WINAPI
WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

// Функция для обработки сообщения WM_CREATE
BOOL WndProc_OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct);

// Функция для обработки сообщения WM_DESTROY
void WndProc_OnDestroy(HWND hWnd);

// Функция для обработки сообщения WM_COMMAND
void WndProc_OnCommand(HWND hWnd, int id, 
  HWND hwndCtl, UINT codeNotify);

// Функция для обработки сообщения WM_SIZE
void WndProc_OnSize(HWND hWnd, UINT state, int cx, int cy);

// Запуск сервера
void ServerStart(HWND hWnd);

// Останов сервера
void ServerStop(HWND hWnd);

// Обработка сообщения WSA_ACCEPT
void WndProc_OnWSAAccept(HWND hWnd, UINT msg, 
                         WPARAM wParam, LPARAM lParam);

// Обработка сообщения WSA_NETEVENT
void WndProc_OnWSANetEvent(HWND hWnd, UINT msg, 
                       WPARAM wParam, LPARAM lParam);
// Порт сервера
#define SERV_PORT 5000

#define IDS_STATUSBAR 802
// Определение кодов сообщений 
#define WSA_ACCEPT (WM_USER + 1)
#define WSA_NETEVENT   (WM_USER + 2)

// -----------------------------------------------------
// Глобальные переменные
// -----------------------------------------------------

// Идентификатор приложения
HINSTANCE hInst;

// Название приложения
char szAppName[] = "WServer";

// Заголовок главного окна приложения
char szAppTitle[] = "Windows Socket Server Demo";

// Идентификатор органа Statusbar
HWND hwndSb;

// Сокет сервера
SOCKET srv_socket;

// Длина использованного сокета
int acc_sin_len;

// Адрес использованного сокета
SOCKADDR_IN acc_sin;

// Локальный сокет
SOCKADDR_IN local_sin;

// -----------------------------------------------------
// Функция WinMain
// -----------------------------------------------------

int APIENTRY 
WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
        LPSTR lpCmdLine, int nCmdShow)
{
  WNDCLASSEX wc;
  HWND hWnd;
  MSG msg;

  hInst = hInstance;

  // Преверяем, не было ли это приложение запущено ранее
  hWnd = FindWindow(szAppName, NULL);
  if(hWnd)
  {
  	// Если окно приложения было свернуто в пиктограмму,
	  // восстанавливаем его
    if(IsIconic(hWnd))
	  ShowWindow(hWnd, SW_RESTORE);

    // Выдвигаем окно приложения на передний план
	  SetForegroundWindow(hWnd);
    return FALSE;
  }

  // Регистрируем класс окна
  memset(&wc, 0, sizeof(wc));
  
  // Поля wc.cbSize	и wc.hIconSm определены в структуре
  // WNDCLASSEX, которой можно пользоваться для
  // регистрации класса окна в Windows 95
  wc.cbSize = sizeof(WNDCLASSEX);

  // Поле wc.hIconSm задает идентификатор маленькой
  // пиктограммы, которая будет отображаться в левой
  // части заголовка окна (в области системного меню).
  // Загружаем пиктограмму из ресурсов приложения при
  // помощи функции LoadImage, так как функция
  // LoadIcon может загрузить только обычную пиктограмму
  wc.hIconSm = LoadImage(hInst,
    MAKEINTRESOURCE(IDI_APPICON_SM), IMAGE_ICON, 16, 16, 0);
  
  // Завершаем заполнение структуры WNDCLASSEX
  wc.style = CS_HREDRAW | CS_VREDRAW;
  wc.lpfnWndProc = (WNDPROC)WndProc;
  wc.cbClsExtra  = 0;
  wc.cbWndExtra  = 0;
  wc.hInstance = hInst;
  
  // Для загрузки обычной пиктограммы вы можете
  // использовать функцию LoadImage
  wc.hIcon = LoadImage(hInst,
    MAKEINTRESOURCE(IDI_APPICON), IMAGE_ICON, 32, 32, 0);
  
  wc.hCursor = LoadCursor(NULL, IDC_ARROW);
  wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
  wc.lpszMenuName = MAKEINTRESOURCE(IDR_MENU1);
  wc.lpszClassName = szAppName;

  // Вызываем функцию RegisterClassEx, которая выполняет
  // регистрацию окна
  if(!RegisterClassEx(&wc))

    // В случае ошибки пытаемся зарегистрировать окно
    // функцией RegisterClass
    if(!RegisterClass((LPWNDCLASS)&wc.style))
	  return FALSE;
    
  // Инициализация библиотеки органов управления
  // общего назначения. Необходима для работы с
  // органом управления Statusbar
  InitCommonControls();
  
  // Создаем главное окно приложения
  hWnd = CreateWindow(szAppName, szAppTitle, WS_OVERLAPPEDWINDOW,
   	 CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInst, NULL);
  if(!hWnd) return(FALSE);

  // Отображаем окно
  ShowWindow(hWnd, nCmdShow);
  UpdateWindow(hWnd);

  // Запускаем цикл обработки сообщений
  while(GetMessage(&msg, NULL, 0, 0))
  {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
  }
  return msg.wParam;
}

// -----------------------------------------------------
// Функция WndProc
// -----------------------------------------------------

LRESULT WINAPI
WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
  switch(msg)
  {
    // Вызываем обработчик сообщения WSA_ACCEPT
    case WSA_ACCEPT:
      WndProc_OnWSAAccept(hWnd, msg, wParam, lParam);
      break;
    
    // Вызываем обработчик сообщения WSA_NETEVENT
    case WSA_NETEVENT:
      WndProc_OnWSANetEvent(hWnd, msg, wParam, lParam);
      break;
    
	  // Для сообщения WM_CREATE назначаем обработчик,
	  // расположенный в функции WndProc_OnCreate
    HANDLE_MSG(hWnd, WM_CREATE, WndProc_OnCreate);

    // Для сообщения WM_COMMAND назначаем обработчик,
	  // расположенный в функции WndProc_OnCommand
    HANDLE_MSG(hWnd, WM_COMMAND, WndProc_OnCommand);

    // Для сообщения WM_SIZE назначаем обработчик,
	  // расположенный в функции WndProc_OnSize
    HANDLE_MSG(hWnd, WM_SIZE, WndProc_OnSize);

    // Для сообщения WM_DESTROY назначаем обработчик,
	  // расположенный в функции WndProc_OnDestroy
    HANDLE_MSG(hWnd, WM_DESTROY, WndProc_OnDestroy);

	  default:
      return(DefWindowProc(hWnd, msg, wParam, lParam));
  }
}

// -----------------------------------------------------
// Функция WndProc_OnCreate
// -----------------------------------------------------

BOOL WndProc_OnCreate(HWND hWnd, 
                      LPCREATESTRUCT lpCreateStruct)
{
  int rc;
  WSADATA WSAData;
  char szTemp[128];

  // Инициализация и проверка версии Windows Sockets
  rc = WSAStartup(MAKEWORD(1, 1), &WSAData);
  if(rc != 0)
  {
    MessageBox(NULL, "WSAStartup Error", "Error", MB_OK);
    return FALSE;
  }

  // Отображаем описание и версию системы Windows Sockets
  // в окне органа управления Statusbar
  wsprintf(szTemp, "Server use %s %s", 
    WSAData.szDescription,WSAData.szSystemStatus);

  hwndSb = CreateStatusWindow(WS_CHILD | WS_VISIBLE 
    | WS_BORDER | SBARS_SIZEGRIP, 
    szTemp, hWnd, IDS_STATUSBAR);

  return TRUE;
}

// -----------------------------------------------------
// Функция WndProc_OnDestroy
// -----------------------------------------------------

// Отключаем предупреждающее сообщение о том, что
// функция типа void возвращает управление при помощи
// оператора return. Этот оператор нужен для
// использования макрокоманды FORWARD_WM_LBUTTONDOWN
#pragma warning(disable: 4098)
void WndProc_OnDestroy(HWND hWnd)
{
  // Освобождение ресурсов, полученных для
  // работы с Windows Sockets
  WSACleanup();

  // Завершение цикла обработки сообщений
  PostQuitMessage(0);
  return FORWARD_WM_DESTROY(hWnd, DefWindowProc);
}

// -----------------------------------------------------
// Функция WndProc_OnSize
// -----------------------------------------------------

#pragma warning(disable: 4098)
void 
WndProc_OnSize(HWND hWnd, UINT state, int cx, int cy)
{
  SendMessage(hwndSb, WM_SIZE, cx, cy);
  return FORWARD_WM_SIZE(hWnd, state, cx, cy, DefWindowProc);
}

// -----------------------------------------------------
// Функция WndProc_OnCommand
// -----------------------------------------------------

#pragma warning(disable: 4098)
void 
WndProc_OnCommand(HWND hWnd, int id, 
  HWND hwndCtl, UINT codeNotify)
{
  switch (id)
  {
    case IDM_EXIT:
      
      // Уничтожение главного окна прилоджения
      DestroyWindow(hWnd);
      break;

    case IDM_START:
      
      // Запуск сервера
      ServerStart(hWnd);
      break;
    
    case IDM_STOP:

      // Останов сервера
      ServerStop(hWnd);
      break;

    default:
      MessageBox(NULL, "Unknown command", "Error", MB_OK);
  }

  return FORWARD_WM_COMMAND(hWnd, id, hwndCtl,
    codeNotify, DefWindowProc);
}

// -----------------------------------------------------
// Функция ServerStart
// -----------------------------------------------------

void ServerStart(HWND hWnd)
{
  struct sockaddr_in srv_address;
  int rc;
  
  // Создаем сокет сервера для работы с потоком данных
  srv_socket = socket(AF_INET, SOCK_STREAM, 0);
  if(srv_socket == INVALID_SOCKET)
  {
    MessageBox(NULL, "socket Error", "Error", MB_OK);
    return;
  }

  // Устанавливаем адрес IP и номер порта
  srv_address.sin_family = AF_INET;
  srv_address.sin_addr.s_addr = INADDR_ANY;
  srv_address.sin_port = htons(SERV_PORT);

  // Связываем адрес IP с сокетом  
  if(bind(srv_socket, (LPSOCKADDR)&srv_address, 
    sizeof(srv_address)) == SOCKET_ERROR)
  {
    // При ошибке закрываем сокет
    closesocket(srv_socket);
    MessageBox(NULL, "bind Error", "Error", MB_OK);
    return;
  }

  // Устанавливаем сокет в режим приема для
  // выполнения ожидания соединения с клиентом
  if(listen(srv_socket, 1) == SOCKET_ERROR)
  {
    closesocket(srv_socket);
    MessageBox(NULL, "listen Error", "Error", MB_OK);
    return;
  }

  // При попытке установки соединения главное окно приложения
  // получит сообщение WSA_ACCEPT
  rc = WSAAsyncSelect(srv_socket, hWnd, WSA_ACCEPT, FD_ACCEPT);
  if(rc > 0)
  {
    closesocket(srv_socket);
    MessageBox(NULL, "WSAAsyncSelect Error", "Error", MB_OK);
    return;
  }

  // Выводим в окна Statusbar сообщение о запуске сервера
  SendMessage(hwndSb, SB_SETTEXT, 0, 
    (LPARAM)"Server started");
}

// -----------------------------------------------------
// Функция ServerStop
// -----------------------------------------------------

void ServerStop(HWND hWnd)
{
  // Отменяем приход любых извещений в главную функцию
  // окна при возникновении любых событий, связанных
  // с системой Windows Sockets
  WSAAsyncSelect(srv_socket, hWnd, 0, 0);
  
  // Если сокет был создан, закрываем его
  if(srv_socket != INVALID_SOCKET)
  {
    closesocket(srv_socket);
  }

  // Выводим в окна Statusbar сообщение об останове сервера
  SendMessage(hwndSb, SB_SETTEXT, 0, 
    (LPARAM)"Server stopped");
}

// -----------------------------------------------------
// Функция WndProc_OnWSAAccept
// -----------------------------------------------------

void WndProc_OnWSAAccept(HWND hWnd, UINT msg, 
                         WPARAM wParam, LPARAM lParam)
{
  int rc;

  // При ошибке отменяем поступление извещений
  // в главное окно приложения
  if(WSAGETSELECTERROR(lParam) != 0)
  {
    MessageBox(NULL, "accept Error", "Error", MB_OK);
    WSAAsyncSelect(srv_socket, hWnd, 0, 0);
    return;
  }

  // Определяем размер адреса сокета
  acc_sin_len = sizeof(acc_sin);

  // Разрешаем установку соединения
  srv_socket = accept(srv_socket, (LPSOCKADDR)&acc_sin,
    (int FAR *)&acc_sin_len);

  if(srv_socket == INVALID_SOCKET)
  {
    MessageBox(NULL, "accept Error, invalid socket",
      "Error", MB_OK);
    return;
  }

  // Если на данном сокете начнется передача данных от
  // клиента, в главное окно приложения поступит сообщение WSA_NETEVENT.
  // Это же сообщение поступит при разрыве соединения
  rc = WSAAsyncSelect(srv_socket, hWnd, WSA_NETEVENT, 
    FD_READ | FD_CLOSE);
  if(rc > 0)
  {
    closesocket(srv_socket);
    MessageBox(NULL, "WSAAsyncSelect Error", "Error", MB_OK);
    return;
  }
}

// -----------------------------------------------------
// Функция WndProc_OnWSANetEvent
// -----------------------------------------------------

void WndProc_OnWSANetEvent(HWND hWnd, UINT msg, 
                       WPARAM wParam, LPARAM lParam)
{
  char szTemp[256];
  int rc;
  
  // Если на сокете выполняется передача данных,
  // принимаем и отображаем эти данные в виде
  // текстовой строки
  if(WSAGETSELECTEVENT(lParam) == FD_READ)
  {
    rc = recv((SOCKET)wParam, szTemp, 256, 0);
    if(rc)
    {
      szTemp[rc] = '\0';
      MessageBox(NULL, szTemp, "Reсeived data", MB_OK);
    }
    return;
  }
  
  // Если соединение завершено, выводми об этом сообщение
  else if(WSAGETSELECTEVENT(lParam) == FD_CLOSE)
  {
    MessageBox(NULL, "Connection closed", "Server", MB_OK);
  }
}

