
#ifndef STRICT
#define STRICT
#endif // STRICT

#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x400
#endif

#include <stdio.h>
#include <windows.h>


//! width of client area
int getClientWidth( HWND hwnd ) {
  RECT rc;
  ::GetClientRect( hwnd, &rc );
  return rc.right - rc.left;
}



//! height of client area
int getClientHeight( HWND hwnd ) {
  RECT rc;
  ::GetClientRect( hwnd, &rc );
  return rc.bottom - rc.top;
}



//! get vertical posigion
int getVScrollPos( HWND hwnd ) {
  SCROLLINFO si;
  si.cbSize = sizeof(SCROLLINFO);
  si.fMask = SIF_POS;
  if (::GetScrollInfo( hwnd, SB_VERT, &si )) {
    return si.nPos;
  }
  return 0;
}


//! set vertical position
void setVScrollPos( HWND hwnd, int pos) {
  SCROLLINFO si;
  si.cbSize = sizeof(SCROLLINFO);
  si.fMask = SIF_POS;
  si.nPos = pos;
  ::SetScrollInfo( hwnd, SB_VERT, &si, TRUE);
  InvalidateRect( hwnd, 0, TRUE );
}


//! get vertical scroll page size
int getVScrollPage( HWND hwnd ) {
  SCROLLINFO si;
  si.cbSize = sizeof(SCROLLINFO);
  si.fMask = SIF_PAGE;
  if (::GetScrollInfo( hwnd, SB_VERT, &si)) {
    return si.nPage;
  }
  return 0;
}

//! set vertical scroll page size
void setVScrollPage( HWND hwnd, int page) {
  SCROLLINFO si;
  si.cbSize = sizeof(SCROLLINFO);
  si.fMask = SIF_PAGE;
  si.nPage = page;
  ::SetScrollInfo( hwnd, SB_VERT, &si, TRUE);
}

//! set vertical scroll range
void setVScrollRange( HWND hwnd, int min, int max ) {
  SCROLLINFO si;
  si.cbSize = sizeof(SCROLLINFO);
  si.fMask = SIF_RANGE;
  si.nMin = min;
  si.nMax = max;
  ::SetScrollInfo( hwnd, SB_VERT, &si, TRUE );
}


///////////////////////////////////////////////////////////////////////////////
//!  methods of main form
LRESULT CALLBACK mainMethods( HWND hwnd, UINT msg, WPARAM wp, LPARAM lp ) {
  static LOGFONT lf = {0};
  static HFONT hFont = 0;
  static int font_height;


  switch (msg) {

  case WM_PAINT: {
    if (hFont) {
      PAINTSTRUCT ps;
      ::BeginPaint( hwnd, &ps );

      HFONT hFontOld = (HFONT)::SelectObject( ps.hdc, hFont );

      int h = getClientHeight( hwnd ) / font_height;
      ::SetBkMode( ps.hdc, TRANSPARENT );
      for (unsigned short j=0; j < 16 * h; ++j ) {
        unsigned short c = j + (16 * getVScrollPos( hwnd ));
        char code[12];
        int len = sprintf( code, "%04x", c );

        ::TextOutW( ps.hdc, (j & 15) * font_height, (j >> 4) * font_height, &c, 1 );
        ::SelectObject( ps.hdc, (HFONT)::GetStockObject( DEFAULT_GUI_FONT ) );

        ::TextOutA( ps.hdc, (j & 15) * font_height, (j >> 4) * font_height + font_height - 8, code, len );
        ::SelectObject( ps.hdc, hFont );
      }

      ::SelectObject( ps.hdc, hFontOld );
      ::EndPaint( hwnd, &ps );
    }
    return 0;
  }


  case WM_MOUSEWHEEL: {
    setVScrollPos( hwnd, getVScrollPos(hwnd) - (short)HIWORD(wp) );
    return 0;
  }


  case WM_VSCROLL: {
    switch (LOWORD(wp)) {
    case SB_THUMBTRACK:
      setVScrollPos( hwnd, HIWORD(wp) );
      break;

    case SB_LINEUP: {
        int pos = getVScrollPos( hwnd );
        if (pos) {
          --pos;
          setVScrollPos( hwnd, pos );
        }
      }
      break;

    case SB_LINEDOWN: {
        int pos = getVScrollPos( hwnd );
        if (pos <= (65536/16) - getVScrollPage( hwnd )) {
          pos++;
          setVScrollPos( hwnd, pos );
        }
      }
      break;

    case SB_PAGEUP: {
        int page = getVScrollPage( hwnd );
        int pos = getVScrollPos( hwnd );
        if ( page < pos ) pos -= page;
                     else pos = 0;
        setVScrollPos( hwnd, pos );
      }
      break;

    case SB_PAGEDOWN: {
        int page = getVScrollPage( hwnd );
        int pos = getVScrollPos( hwnd );
        if ( page < (65536/16) - pos ) pos += page;
                     else pos = (65536/16) - page;
        setVScrollPos( hwnd, pos );
      }
      break;

    }
    return 0;
  }


  case WM_SIZE: {
    font_height = getClientWidth( hwnd ) / 16;
    if (font_height == 0) font_height = 1;
    setVScrollPage( hwnd, getClientHeight( hwnd ) / font_height );

    lf.lfHeight         = font_height;
    lf.lfWidth          = 0;
    lf.lfEscapement     = 0;
    lf.lfOrientation    = 0;
    lf.lfWeight         = FW_DONTCARE;
    lf.lfItalic         = 0;
    lf.lfUnderline      = 0;
    lf.lfStrikeOut      = 0;
    lf.lfCharSet        = DEFAULT_CHARSET;
    lf.lfOutPrecision   = OUT_DEFAULT_PRECIS;
    lf.lfClipPrecision  = CLIP_DEFAULT_PRECIS;
    lf.lfQuality        = DEFAULT_QUALITY;
    lf.lfPitchAndFamily = DEFAULT_PITCH | FF_DONTCARE;
    lstrcpy (lf.lfFaceName, "Arial Unicode MS");

    if (hFont) ::DeleteObject( hFont );
    hFont = ::CreateFontIndirect( &lf );

    return 0;
  }

  case WM_CLOSE: {
    ::DestroyWindow( hwnd );
    return 0;
  }


  case WM_DESTROY: {
    if (hFont) ::DeleteObject( hFont );
    PostQuitMessage(0);
    return 0;
  }

  }

  return ::DefWindowProc( hwnd, msg, wp, lp );
}


///////////////////////////////////////////////////////////////////////////////
// main entry point

int WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR, int ) {

  WNDCLASSEX window_class = {
        sizeof( WNDCLASSEX ),
        CS_HREDRAW | CS_VREDRAW,
        mainMethods,
        0,
        0,
        hInst,
        (HICON)  ::LoadIcon( NULL, IDI_WINLOGO ),
        (HCURSOR)::LoadCursor( NULL, IDC_ARROW ),
        (HBRUSH) ::GetStockObject( WHITE_BRUSH ),
        NULL,
        "UnicodeViewer",
        NULL
  };

  if (! ::RegisterClassEx( &window_class )) {
    ::MessageBox( 0, "RegisterClassEx Failed", "error", 0 );
    return 0;
  }

  HWND main_form = ::CreateWindowEx (
                       0,
                       "UnicodeViewer",
                       "Unicode Viewer",
                       WS_OVERLAPPEDWINDOW | WS_VSCROLL,
                       CW_USEDEFAULT,
                       CW_USEDEFAULT,
                       CW_USEDEFAULT,
                       CW_USEDEFAULT,
                       NULL,
                       NULL,
                       hInst,
                       0 );

  if (! main_form) {
    ::MessageBox( 0, "CreateWindowEx Failed", "error", 0 );
    return 0;
  }

  setVScrollRange( main_form, 0, 65536 / 16 - 1 );

  ::ShowWindow( main_form, SW_SHOW );

  MSG msg;
  while (::GetMessage( &msg, 0, 0, 0 ) > 0) {
    ::TranslateMessage( &msg );
    ::DispatchMessage( &msg );
  }

  return 0;
}


