// ----------------------------------------
// Проигрыватель звуковых компакт-дисков
// ----------------------------------------

#define STRICT
#include <windows.h>
#include <mmsystem.h>
#include <mem.h>
#include <stdlib.h>

#include "mcicdpl.hpp"

#define CD_EMPTY   0
#define CD_READY   1
#define CD_PLAYING 2
#define CD_PAUSED  3

// Прототипы функций
BOOL InitApp(HINSTANCE);
LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);
void mciwioError(DWORD dwrc);
void Play(HWND hwnd, UINT nTrack);


// Имя класса окна
char const szClassName[]   = "MCICDP";

// Заголовок окна
char const szWindowTitle[] = "MCI CD Player";

HINSTANCE hInst;

DWORD dwrc;
UINT nTimerID;

MCI_OPEN_PARMS   MCIOpen;
MCI_SET_PARMS    MCISet;
MCI_STATUS_PARMS MCIStatus;
MCI_PLAY_PARMS   MCIPlay;

BOOL bMediaPresent = FALSE;
BOOL bPaused       = FALSE;
UINT nMode = 0;
UINT nCurTrack = 0;
UINT nTrackCnt = 0;

HWND hwndCurTrack = NULL;

// =====================================
// Функция WinMain
// =====================================
#pragma argsused

int PASCAL
WinMain(HINSTANCE hInstance,
	HINSTANCE hPrevInstance,
	LPSTR     lpszCmdLine, 
	int       nCmdShow)   
{
  MSG  msg;   // структура для работы с сообщениями
  HWND hwnd;  // идентификатор главного окна приложения

  if(hPrevInstance)
    return FALSE;

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

  hInst = hInstance;

  // Открываем устройство чтения компакт-дисков
  MCIOpen.lpstrDeviceType = (LPSTR)MCI_DEVTYPE_CD_AUDIO;
  dwrc = mciSendCommand(NULL, MCI_OPEN,
    MCI_OPEN_TYPE | MCI_OPEN_TYPE_ID,
    (DWORD)(LPVOID)&MCIOpen);
  if(dwrc)
  {
    mciwioError(dwrc);
    return -1;
  }

  // Устанавливаем формат времени
  MCISet.dwTimeFormat = MCI_FORMAT_TMSF;
  dwrc = mciSendCommand(MCIOpen.wDeviceID, MCI_SET,
    MCI_SET_TIME_FORMAT,
    (DWORD)(LPVOID)&MCISet);
  if(dwrc)
  {
    mciwioError(dwrc);
    return -1;
  }

  // Создаем диалоговую панель вместо главного окна
  hwnd = CreateDialog(hInstance, szClassName, 0, NULL);

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

  // Определяем идентификатор поля, которое используется
  // для отображения номера текущей дорожки
  hwndCurTrack = GetDlgItem(hwnd, IDT_CURTRACK);

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

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

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

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

  memset(&wc, 0, sizeof(wc));

  wc.style = 0;
  wc.lpfnWndProc    = (WNDPROC) WndProc;
  wc.cbClsExtra     = 0;
  wc.cbWndExtra     = DLGWINDOWEXTRA;
  wc.hInstance      = hInstance;
  wc.hIcon          = LoadIcon(hInstance, "APPICON");
  wc.hCursor        = LoadCursor(NULL, IDC_ARROW);
  wc.hbrBackground  = NULL;
  wc.lpszMenuName   = (LPSTR)NULL;
  wc.lpszClassName  = (LPSTR)szClassName;

  // Регистрация класса
  aWndClass = RegisterClass(&wc);

  return (aWndClass != 0);
}

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

LRESULT CALLBACK _export
WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
  switch (msg)
  {
    // ---------------------------------------
    // Обработчик сообщения WM_CREATE
    // ---------------------------------------
    case WM_CREATE:
    {
      // Создаем таймер, который нужен для периодического
      // определения состояния устройства чтения CD
      nTimerID = SetTimer(hwnd, 1, 1000, NULL);

      return 0;
    }

    // ---------------------------------------
    // Обработчик сообщения WM_COMMAND
    // ---------------------------------------
    case WM_COMMAND:
    {
      switch(wParam)
      {
        // Запуск режима проигрывания 
        case IDB_PLAY:
        {
          // Если в проигрывателе есть компакт-диск,
          // запускаем проигрывание
          if(bMediaPresent)
            Play(hwnd, 1);
          return 0;
        }

        // Останов проигрывания
        case IDB_STOP:
        {
          if(bMediaPresent)
          {
            bPaused = FALSE;
            nCurTrack = 0;
            mciSendCommand(MCIOpen.wDeviceID, MCI_STOP,
              NULL, NULL);
          }
          return 0;
        }

        // Временный останов проигрывания
        case IDB_PAUSE:
        {
          if(bMediaPresent)
          {
            if(!bPaused)
            {
              bPaused = TRUE;
              mciSendCommand(MCIOpen.wDeviceID, MCI_PAUSE,
                NULL, NULL);
            }
          }
          return 0;
        }

        // Продолжение проигрывания после
        // временного останова
        case IDB_RESUME:
        {
          if(bMediaPresent)
          {
            if(bPaused)
            {
              bPaused = FALSE;
              MCIPlay.dwCallback = (DWORD)hwnd;
              mciSendCommand(MCIOpen.wDeviceID, MCI_PLAY,
                MCI_NOTIFY, (DWORD)(LPVOID)&MCIPlay);
            }
          }
          return 0;
        }

        // Позиционирование на следующую дорожку
        case IDB_NEXT:
        {
          if(bMediaPresent)
          {
            UINT nNewTrack;

            // Если текущая дорожка - последняя,
            // начинаем проигрывание с первой дорожки.
            // Если нет - проигрываем следующую дорожку
            if(nCurTrack == nTrackCnt)
              nNewTrack = 1;
            else
              nNewTrack = nCurTrack + 1;

            Play(hwnd, nNewTrack);
          }
          return 0;
        }

        // Позиционирование на предыдущую дорожку 
        case IDB_PREV:
        {
          if(bMediaPresent)
          {
            UINT nNewTrack;

            // Если текущая дорожка - первая,
            // проигрываем последнюю дорожку
            if(nCurTrack <= 1)
              nNewTrack = nTrackCnt;
            else
              nNewTrack = nCurTrack - 1;

            Play(hwnd, nNewTrack);
          }
          return 0;
        }

        // Завершаем работу приложения
        case IDOK:
        case IDCANCEL:
        {
          SendMessage(hwnd, WM_CLOSE, 0, 0L);
          return 0;
        }

        // Выполняем команду извлечения диска из
        // устройства чтения
        case IDB_EJECT:
        {
          mciSendCommand(MCIOpen.wDeviceID, MCI_SET,
            MCI_SET_DOOR_OPEN, NULL);
          return 0;
        }
      }
    }

    // ---------------------------------------
    // Обработчик сообщения WM_TIMER
    // ---------------------------------------
    case WM_TIMER:
    {
      UINT nCurMode;

      // Если окно свернуто в пиктограмму, ничего не делаем,
      // чтобы не снижать производительность системы
      if(IsIconic(hwnd))
        return 0;

      // Определяем текущее состояние проигрывателя CD
      MCIStatus.dwItem = MCI_STATUS_MODE;
      mciSendCommand(MCIOpen.wDeviceID, MCI_STATUS,
        MCI_STATUS_ITEM | MCI_WAIT,
        (DWORD)(LPVOID)&MCIStatus);

      // Проверяем, готово ли устройство чтения к работе 
      if((MCIStatus.dwReturn == MCI_MODE_NOT_READY) ||
         (MCIStatus.dwReturn == MCI_MODE_OPEN))
      {
        // Устройство не готово
        nCurMode = CD_EMPTY;
      }
       else if((MCIStatus.dwReturn == MCI_MODE_STOP) &&
         bPaused)
      {
        // Устройство остановлено 
        nCurMode = CD_PAUSED;
      }
      else if(MCIStatus.dwReturn == MCI_MODE_PLAY)
      {  
        // Устройство находится в режиме проигрывания
        nCurMode = CD_PLAYING;
      }
      else
      {
        // Устройство готово
        nCurMode = CD_READY;
      }

      // Если с момента последней проверки произошло
      // изменение режима, записываем код нового режима
      if(nMode != nCurMode)
      {
        nMode = nCurMode;
      }

      // Проверяем, вставлен ли компакт-диск
      MCIStatus.dwItem = MCI_STATUS_MEDIA_PRESENT;
      mciSendCommand(MCIOpen.wDeviceID, MCI_STATUS,
        MCI_STATUS_ITEM | MCI_WAIT,
        (DWORD)(LPVOID)&MCIStatus);

      // Если компакт-диск вставлен, определяем
      // количество звуковых дорожек
      if((!bMediaPresent) && MCIStatus.dwReturn)
      {
        bMediaPresent = TRUE;
        bPaused = FALSE;
        nCurTrack = 0;

        MCIStatus.dwItem = MCI_STATUS_NUMBER_OF_TRACKS;
        mciSendCommand(MCIOpen.wDeviceID, MCI_STATUS,
          MCI_STATUS_ITEM | MCI_WAIT,
          (DWORD)(LPVOID)&MCIStatus);

        nTrackCnt = MCIStatus.dwReturn;
      }

      // Если компакт-диск не вставлен, сбрасываем
      // номер текущей дорожке в поле диалоговой панели
      else if((bMediaPresent) && !MCIStatus.dwReturn)
      {
        bMediaPresent = FALSE;
        bPaused = FALSE;
        SetWindowText(hwndCurTrack, (LPSTR)"");
      }

      // Если приложение находится в режиме проигрывания,
      // определяем номер текущей дорожки
      if(nCurMode == CD_PLAYING)
      {
        // Определяем текущую позицию
        MCIStatus.dwItem = MCI_STATUS_POSITION;
        mciSendCommand(MCIOpen.wDeviceID, MCI_STATUS,
          MCI_STATUS_ITEM | MCI_WAIT,
          (DWORD)(LPVOID)&MCIStatus);

        // Если номер дорожки изменился, отображаем новое
        // значение в соответствующем поле диалоговой панели 
        if(nCurTrack != (UINT)MCI_TMSF_TRACK(MCIStatus.dwReturn))
        {
          BYTE szBuf[20];
          nCurTrack = (UINT)MCI_TMSF_TRACK(MCIStatus.dwReturn);
          SetWindowText(hwndCurTrack, itoa(nCurTrack, szBuf, 10));
        }
      }
      return 0;
    }

    // ---------------------------------------
    // Обработчик сообщения MM_MCINOTIFY
    // ---------------------------------------
    case MM_MCINOTIFY:
    {
      if(wParam == MCI_NOTIFY_SUCCESSFUL)
      {
        if(bMediaPresent)
           Play(hwnd, 1);
      }
      return 0;
    }

    // ---------------------------------------
    // Обработчик сообщения WM_CLOSE
    // ---------------------------------------
    case WM_CLOSE:
    {
      DestroyWindow(hwnd);
      return 0;
    }

    // ---------------------------------------
    // Обработчик сообщения WM_DESTROY
    // ---------------------------------------
    case WM_DESTROY:
    {
      // Закрываем устройство чтения компакт-дисков
      dwrc = mciSendCommand(MCIOpen.wDeviceID, MCI_CLOSE,
        NULL, NULL);
      if(dwrc)
        mciwioError(dwrc);

      // Уничтожаем таймер
      KillTimer(hwnd, nTimerID);

      PostQuitMessage(0);
      return 0;
    }
  }
  return DefDlgProc(hwnd, msg, wParam, lParam);
}

//-----------------------------------------------------
// mciwioError
// Обработка ошибок
//-----------------------------------------------------
void mciwioError(DWORD dwrc)
{
  BYTE szBuf[MAXERRORLENGTH];

  if(mciGetErrorString(dwrc, (LPSTR)szBuf, MAXERRORLENGTH))
    MessageBox(NULL, szBuf,
      "MCIWAVE Error", MB_ICONEXCLAMATION);
  else
    MessageBox(NULL, "Неизвестная ошибка",
      "MCIWAVE Error", MB_ICONEXCLAMATION);
}

//-----------------------------------------------------
// Play
// Запуск проигрывания дорожки
//-----------------------------------------------------
void Play(HWND hwnd, UINT nTrack)
{
  bPaused = FALSE;

  MCIPlay.dwCallback = (DWORD)hwnd;
  MCIPlay.dwFrom     = MCI_MAKE_TMSF(nTrack, 0, 0, 0);

  dwrc = mciSendCommand(MCIOpen.wDeviceID, MCI_PLAY,
    MCI_FROM | MCI_NOTIFY,
    (DWORD)(LPVOID)&MCIPlay);
  if(dwrc)
  {
    mciwioError(dwrc);
    return;
  }
}