// ==================================================
// Приложение OEM2CHAR
// Демонстрация использования файловых операций
// для перекодировки текстовых файлов
//
// (С) Фролов А.В., 1996
// Email: frolov@glas.apc.org
// ==================================================

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

// Различные режимы работы с файлами

#define SYNCHRONOUS_IO  1 // синхронные операции
#define ASYNCHRONOUS_IO 2 // асинхроные операции
#define MEMORYMAPPED_IO 3 // отображение на память

// Для использования различных режимов работы 
// с файлами используйте только одну из 
// приведенных ниже трех строк

#define FILEOP SYNCHRONOUS_IO
//#define FILEOP ASYNCHRONOUS_IO
//#define FILEOP MEMORYMAPPED_IO

#include "oem2char.h"

HINSTANCE hInst;
char szAppName[]  = "Oem2CharApp";
char szAppTitle[] = "Text File Converter";

// Тип преобразования:
//  OEM -> ANSI или ANSI -> OEM
BOOL fConversionType = OEM_TO_ANSI;

// Идентификатор файла, который будет
// перекодирован в соответствии с содержимым
// глобальной переменной fConversionType
HANDLE hSrcFile;

// Эти определения используются для всех способов
// работы с файлами, кроме отображения на память
#if FILEOP != MEMORYMAPPED_IO

  // Идентификатор файла, в который будет записан
  // результат перекодировки
  HANDLE hDstFile; 
  
  // Буфер для работы с файлами
  CHAR   cBuf[2048];

#endif
  
// -----------------------------------------------------
// Функция 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 = sizeof(WNDCLASSEX);
  wc.hIconSm = LoadImage(hInst,
    MAKEINTRESOURCE(IDI_APPICONSM), 
    IMAGE_ICON, 16, 16, 0);
  wc.style = 0;
  wc.lpfnWndProc = (WNDPROC)WndProc;
  wc.cbClsExtra  = 0;
  wc.cbWndExtra  = 0;
  wc.hInstance = hInst;
  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_APPMENU);
  wc.lpszClassName = szAppName;
  if(!RegisterClassEx(&wc))
    if(!RegisterClass((LPWNDCLASS)&wc.style))
	  return FALSE;
    
  // Создаем главное окно приложения
  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)
  {
    HANDLE_MSG(hWnd, WM_COMMAND, WndProc_OnCommand);
    HANDLE_MSG(hWnd, WM_DESTROY, WndProc_OnDestroy);

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

// -----------------------------------------------------
// Функция WndProc_OnDestroy
// -----------------------------------------------------
#pragma warning(disable: 4098)
void WndProc_OnDestroy(HWND hWnd)
{
  PostQuitMessage(0);
  return 0L;
}

// -----------------------------------------------------
// Функция WndProc_OnCommand
// -----------------------------------------------------
#pragma warning(disable: 4098)
void WndProc_OnCommand(HWND hWnd, int id, 
  HWND hwndCtl, UINT codeNotify)
{
  switch (id)
  {
    // Выполняем преобразование файла
    case ID_FILE_CONVERT:
    {
      // Если не удалось открыть файлы, выводим
      // сообщение об ошибке
      if(!StartConversion(hWnd))
        MessageBox(hWnd, 
          "Conversion Error\n"
          "Unable to open file(s)",
          szAppTitle, MB_OK | MB_ICONEXCLAMATION);

      break;
    }

    case ID_FILE_OPTIONS:
    {
      // Отображаем диалоговую панель, предназначенную
      // для настройки параметров преобразования
      DialogBox(hInst, MAKEINTRESOURCE(IDD_DIALOG1), 
        hWnd, DlgProc);
      break;
    }
    
    case ID_FILE_EXIT:  
    {
	    // Завершаем работу приложения
      PostQuitMessage(0);
	    return 0L;
	    break;
    }
	  
  	case ID_HELP_ABOUT:
    {
      MessageBox(hWnd, 
        "Text File Converter\n"
        "(C) Alexandr Frolov, 1996\n"
        "Email: frolov@glas.apc.org",
        szAppTitle, MB_OK | MB_ICONINFORMATION);
	     return 0L;
	     break;
    }

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

// -----------------------------------------------------
// Функция DlgProc
// -----------------------------------------------------
LRESULT WINAPI
DlgProc(HWND hdlg, UINT msg, WPARAM wParam, 
        LPARAM lParam)
{
  switch(msg)
  {
    HANDLE_MSG(hdlg, WM_INITDIALOG, DlgProc_OnInitDialog);
    HANDLE_MSG(hdlg, WM_COMMAND,    DlgProc_OnCommand);

	default:
    return FALSE;
  }
}

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

BOOL DlgProc_OnInitDialog(HWND hdlg, HWND hwndFocus, 
                          LPARAM lParam)
{
  // При инициализации диалоговой панели включаем
  // переключатель "OEM -> ANSI"
  CheckDlgButton(hdlg, IDC_OEMANSI, 1);
  
  return TRUE;
}

// -----------------------------------------------------
// Функция DlgProc_OnCommand
// -----------------------------------------------------
#pragma warning(disable: 4098)
void DlgProc_OnCommand(HWND hdlg, int id, 
  HWND hwndCtl, UINT codeNotify)
{
  switch (id)
  {
    // Определяем и сохраняем состояние переключателей
    case IDOK:
    {
      if(IsDlgButtonChecked(hdlg, IDC_OEMANSI))
      {
        // Включен режим преобразования из кодировки
        // OEM в кодировку ANSI
        fConversionType = OEM_TO_ANSI;  
      }
      else if(IsDlgButtonChecked(hdlg, IDC_ANSIOEM))
      {
        // Включен режим преобразования из кодировки
        // ANSI в кодировку OEM
        fConversionType = ANSI_TO_OEM;  
      }

      EndDialog(hdlg, 0);
      return TRUE;
    }

    // Если пользователь нажимает кнопку Cancel,
    // завершаем работу диалоговой панели без
    // изменения режима перекодировки
    case IDCANCEL:
    {
      EndDialog(hdlg, 0);
      return TRUE;
    }
  	default:
	    break;
  }
  return FALSE;
}


// -----------------------------------------------------
// Функция StartConversion
// -----------------------------------------------------
BOOL StartConversion(HWND hwnd)
{
  OPENFILENAME ofn;
  char szFile[256];
  char szDirName[256];
  char szFileTitle[256];
  char szFilter[256] = 
    "Text Files\0*.txt\0Any Files\0*.*\0";
  char szDlgTitle[] = "Select source file";
  char szDlgTitleSave[] = "Select destination file";

  // Подготавливаем структуру для выбора исходного файла

  memset(&ofn, 0, sizeof(OPENFILENAME));
  GetCurrentDirectory(sizeof(szDirName), szDirName);
  szFile[0] = '\0';

  ofn.lStructSize     = sizeof(OPENFILENAME);
  ofn.hwndOwner       = hwnd;
  ofn.lpstrFilter     = szFilter;
  ofn.lpstrInitialDir = szDirName;
  ofn.nFilterIndex    = 1;
  ofn.lpstrFile       = szFile;
  ofn.nMaxFile        = sizeof(szFile);
  ofn.lpstrFileTitle  = szFileTitle;
  ofn.nMaxFileTitle   = sizeof(szFileTitle);
  ofn.lpstrTitle      = szDlgTitle;
  ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST
    | OFN_HIDEREADONLY;

  // Выводим на экран диалоговую панель, предназначенную
  // для выбора исходного файла
  if(GetOpenFileName(&ofn))
  {
    // Если файл выбран, открываем его
    if (*ofn.lpstrFile)
    {

#if FILEOP == SYNCHRONOUS_IO
      
      // В синхронном режиме указываем флаг
      // FILE_FLAG_SEQUENTIAL_SCAN
      hSrcFile = CreateFile(ofn.lpstrFile, GENERIC_READ,
        FILE_SHARE_READ, NULL, OPEN_EXISTING,
        FILE_FLAG_SEQUENTIAL_SCAN, NULL);

#elif FILEOP == ASYNCHRONOUS_IO
      
      // В асинхронном режиме необходимо указывать
      // флаг FILE_FLAG_OVERLAPPED
      hSrcFile = CreateFile(ofn.lpstrFile, GENERIC_READ,
        FILE_SHARE_READ, NULL, OPEN_EXISTING,
        FILE_FLAG_OVERLAPPED, NULL);

#elif FILEOP == MEMORYMAPPED_IO

      // В режиме отображения на память мы не используем
      // дополнительные флаги, однако указываем, что
      // файл будет открываться не только для чтения,
      // но и для записи
      hSrcFile = CreateFile(ofn.lpstrFile, 
        GENERIC_READ | GENERIC_WRITE,
        0, NULL, OPEN_EXISTING,  0, NULL);

#endif

    }
    else
      return FALSE;
  }
  else 
    return FALSE;

  // Если исходный файл открыть не удалось,
  // возвращаем значение FALSE
  if(hSrcFile == INVALID_HANDLE_VALUE)
    return FALSE;

#if FILEOP == MEMORYMAPPED_IO

  // В том случае, когда используется отображение
  // файла на память, этот файл перекодируется
  // "по месту". Для этого используется функция
  // Oem2Char с одним параметром.

  Oem2Char(hSrcFile);
  CloseHandle(hSrcFile);
  
#elif FILEOP == SYNCHRONOUS_IO || FILEOP == ASYNCHRONOUS_IO 
  
  // При использовании синхронного или асинхронного
  // режима работы с файлами результат перекодировки
  // будет записан в новый файл. Поэтому в этом
  // случае мы открываем второй файл и используем
  // другой вариант функции Oem2Char - с двумя
  // параметрами
  
  ofn.lpstrTitle = szDlgTitleSave;
  ofn.Flags      = OFN_HIDEREADONLY;
  
  if(GetSaveFileName(&ofn))
  {
    // Если файл выбран, открываем его
    if (*ofn.lpstrFile)
    {

#if FILEOP == SYNCHRONOUS_IO

      // При использовании синхронных операций указываем
      // флаг FILE_FLAG_SEQUENTIAL_SCAN
      hDstFile = CreateFile(ofn.lpstrFile, GENERIC_WRITE,
        0, NULL, CREATE_ALWAYS,
        FILE_FLAG_SEQUENTIAL_SCAN, NULL);

#elif FILEOP == ASYNCHRONOUS_IO

      // При использовании асинхронных операций 
      // необходимо указать флаг FILE_FLAG_OVERLAPPED
      hDstFile = CreateFile(ofn.lpstrFile, GENERIC_WRITE,
        0, NULL, CREATE_ALWAYS,
        FILE_FLAG_OVERLAPPED,  NULL);

#endif
    
    }
    else
      return FALSE;
  }
  else 
    return FALSE;

  // Если выходной файл открыть не удалось,
  // возвращаем значение FALSE
  if(hDstFile == INVALID_HANDLE_VALUE)
    return FALSE;

  // Выполняем перекодировку файла hSrcFile с записью
  // результата перекодировки в файл hDstFile
  Oem2Char(hSrcFile, hDstFile);

  // После перекодировки закрываем оба файла
  CloseHandle(hSrcFile);
  CloseHandle(hDstFile);

#endif
  
  // В случае успеха возвращаем значение TRUE
  return TRUE;
}

// -----------------------------------------------------
// Функция Oem2Char
// -----------------------------------------------------
// Синхронный вариант функции
// -----------------------------------------------------

#if FILEOP == SYNCHRONOUS_IO

void Oem2Char(HANDLE hSrcFile, HANDLE hDstFile)
{
  DWORD dwBytesRead;
  DWORD dwBytesWritten;
  BOOL  bResult;

  // Выполняем перекодировку файла в цикле
  while(TRUE)
  {
    // Читаем блок данных из исходного файла
    // в буфер cBuf
    bResult = ReadFile(hSrcFile, cBuf, 2048, 
      &dwBytesRead, NULL);
    
    // Проверяем, не был ли достигнут конец файла
    if(bResult && dwBytesRead == 0) 
      break;

    // Выполняем преобразование в соответствии с
    // содержимым глобальной переменной fConversionType
    if(fConversionType == OEM_TO_ANSI)
      
      // Преобразование из OEM в ANSI
      OemToCharBuff(cBuf, cBuf, dwBytesRead);
    
    else if(fConversionType == ANSI_TO_OEM)

      // Преобразование из ANSI в OEM
      CharToOemBuff(cBuf, cBuf, dwBytesRead);

    // Запись содержимого буфера в выходной файл
    WriteFile(hDstFile, cBuf, dwBytesRead,
      &dwBytesWritten, NULL);
  }
}

// -----------------------------------------------------
// Функция Oem2Char
// -----------------------------------------------------
// Асинхронный вариант функции
// -----------------------------------------------------

#elif FILEOP == ASYNCHRONOUS_IO

void Oem2Char(HANDLE hSrcFile, HANDLE hDstFile)
{
  DWORD dwBytesRead;
  DWORD dwBytesWritten;
  BOOL  bResult;
  DWORD dwError;

  // Структуры для выполнения асинхронной работы
  OVERLAPPED ovRead;
  OVERLAPPED ovWrite;

  // Инициализация структуры для асинхронного чтения
  ovRead.Offset = 0;
  ovRead.OffsetHigh = 0;
  ovRead.hEvent = NULL;
  
  // Инициализация структуры для асинхронной записи
  ovWrite.Offset = 0;
  ovWrite.OffsetHigh = 0;
  ovWrite.hEvent = NULL;

  // Выполняем перекодировку файла в цикле
  while(TRUE)
  {
    // Запускаем операцию асинхронного чтения
    bResult = ReadFile(hSrcFile, cBuf, sizeof(cBuf), 
      &dwBytesRead, &ovRead);
    
    // Проверяем результат запуска
    if(!bResult) 
    { 
 	    // Если произошла ошибка, анализируем ее код 
      switch (dwError = GetLastError()) 
      { 
        // При достижении конца файла завершаем работу
        // цикла и возвращаемся из функции
        case ERROR_HANDLE_EOF: 
        {
          return;
        }

        // Операция чтения запущена и еще выполняется
        case ERROR_IO_PENDING: 
        { 
          // Здесь вы можете разместить вызов функции,
          // которая будет выполняться параллельно с
          // только что запущенной операцией чтения
          //
          //  IdleWork();
          
          // Перед тем как перейти к перекодировке
          // считанного из файла блока, необходимо
          // дождаться завершения операции
          WaitForSingleObject(hSrcFile, INFINITE);

          // Получаем результат выполнения асинхронной
          // операции чтения
          bResult = GetOverlappedResult(hSrcFile, &ovRead, 
            &dwBytesRead, FALSE);
          
          if(!bResult) 
          { 
    				switch (dwError = GetLastError()) 
            { 
              // При достижении конца файла завершаем работу
              // цикла и возвращаемся из функции
              case ERROR_HANDLE_EOF: 
              {
                return;
              }
              
              default:
                break;
            }
          }
        }

        default:
          break;        
      }
    }

    // Получаем результат выполнения асинхронной
    // операции чтения
    GetOverlappedResult(hSrcFile, &ovRead, 
        &dwBytesRead, FALSE);
 
    // Выполняем преобразование
    if(fConversionType == OEM_TO_ANSI)
      OemToCharBuff(cBuf, cBuf, dwBytesRead);
    
    else if(fConversionType == ANSI_TO_OEM)
      CharToOemBuff(cBuf, cBuf, dwBytesRead);

    // Продвигаем указатель позиции, с которой
    // начнется следующая операция асинхронного
    // чтения на количество считанных байт
    ovRead.Offset += dwBytesRead;

    // Запускаем асинхронную операцию записи
    bResult = WriteFile(hDstFile, cBuf, dwBytesRead,
      &dwBytesWritten, &ovWrite);

    // Если произошла ошибка, анализируем ее код
    if(!bResult) 
    { 
 	    switch (dwError = GetLastError()) 
      { 
        // Операция записи запущена и еще выполняется
        case ERROR_IO_PENDING: 
        { 
          // Здесь вы можете разместить вызов функции,
          // которая будет выполняться параллельно с
          // только что запущенной операцией чтения
          //
          //  IdleWork();
          
          // Получаем результат выполнения асинхронной
          // операции записи
          GetOverlappedResult(hDstFile, &ovWrite, 
            &dwBytesWritten, TRUE);
          
          if(!bResult) 
          { 
            switch (dwError = GetLastError()) 
            { 
              
              default:
                break;
            }
          }
        }

        default:
          break;
      }
    }

    // Продвигаем указатель позиции, с которой
    // начнется следующая операция асинхронной
    // записи на количество записанных байт
    ovWrite.Offset += dwBytesWritten;
  }
}

// -----------------------------------------------------
// Функция Oem2Char
// -----------------------------------------------------
// Вариант функции с использованием отображения 
// на память
// -----------------------------------------------------

#elif FILEOP == MEMORYMAPPED_IO

void Oem2Char(HANDLE hSrcFile)
{
  DWORD dwFileSize;
  HANDLE hFileMapping;
  LPVOID lpFileMap;

  // Получаем и сохраняем размер файла
  dwFileSize = GetFileSize(hSrcFile, NULL);

  // Создаем объект-отображение для исходного файла
  hFileMapping = CreateFileMapping(hSrcFile,
    NULL, PAGE_READWRITE, 0, dwFileSize, NULL);

  // Если создать не удалось, возвращаем управление
  if(hFileMapping == NULL)
    return;

  // Выполняем отображение файла на память.
  // В переменную lpFileMap будет записан указатель на
  // отображаемую область памяти
  lpFileMap = MapViewOfFile(hFileMapping, 
    FILE_MAP_WRITE, 0, 0, 0);

  // Если выполнить отображение не удалось,
  // возвращаем управление
  if(lpFileMap == 0)
    return;
    
  // Выполняем преобразование файла за один прием
  if(fConversionType == OEM_TO_ANSI)
    OemToCharBuff(lpFileMap, lpFileMap, dwFileSize);
    
  else if(fConversionType == ANSI_TO_OEM)
    CharToOemBuff(lpFileMap, lpFileMap, dwFileSize);

  // Отменяем отображение файла
  UnmapViewOfFile(lpFileMap);

  // Освобождаем идентификатор созданного
  // объекта-отображения
  CloseHandle(hFileMapping);
}

#endif
