
#include <limits.h>

#include <Windows.h>
#include <WinError.h>

#include <atlbase.h>
#include <atlapp.h>
#include <atlwin.h>
#include <atlcrack.h>
#include <atlctrls.h>

#include "resource.h"
#include "Consts.h"

#pragma comment(lib, "winmm.lib")

class CMainForm : public CDialogImpl<CMainForm> {
public:
  enum {
    IDD = IDD_MAINFORM,
  };

private:

  enum Timers {
      TID_TIMER,
  };

  CEdit edits[4];
  unsigned int buttons[4];

  BEGIN_MSG_MAP(CMainForm)
    MSG_WM_INITDIALOG(OnInitDialog)
    MSG_WM_CLOSE(OnClose)
    MSG_WM_DESTROY(OnDestroy)
    COMMAND_ID_HANDLER_EX(IDOK, OnOK)
    COMMAND_ID_HANDLER_EX(IDCANCEL, OnCancel)
    MSG_WM_TIMER(OnTimer)
  END_MSG_MAP()

  CMainForm() {
  }

  LRESULT OnInitDialog(HWND hWnd, LPARAM lParam) {
    edits[0] = GetDlgItem(IDC_EDIT1);
    edits[1] = GetDlgItem(IDC_EDIT2);
    edits[2] = GetDlgItem(IDC_EDIT3);
    edits[3] = GetDlgItem(IDC_EDIT4);
    edits[0].SetFocus();

    for (unsigned int i = 0; i < _countof(edits); i++) {
      SetButton(i, GetIniInt(INI_KEY_LIST[i], INI_DEFAULT_VALUE_LIST[i]));
    }

    SetTimer(TID_TIMER, 1000 / 60);
    return 0;
  }

  void OnClose() {
    DestroyWindow();
  }

  void OnDestroy() {
    ::PostQuitMessage(0);
  }

  void OnOK(UINT uNotifyCode, int nID, HWND hWndCtl) {
    for (unsigned int i = 0; i < _countof(edits); i++) {
      WriteIniInt(INI_KEY_LIST[i], buttons[i]);
    }
    DestroyWindow();
  }

  void OnCancel(UINT uNotifyCode, int nID, HWND hWndCtl) {
    DestroyWindow();
  }

  void OnTimer(UINT_PTR nIDEvent) {
    static JOYINFOEX prev = {0};
    JOYINFOEX info;
    info.dwSize = sizeof(info);
    info.dwFlags = JOY_RETURNALL;
    if (JOYERR_NOERROR != ::joyGetPosEx(JOYSTICKID1, &info)) {
      return;
    }
    if (prev.dwSize == 0) {
      prev = info;
      return;
    }
    for (unsigned int i = 0; i < 24; i++) {
      if ((info.dwButtons & (1 << i)) != 0 && (prev.dwButtons & (1 << i)) == 0) {
        SetButton(GetFocusIndex(), i);
        NextFocus();
        break;
      }
    }
    prev = info;
  }

  void SetButton(const unsigned int index, const unsigned int buttonId) {
    if (index >= _countof(edits)) {
      return;
    }
    buttons[index] = buttonId;
    char displayName[64];
    ::sprintf(displayName, "Pad%d", buttonId + 1);
    edits[index].SetWindowTextA(displayName);
  }

  unsigned int GetFocusIndex() const {
    CWindow wnd = GetFocus();
    for (unsigned int i = 0; i < _countof(edits); i++) {
      if (wnd == edits[i]) {
        return i;
      }
    }
    return UINT_MAX;
  }

  void NextFocus() {
    const unsigned int index = GetFocusIndex();
    if (index >= _countof(edits)) {
      return;
    }
    edits[(index + 1) % 4].SetFocus();
  }

  unsigned int GetIniInt(const char * const key, const unsigned int defaultValue) const {
    char defaultString[32];
    ::sprintf(defaultString, "%d", defaultValue);
    char result[32];
    ::GetPrivateProfileString(INI_SECTION_NAME, key, defaultString, result, _countof(result), GetIniFullPath());
    return ::atoi(result);
  }

  bool WriteIniInt(const char * const key, const unsigned int value) const {
    char valueString[32];
    ::sprintf(valueString, "%d", value);
    return ::WritePrivateProfileStringA(INI_SECTION_NAME, key, valueString, GetIniFullPath()) != 0;
  }

  const char *GetIniFullPath() const {
    static char result[MAX_PATH] = "";
    if (result[0] == '\0') {
      ::GetCurrentDirectoryA(_countof(result), result);
      ::PathAppendA(result, INI_FILENAME);
    }
    return result;
  }
};


int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
  CAppModule _Module;
  _Module.Init(NULL, hInstance);

  CMessageLoop theLoop;
  _Module.AddMessageLoop(&theLoop);

  CMainForm wnd;
  wnd.Create(NULL, CWindow::rcDefault, WS_OVERLAPPEDWINDOW | WS_VISIBLE);

  const int nRet = theLoop.Run();

  _Module.RemoveMessageLoop();
  _Module.Term();

	return nRet;
}
