
#ifndef APPBASE_H
#define APPBASE_H

#include <windows.h>
#include <process.h>
#include <stdio.h>

#ifndef _MT
#error multithread required!
#endif

static LRESULT CALLBACK myWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
static unsigned _stdcall threadFunc(void *arg);


class AppBase{

protected:
  AppBase(){}

public:
  HWND windowHandle;
  int width;
  int height;
  int depth;
  unsigned char *pixels;
  HBITMAP hDIB;
  BITMAPINFO bmi;
  HANDLE threadHandle;
  unsigned threadID;
  volatile bool terminate_thread; // これがtrue になると theMain は終了するべき

  /**
   * コンストラクタ
   */
  AppBase(int x, int y, int w, int h, int d=24){
    initialize(x,y,w,h,d);
  }
  void initialize(int x, int y, int w, int h, int d=24){
    RECT rcWin, rcCli;

    hDIB =NULL;
    windowHandle = NULL;
    threadHandle = NULL;

    //クラス登録
    WNDCLASSEX wndClass = {
            sizeof(WNDCLASSEX),
            CS_HREDRAW|CS_VREDRAW,
            (WNDPROC)myWndProc,
            0,
            0, // sizeof(AppBase),
            NULL,
            NULL,
            ::LoadCursor(NULL, IDC_ARROW),
            (HBRUSH)::GetStockObject(WHITE_BRUSH),
            NULL,
            "myClass",
            NULL };
    ::RegisterClassEx( &wndClass );

    //ウィンドウ生成
    windowHandle = ::CreateWindowEx(
            0,//WS_EX_OVERLAPPEDWINDOW,
            "myClass",
            "myWindow",
            //WS_EX_TOOLWINDOW,
            WS_POPUPWINDOW|WS_CAPTION,
            x, y, w, h,
            NULL,
            NULL,
            NULL,
            NULL );

    if( ! windowHandle ) {
      MessageBox(0, "CreateWindow failed.", "error", 0);
      return;
    }

    ::SetWindowLong( windowHandle, GWL_USERDATA, (LONG)this );

    width = w;
    height = h;
    depth = d;

    ::GetWindowRect(windowHandle, &rcWin);
    ::GetClientRect(windowHandle, &rcCli);
    w = width + (rcWin.right - rcWin.left) - (rcCli.right - rcCli.left);
    h = height+ (rcWin.bottom- rcWin.top ) - (rcCli.bottom- rcCli.top );
    ::SetWindowPos(windowHandle, NULL, 0, 0, w, h, SWP_NOMOVE|SWP_NOZORDER);

    ::ShowWindow(windowHandle, SW_SHOW);

    //DIB生成
    HDC dc = ::GetDC(windowHandle);

    //BITMAPINFO bmi;
#define bmih bmi.bmiHeader
    bmih.biSize = sizeof(BITMAPINFOHEADER);
    bmih.biWidth = width;
    bmih.biHeight = height;
    bmih.biPlanes = 1; // must be set 1
    bmih.biBitCount = depth;
    bmih.biCompression = BI_RGB;
    bmih.biSizeImage = 0;
    bmih.biXPelsPerMeter = 1;
    bmih.biYPelsPerMeter = 1;
    bmih.biClrUsed = 0;
    bmih.biClrImportant = 0;
#undef bmih

    hDIB = CreateDIBSection(
              dc,
              &bmi,
              DIB_RGB_COLORS,
              (void **)&pixels,
              NULL,
              0 );

    ::ReleaseDC(windowHandle,dc);

    if (! hDIB) {
      MessageBox(0, "CreateDIBSection failed.", "error", 0);
      goto on_error;
    }

    threadHandle = (HANDLE)_beginthreadex(
            NULL, // void *security
            0,    // unsigned stack size
            (unsigned int (__stdcall *)(void *))threadFunc,
            (LPVOID)this,
            CREATE_SUSPENDED,
            &threadID );

    if (! threadHandle) {
      MessageBox(0, "CreateThread failed", "error", 0);
      goto on_error;
    }

    return;

on_error:
    return;
  }

  /**
   * デストラクタ
   */
  virtual ~AppBase(){ }

  /**
   * (x ,y) に DIB はあるか？
   */
  bool posAssert( int y, int x ){
    if(! hDIB) return false;
    if( x < 0 || width <= x ) return false;
    if( y < 0 || height <= y ) return false;
    return true;
  }

  /**
   * DIB の BitsPerPix, BytesPerLine
   */
  int BPP(){ return (depth >> 3); }
  int BPL(){ return (((width * depth + 31) >> 5) << 2); }

  unsigned char *scanPixel( int y, int x ){
    if(posAssert(y,x))
      return pixels + (height-1-y)*BPL() + x*BPP();
    return NULL;
  }

  /**
   * (x ,y) に 点
   */
  void dot(int y, int x, unsigned char b, unsigned char g, unsigned char r ){
    unsigned char *pix;
    if (pix = scanPixel(y,x)) {
      pix[0]=b;  pix[1]=g;  pix[2]=r;
    }
    return;
  }

  /**
   * メインなプロセス
   */
  WORD run(){
    MSG msg;
    if (! windowHandle) return 0;

    // スレッド起動
    terminate_thread = false;
    if (threadHandle) ::ResumeThread(threadHandle);

    while (1) { /* メインループ */
      if (::PeekMessage (&msg,NULL,0,0,PM_NOREMOVE)) {

        if (!::GetMessage (&msg,NULL,0,0)) /* メッセージ処理 */
          return msg.wParam;

        ::TranslateMessage(&msg);
        ::DispatchMessage(&msg);
      }
      // スレッドが終わったらアプリケーションを終了したいなら、コメントを外す
      //if (threadHandle && ::WaitFoeSingleObject(threadHandle, 0) != 0)
      //  close();
    }

    // unreachable
    return 0;
  }

  /**
   *  メッセージを送る
   */
  void postMessage( UINT msg, WPARAM wp, LPARAM lp ) {
    if ( windowHandle ) ::PostMessage(windowHandle, msg, wp, lp);
  }
  LRESULT sendMessage( UINT msg, WPARAM wp, LPARAM lp ) {
    if ( windowHandle ) return ::SendMessage( windowHandle, msg, wp, lp );
    return 0;
  }

  /**
   * 終了
   */
  void close() {
    postMessage( WM_CLOSE, 0, 0 );
  }

  void doClose(){

    int i, r;

    // スレッドが続行中ならば終了
    if (threadHandle) {

      terminate();

      for (i=0; i<100; ++i) {
        r = ::WaitForSingleObject(threadHandle, 10);
        if (r!=STATUS_TIMEOUT) break;
        ::Sleep(1);
      }
      if (r==STATUS_TIMEOUT)
        ::TerminateThread(threadHandle, 0);

      ::CloseHandle(threadHandle);
      threadHandle = 0;
    }

    if (hDIB) {
      ::DeleteObject( hDIB );
      hDIB = 0;
    }

    if (windowHandle) {
      ::DestroyWindow( windowHandle );
      windowHandle = 0;
    }

  }

  /**
   * on paint
   */
  void update(){
    ::InvalidateRect(windowHandle, NULL, FALSE);
    ::UpdateWindow(windowHandle);
  }

  void doPaint(){
    PAINTSTRUCT ps;
    HDC mdc;
    HBITMAP old;
    if (! hDIB) return;
    if (! windowHandle) return;
    ::BeginPaint(windowHandle, &ps);
    mdc = ::CreateCompatibleDC(ps.hdc);
    old = (HBITMAP)::SelectObject(mdc, hDIB);
    ::BitBlt(ps.hdc,0,0,width,height,mdc,0,0,SRCCOPY);
    ::GdiFlush();
    ::SelectObject(mdc, old);
    ::DeleteObject(mdc);
    ::EndPaint(windowHandle, &ps);
  }


  /**
   * メインスレッド .. こいつを実装する
   */
  virtual void terminate() { terminate_thread = true; }
  bool isTerminated() const { return terminate_thread; }

  virtual void theMain() = 0;
};


//-----------------------------------------------------------------------------
// ウィンドウプロシ−ジャ
//=============================================================================
static LRESULT CALLBACK myWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam){
  AppBase *w = (AppBase *)::GetWindowLong( hwnd, GWL_USERDATA );
  if (w) {
    switch( uMsg ){

    case WM_RBUTTONDOWN:
    {
      // マウスポインタの座標を取得
      POINT  pt;
      pt.x = LOWORD(lParam);
      pt.y = HIWORD(lParam);
      // クライアント座標からスクリーン座標に変換
      ::ClientToScreen(hwnd, &pt);
      //  システムメニューの表示
      //  post でも defwindowproc でもどっちでもいい模様
      //PostMessage( hwnd, 0x0313, 0, MAKELPARAM( pt.x, pt.y ));
      ::DefWindowProc(hwnd, 0x0313, 0, MAKELPARAM(pt.x, pt.y));
      return 0;
    }

    case WM_CLOSE:
      w->doClose();
      return 0;

    case WM_PAINT:
      w->doPaint();
      return 0;

    case WM_DESTROY:
      ::PostQuitMessage(0);
      return 0;
    }
  }
  return ::DefWindowProc(hwnd, uMsg, wParam, lParam);
}


//-----------------------------------------------------------------------------
// スレッド
//=============================================================================
static
unsigned _stdcall threadFunc(void *arg){
  AppBase *w = (AppBase *)arg; // reinterpret_cast
  if (w)
    w->theMain();
  // ::ExitThread(0);
  return 0;
}

#endif /* winbase_h */


