/*
 * Copyright (c) 2015 akamoz.jp
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.

 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <windows.h>
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <assert.h>

class Window {
  public:
    Window(HWND hwnd = NULL) { set(hwnd); }
    HWND create(LPCTSTR cls, DWORD ws, DWORD exws, HWND parent = NULL,
        LPCTSTR title = TEXT(""), HMENU hmenu = NULL, 
        LPVOID lpv = NULL, int xpos = CW_USEDEFAULT, int ypos = 0,
        int width = 100, int height = 70) {
        if ((width == (int)CW_USEDEFAULT) && (ws & WS_CHILD)) {
            width = 100;
            height = 100;
        }
        mhwnd = ::CreateWindowEx(exws, cls, title, ws, 
            xpos, ypos, width, height, parent, hmenu,
            GetModuleHandle(NULL), lpv);
        return mhwnd;
    }
    BOOL destroy() {
        return ::DestroyWindow(mhwnd);
    }
    HWND get() { return mhwnd; }
    void set(HWND hwnd) { mhwnd = hwnd; }
    LONG_PTR setLongPtr(int nIndex, LONG_PTR nVal) {
        return ::SetWindowLongPtr(mhwnd, nIndex, nVal);
    }
    LONG_PTR getLongPtr(int nIndex) {
        return ::GetWindowLongPtr(mhwnd, nIndex);
    }
    LRESULT defproc(UINT u, WPARAM w, LPARAM l) {
        return ::DefWindowProc(mhwnd, u, w, l);
    }
    LRESULT postMessage(UINT uMsg, WPARAM wp = 0, LPARAM lp = 0) {
        return ::PostMessage(mhwnd, uMsg, wp, lp);
    }
    BOOL show(int n = SW_SHOW) {
        return ::ShowWindow(mhwnd, n);
    }
  private:
    HWND mhwnd;
};

class WindowBase : public virtual Window {
  public:
    WindowBase();
    virtual ~WindowBase();
    HWND create(
        HWND hwndParent, DWORD ws, DWORD exws = 0, 
        LPCTSTR title=TEXT(""), HMENU hmenu = (0)
    );
    ATOM registerClass();
  protected:
    LPCTSTR mptszClass;
    virtual LRESULT wndproc(UINT uMsg, WPARAM wp, LPARAM lp);
  private:
    static LRESULT CALLBACK _wndproc(HWND, UINT, WPARAM, LPARAM);
};

WindowBase::WindowBase()
{
}

WindowBase::~WindowBase()
{
    if (get() != NULL)
        destroy();
}

LRESULT CALLBACK WindowBase::_wndproc(
    HWND hwnd, UINT uMsg, WPARAM wp, LPARAM lp
) {
    WindowBase *pwnd;
    pwnd = (WindowBase *)GetWindowLongPtr(hwnd, GWLP_USERDATA);
    if ((pwnd == NULL) && (uMsg == WM_NCCREATE)) {
        CREATESTRUCT *pcs = (CREATESTRUCT *)lp;
        pwnd = (WindowBase *)pcs->lpCreateParams;
        if (pwnd != NULL) {
            pwnd->set(hwnd);
            pwnd->setLongPtr(GWLP_USERDATA, (LONG_PTR)pwnd);
        }
    }
    if (pwnd != NULL) {
        assert(pwnd->get() == hwnd);
        return pwnd->wndproc(uMsg, wp, lp);
    }
    return ::DefWindowProc(hwnd, uMsg, wp, lp);
}

LRESULT WindowBase::wndproc(UINT uMsg, WPARAM wp, LPARAM lp)
{
    return defproc(uMsg, wp, lp);
}

#define WINDOWCLASSNAME TEXT("akamoz.jp/WindowBase")

ATOM WindowBase::registerClass()
{
    LPCTSTR pszClass = WINDOWCLASSNAME;
    ATOM atm = FindAtom(pszClass);
    if(atm == 0) {
        HINSTANCE hinst = GetModuleHandle(NULL);
        WNDCLASS wc;
        memset(&wc, 0, sizeof(wc));
        wc.style = CS_DBLCLKS;
        wc.lpfnWndProc = _wndproc;
        wc.hInstance = hinst;
        wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
        wc.hCursor = LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
        wc.lpszClassName = pszClass;
        atm = RegisterClass(&wc);
    }
    return atm;
}

HWND WindowBase::create(
    HWND hwndParent, DWORD ws, DWORD exws, LPCTSTR title, HMENU hmenu
) {
    if (registerClass() == 0)
        return NULL;
    Window::create(WINDOWCLASSNAME, ws, exws, hwndParent, title, hmenu, this);
    return get();
}

class Message : public MSG {
  public:
    BOOL get(HWND hwnd = NULL, UINT uMin = 0, UINT uMax = 0) {
        return ::GetMessage(this, hwnd, uMin, uMax);
    }
    BOOL peek(
        UINT flag = PM_REMOVE, HWND hwnd = NULL, UINT uMin = 0, UINT uMax = 0
    ) {
        return ::PeekMessage(this, hwnd, uMin, uMax, flag);
    }
    BOOL translate() {
        return ::TranslateMessage(this);
    }
    LONG dispatch() {
        return ::DispatchMessage(this);
    }
    WPARAM doLoop();
};

WPARAM Message::doLoop()
{
    while (get()) {
        translate();
        dispatch();
    }
    return wParam;
}

class Win32Timer {
  public:
    Win32Timer() {
        muID = 0;
        mhwnd = NULL;
    }
    ~Win32Timer() {
        kill();
    }
    int set(HWND hwnd, UINT_PTR uID, UINT umsec, TIMERPROC proc = NULL) {
        muID = ::SetTimer(hwnd, uID, umsec, proc);
        if (muID == 0)
            return -1;
        mhwnd = hwnd;
        if (hwnd != NULL)
            muID = uID;
        return 0;
    }
    int kill() {
        if (muID == 0)
            return 0;
        if (::KillTimer(mhwnd, muID) == FALSE)
            return -1;
        muID = 0;
        return 0;
    }
  private:
    UINT_PTR muID;
    HWND mhwnd;
};

class MainWindow : public WindowBase {
  protected:
    virtual LRESULT wndproc(UINT uMsg, WPARAM wp, LPARAM lp);
  private:
    Win32Timer tm;
};

LRESULT MainWindow::wndproc(UINT uMsg, WPARAM wp, LPARAM lp)
{
    switch (uMsg) {
      case WM_CREATE:
        tm.set(get(), 1, 100);
        return 0;
      case WM_TIMER: {
            sigset_t sig;
            sigpending(&sig);
        }
        return 0;
      case WM_DESTROY:
        tm.kill();
        PostQuitMessage(0);
        return 0;
    }
    return WindowBase::wndproc(uMsg, wp, lp);
}

MainWindow win;

BOOL WINAPI ctrlHandler(DWORD)
{
    fprintf(stderr, "!"); 
    win.postMessage(WM_CLOSE);
    return TRUE;
}

void sighandler(int)
{
    fprintf(stderr, "#");
    win.postMessage(WM_CLOSE);
}

int main(void)
{
    SetConsoleCtrlHandler(ctrlHandler, TRUE);
    signal(SIGINT, sighandler);

    win.create(NULL, WS_OVERLAPPEDWINDOW);
    win.show();

    Message msg;
    msg.doLoop();
    getchar();

    return 0;
}