/*------------------------------------------------------------------------
 * filename - easywin.cpp
 *
 * Easy Windows functions
 *-----------------------------------------------------------------------*/

/*
 *      C/C++ Run Time Library - Version 5.0
 *
 *      Copyright (c) 1991, 1992 by Borland International
 *      All Rights Reserved.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <mem.h>
#include <string.h>
#include <windows.h>

// These are dummy variables that are used because this module is
//   always linked in.

unsigned __hInstance;
unsigned __hPrev;
int      __cmdShow;

extern WORD  (*__ReadBufFPtr)(char *Buffer, WORD Count);
extern void  (*__WriteBufFPtr)(char *Buffer, WORD Count);

extern "C"
{
POINT _WindowOrg  = { CW_USEDEFAULT, CW_USEDEFAULT};
POINT _WindowSize = { CW_USEDEFAULT, CW_USEDEFAULT};

POINT _ScreenSize = { 80, 25 };            // Screen buffer dimensions
POINT _Cursor = { 0, 0 };                  // Cursor location
POINT _Origin = { 0, 0 };                  // Client area origin
char *_InactiveTitle = "(Inactive %s)";    // Inactive window title
BOOL  _AutoTracking = TRUE;                // Track cursor on Write?
BOOL  _CheckEOF = TRUE;                    // Allow Ctrl-Z for EOF?
BOOL  _CheckBreak = TRUE;                  // Allow Ctrl-C for break?

char  _WindowTitle[80];                    // Window title

void  _DoneEasyWin(void);

void  _WriteBuf(char *Buffer, WORD Count);
void  _WriteChar(char Ch);

BOOL  _KeyPressed(void);
int   _ReadKey(void);
WORD  _ReadBuf(char *Buffer, WORD Count);

void   gotoxy(int X, int Y);
int    wherex(void);
int    wherey(void);
void   clrscr(void);
void   clreol(void);

void  _CursorTo(int X, int Y);
void  _ScrollTo(int X, int Y);
void  _TrackCursor(void);
}

void __InitEasyWin(void);

// MinMaxInfo array

typedef POINT TMinMaxInfo[5];

// Scroll key definition record

typedef struct
{
    BYTE Key;
    BOOL Ctrl;
    BYTE SBar;
    BYTE Action;
} TScrollKey;

// Easy window procedure

long FAR PASCAL _export _EasyWinProc(HWND Window, UINT Message,
                                     WPARAM WParam, LONG LParam);

// CRT window class

static WNDCLASS CrtClass =
{
    CS_HREDRAW | CS_VREDRAW, ::_EasyWinProc, 0, 0, 0, 0, 0, 0,
    NULL, "BCEasyWin"
};

static int FirstLine  = 0;                   // First line in circular buffer
static int KeyCount   = 0;                   // Count of keys in KeyBuffer
static BOOL Created = FALSE;                 // Window created?
static BOOL Focused = FALSE;                 // Window focused?
static BOOL Reading = FALSE;                 // Reading from window?
static BOOL Painting = FALSE;                // Handling wm_Paint?

static HWND CrtWindow = 0;                   // CRT window handle
static char *ScreenBuffer;                   // Screen buffer pointer
static POINT ClientSize;                     // Client area dimensions
static POINT Range;                          // Scroll bar ranges
static POINT CharSize;                       // Character cell size
static int CharAscent;                       // Character ascent
static HDC DC;                               // Global device context
static PAINTSTRUCT PS;                       // Global paint structure
static HFONT SaveFont;                       // Saved device context font
static char KeyBuffer[64];                   // Keyboard type-ahead buffer

// Scroll keys table

const
  int ScrollKeyCount = 12;

const
  TScrollKey ScrollKeys[ScrollKeyCount]  =
      {
          { VK_LEFT,  FALSE, SB_HORZ, SB_LINEUP },
          { VK_RIGHT, FALSE, SB_HORZ, SB_LINEDOWN },
          { VK_LEFT,  TRUE,  SB_HORZ, SB_PAGEUP },
          { VK_RIGHT, TRUE,  SB_HORZ, SB_PAGEDOWN },
          { VK_HOME,  FALSE, SB_HORZ, SB_TOP },
          { VK_END,   FALSE, SB_HORZ, SB_BOTTOM },
          { VK_UP,    FALSE, SB_VERT, SB_LINEUP },
          { VK_DOWN,  FALSE, SB_VERT, SB_LINEDOWN },
          { VK_PRIOR, FALSE, SB_VERT, SB_PAGEUP },
          { VK_NEXT,  FALSE, SB_VERT, SB_PAGEDOWN },
          { VK_HOME,  TRUE,  SB_VERT, SB_TOP },
          { VK_END,   TRUE,  SB_VERT, SB_BOTTOM }
      };

// Return the smaller of two integer values

inline int Min(int X, int Y)
{
    return((X < Y)? X : Y);
}

//Return the larger of two integer values

inline int Max(int X, int Y)
{
    return((X > Y)? X: Y);
}

// Allocate device context

static void InitDeviceContext(void)
{
    if (Painting)
        DC = BeginPaint(CrtWindow, &PS);
    else
        DC = GetDC(CrtWindow);
    SaveFont = SelectObject(DC, GetStockObject(SYSTEM_FIXED_FONT));
}

// Release device context

static void DoneDeviceContext(void)
{
    SelectObject(DC, SaveFont);
    if (Painting)
        EndPaint(CrtWindow, &PS);
    else
        ReleaseDC(CrtWindow, DC);
}

// Show caret

static void ShowCursor(void)
{
    CreateCaret(CrtWindow, 0, CharSize.x, 2);
    SetCaretPos((_Cursor.x - _Origin.x) * CharSize.x,
                (_Cursor.y - _Origin.y) * CharSize.y + CharAscent);
    ShowCaret(CrtWindow);
}

// Hide caret

static void HideCursor(void)
{
    DestroyCaret();
}

// Update scroll bars

static void SetScrollBars(void)
{
    SetScrollRange(CrtWindow, SB_HORZ, 0, Max(1, Range.x), FALSE);
    SetScrollPos(CrtWindow, SB_HORZ, _Origin.x, TRUE);
    SetScrollRange(CrtWindow, SB_VERT, 0, Max(1, Range.y), FALSE);
    SetScrollPos(CrtWindow, SB_VERT, _Origin.y, TRUE);
}

// Terminate window

static void Terminate(void)
{
    if (Focused && Reading)
        HideCursor();
    exit(255);
}

// Set cursor position

void _CursorTo(int X, int Y)
{
    _Cursor.x = Max(0, Min(X, _ScreenSize.x - 1));
    _Cursor.y = Max(0, Min(Y, _ScreenSize.y - 1));
}

// Scroll window to given origin

void _ScrollTo(int X, int Y)
{
    if (Created)
        {
        X = Max(0, Min(X, Range.x));
        Y = Max(0, Min(Y, Range.y));
        if ((X != _Origin.x) || (Y != _Origin.y))
            {
            if (X != _Origin.x)
                SetScrollPos(CrtWindow, SB_HORZ, X, TRUE);
            if (Y != _Origin.y)
                SetScrollPos(CrtWindow, SB_VERT, Y, TRUE);
            ScrollWindow(CrtWindow,
                    (_Origin.x - X) * CharSize.x,
                    (_Origin.y - Y) * CharSize.y, NULL, NULL);
            _Origin.x = X;
            _Origin.y = Y;
            UpdateWindow(CrtWindow);
            }
        }
}

// Scroll to make cursor visible

void _TrackCursor(void)
{
    _ScrollTo(Max(_Cursor.x - ClientSize.x + 1,
               Min(_Origin.x, _Cursor.x)),
               Max(_Cursor.y - ClientSize.y + 1,
               Min(_Origin.y, _Cursor.y)));
}

// Return pointer to location in screen buffer

static char *ScreenPtr(int X, int Y)
{
    Y += FirstLine;
    if (Y >= _ScreenSize.y)
        Y -= _ScreenSize.y;
    return (ScreenBuffer+ (Y * _ScreenSize.x + X));
}

// Update text on cursor line

static void ShowText(int L, int R)
{
    if (L < R)
        {
        InitDeviceContext();
        TextOut(DC, (L - _Origin.x) * CharSize.x,
                    (_Cursor.y - _Origin.y) * CharSize.y,
                    ScreenPtr(L, _Cursor.y), R - L);
        DoneDeviceContext();
        }
}

// Write text buffer to window

static void NewLine(int& L, int& R)
{
    ShowText(L, R);
    L = 0;
    R = 0;
    _Cursor.x = 0;
    ++_Cursor.y;
    if (_Cursor.y == _ScreenSize.y)
        {
        --_Cursor.y;
        ++FirstLine;
        if (FirstLine == _ScreenSize.y)
            FirstLine = 0;
        memset(ScreenPtr(0, _Cursor.y), ' ', _ScreenSize.x);
        ScrollWindow(CrtWindow, 0, -CharSize.y, NULL, NULL);
        UpdateWindow(CrtWindow);
        }
}


void _WriteBuf(char *Buffer, WORD Count)
{
    int L, R;

    __InitEasyWin();
    L = _Cursor.x;
    R = _Cursor.x;
    while (Count > 0)
        {
        if (Buffer[0] == -1)
            Buffer[0] = ' ';
        switch (Buffer[0])
            {
            case 13:
            NewLine(L, R);
                break;
            case 10:
                break;
            case  9:
                do
                    {
                    *(ScreenPtr(_Cursor.x, _Cursor.y)) = ' ';
                    ++_Cursor.x;
                    if (_Cursor.x > R)
                        R = _Cursor.x;
                    if (_Cursor.x == _ScreenSize.x)
                        {
                        NewLine(L, R);
                        break;
                        }
                    } while (_Cursor.x % 8);
                break;
            case  8:
            if (_Cursor.x > 0)
                {
                --_Cursor.x;
                *(ScreenPtr(_Cursor.x, _Cursor.y)) = ' ';
                if (_Cursor.x < L )
                        L = _Cursor.x;
                }
                break;
            case  7:
                MessageBeep(0);
                break;
            default:
                *(ScreenPtr(_Cursor.x, _Cursor.y)) = Buffer[0];
                ++_Cursor.x;
                if (_Cursor.x > R)
                    R = _Cursor.x;
                if (_Cursor.x == _ScreenSize.x)
                    NewLine(L, R);
            }

        ++Buffer;
        --Count;
        }
    ShowText(L, R);
    if (_AutoTracking)
        _TrackCursor();
}

// Write character to window

void _WriteChar(char Ch)
{
    _WriteBuf(&Ch, 1);
}

// Return keyboard status

BOOL _KeyPressed(void)
{
    MSG M;

    __InitEasyWin();
    while (PeekMessage(&M, 0, 0, 0, PM_REMOVE))
        {
        if (M.message == WM_QUIT)
            Terminate();
        TranslateMessage(&M);
        DispatchMessage(&M);
        }
    return (BOOL)( KeyCount > 0 );
}

// Read key from window

int _ReadKey(void)
{
    int readkey;

    _TrackCursor();
    if (!_KeyPressed())
        {
        Reading = TRUE;
        if (Focused)
            ShowCursor();
        do
            {
            } while (!_KeyPressed());
        if (Focused)
            HideCursor();
        Reading = FALSE;
        }
    readkey = KeyBuffer[0];
    --KeyCount;
    memmove(KeyBuffer, KeyBuffer+1, KeyCount);
    return readkey;
}

// Read text buffer from window

WORD _ReadBuf(char *Buffer, WORD Count)
{
    unsigned char Ch;
    WORD I;

    I = 0;
    do
        {
        Ch = _ReadKey();
        if (Ch == 8)
            {
            if (I > 0)
                {
                --I;
                _WriteChar(8);
                }
            }
        else
            if (Ch >= 32)
                {
                if (I < Count)
                    {
                    Buffer[I++] = Ch;
                    _WriteChar(Ch);
                    }
                }
        } while (!((Ch == 13) || (_CheckEOF && (Ch == 26))));
    if (I < Count - 2)
        {
        Buffer[I++] = Ch;
        if (Ch == 13)
            {
            Buffer[I++] = 10;
            _WriteChar(13);
            }
        }
    _TrackCursor();
    return I;
}

// Set cursor position }

void gotoxy(int X, int Y)
{
    _CursorTo(X - 1, Y - 1);
}

// Return cursor X position

int wherex(void)
{
    return (_Cursor.x + 1);
}

// Return cursor Y position

int wherey(void)
{
    return(_Cursor.y + 1);
}

// Clear screen

void clrscr(void)
{
    __InitEasyWin();
    memset(ScreenBuffer, ' ', _ScreenSize.x * _ScreenSize.y);
    _Cursor.x = 0;
    _Cursor.y = 0;
    _Origin.x = 0;
    _Origin.y = 0;
    SetScrollBars();
    InvalidateRect(CrtWindow, NULL, TRUE);
    UpdateWindow(CrtWindow);
}

// Clear to end of line

void clreol(void)
{
    __InitEasyWin();
    memset(ScreenPtr(_Cursor.x, _Cursor.y), ' ',_ScreenSize.x - _Cursor.x);
    ShowText(_Cursor.x, _ScreenSize.x);
}

// WM_CREATE message handler

static void WindowCreate(void)
{
    Created = TRUE;
    ScreenBuffer = (char *) malloc(_ScreenSize.x * _ScreenSize.y);
    memset(ScreenBuffer, ' ', _ScreenSize.x * _ScreenSize.y);
    if (!_CheckBreak)
        EnableMenuItem(GetSystemMenu(CrtWindow, FALSE), SC_CLOSE,
                       MF_DISABLED | MF_GRAYED);
}

// WM_PAINT message handler

static void WindowPaint(VOID)
{
    int X1, X2, Y1, Y2;

    Painting = TRUE;
    InitDeviceContext();
    X1 = Max(0, PS.rcPaint.left / CharSize.x + _Origin.x);
    X2 = Min(_ScreenSize.x,
             (PS.rcPaint.right + CharSize.x - 1) / CharSize.x + _Origin.x);
    Y1 = Max(0, PS.rcPaint.top / CharSize.y + _Origin.y);
    Y2 = Min(_ScreenSize.y,
             (PS.rcPaint.bottom + CharSize.y - 1) / CharSize.y + _Origin.y);
    while (Y1 < Y2)
        {
        TextOut(DC, (X1 - _Origin.x) * CharSize.x, (Y1 - _Origin.y) * CharSize.y,
                    ScreenPtr(X1, Y1), X2 - X1);
        ++Y1;
        }
    DoneDeviceContext();
    Painting = FALSE;
}

// WM_VSCROLL and WM_HSCROLL message handler }

static int GetNewPos(int Pos, int Page, int Range, int Action, int Thumb)
{
    switch (Action)
        {
        case SB_LINEUP:
            return(Pos - 1);
        case SB_LINEDOWN:
            return(Pos + 1);
        case SB_PAGEUP:
            return(Pos - Page);
        case SB_PAGEDOWN:
            return(Pos + Page);
        case SB_TOP:
            return(0);
        case SB_BOTTOM:
            return(Range);
        case SB_THUMBPOSITION:
            return(Thumb);
        default:
            return(Pos);
        }
}

static void WindowScroll(int Which, int Action, int Thumb)
{
    int X, Y;

    X = _Origin.x;
    Y = _Origin.y;
    switch (Which)
        {
        case SB_HORZ:
            X = GetNewPos(X, ClientSize.x / 2, Range.x, Action, Thumb);
            break;
        case SB_VERT:
            Y = GetNewPos(Y, ClientSize.y, Range.y, Action, Thumb);
        }
    _ScrollTo(X, Y);
}

// WM_SIZE message handler

static void WindowResize(int X, int Y)
{
    if (Focused && Reading)
        HideCursor();
    ClientSize.x = X / CharSize.x;
    ClientSize.y = Y / CharSize.y;
    Range.x = Max(0, _ScreenSize.x - ClientSize.x);
    Range.y = Max(0, _ScreenSize.y - ClientSize.y);
    _Origin.x = Min(_Origin.x, Range.x);
    _Origin.y = Min(_Origin.y, Range.y);
    SetScrollBars();
    if (Focused && Reading)
        ShowCursor();
}

// WM_GETMINMAXINFO message handler

static void WindowMinMaxInfo(TMinMaxInfo far * MinMaxInfo)
{
    int X, Y;
    TEXTMETRIC Metrics;

    InitDeviceContext();
    GetTextMetrics(DC, &Metrics);
    CharSize.x = Metrics.tmMaxCharWidth;
    CharSize.y = Metrics.tmHeight + Metrics.tmExternalLeading;
    CharAscent = Metrics.tmAscent;
    X = Min(_ScreenSize.x * CharSize.x + GetSystemMetrics(SM_CXVSCROLL),
    GetSystemMetrics(SM_CXSCREEN)) + GetSystemMetrics(SM_CXFRAME) * 2;
    Y = Min(_ScreenSize.y * CharSize.y + GetSystemMetrics(SM_CYHSCROLL) +
    GetSystemMetrics(SM_CYCAPTION), GetSystemMetrics(SM_CYSCREEN)) +
    GetSystemMetrics(SM_CYFRAME) * 2;
    (*MinMaxInfo)[1].x = X;
    (*MinMaxInfo)[1].y = Y;
    (*MinMaxInfo)[3].x = CharSize.x * 16 + GetSystemMetrics(SM_CXVSCROLL) +
    GetSystemMetrics(SM_CXFRAME) * 2;
    (*MinMaxInfo)[3].y = CharSize.y * 4 + GetSystemMetrics(SM_CYHSCROLL) +
    GetSystemMetrics(SM_CYFRAME) * 2 + GetSystemMetrics(SM_CYCAPTION);
    (*MinMaxInfo)[4].x = X;
    (*MinMaxInfo)[4].y = Y;
    DoneDeviceContext();
}

// WM_CHAR message handler

static void WindowChar(char Ch)
{
    if (_CheckBreak  && (Ch == 3))
        Terminate();
    if (KeyCount < sizeof(KeyBuffer))
        {
        KeyBuffer[KeyCount] = Ch;
        ++KeyCount;
        }
}

// WM_KEYDOWN message handler

static void WindowKeyDown(BYTE KeyDown)
{
    BOOL CtrlDown;
    int I;

    if (_CheckBreak && (KeyDown == VK_CANCEL))
        Terminate();
    CtrlDown = (BOOL) (GetKeyState(VK_CONTROL < 0));
    for (I = 0; I < ScrollKeyCount; ++I)
        {
        if (ScrollKeys[I].Key == KeyDown && ScrollKeys[I].Ctrl == CtrlDown)
            {
        WindowScroll(ScrollKeys[I].SBar, ScrollKeys[I].Action, 0);
        return;
            }
        }
}

// WM_SETFOCUS message handler

static void WindowSetFocus(void)
{
    Focused = TRUE;
    if (Reading)
        ShowCursor();
}

// WM_KILLFOCUS message handler

static void WindowKillFocus(void)
{
    if (Reading)
        HideCursor();
    Focused = FALSE;
}

// WM_DESTROY message handler

static void WindowDestroy(void)
{
    free(ScreenBuffer);
    _Cursor.x = 0;
    _Cursor.y = 0;
    _Origin.x = 0;
    _Origin.y = 0;
    PostQuitMessage(0);
    Created = FALSE;
}

inline int LoVal(LONG LVal)
{
    return (((unsigned *)&LVal)[0]);
}

inline int HiVal(LONG LVal)
{
    return (((unsigned *)&LVal)[1]);
}

// Easy window procedure

long FAR PASCAL _export _EasyWinProc(HWND Window, UINT Message,
                                   WPARAM WParam, LONG LParam)
{
    CrtWindow = Window;
    switch (Message)
        {
        case WM_CREATE:
            WindowCreate();
            break;
        case WM_PAINT:
            WindowPaint();
            break;
        case WM_VSCROLL:
            WindowScroll(SB_VERT, WParam, LoVal(LParam));
            break;
        case WM_HSCROLL:
            WindowScroll(SB_HORZ, WParam, LoVal(LParam));
            break;
        case WM_SIZE:
            WindowResize(LoVal(LParam), HiVal(LParam));
            break;
        case WM_GETMINMAXINFO:
            WindowMinMaxInfo((TMinMaxInfo far *) LParam);
            break;
        case WM_CHAR:
            WindowChar((char)WParam);
            break;
        case WM_KEYDOWN:
            WindowKeyDown((BYTE)WParam);
            break;
        case WM_SETFOCUS:
            WindowSetFocus();
            break;

        case WM_KILLFOCUS:
            WindowKillFocus();
            break;
        case WM_DESTROY:
            WindowDestroy();
            break;
        default:
            return DefWindowProc(Window, Message, WParam, LParam);
        }
    return NULL;
}


// Create window if required

void __InitEasyWin(void)
{
   if (!Created)
       {
       CrtWindow = CreateWindow(
                       CrtClass.lpszClassName,
                       _WindowTitle,
                       WS_OVERLAPPEDWINDOW | WS_HSCROLL | WS_VSCROLL,
                       _WindowOrg.x, _WindowOrg.y,
                       _WindowSize.x, _WindowSize.y,
                       0,
                       0,
                       __hInstance,
                       NULL);
       ShowWindow(CrtWindow, __cmdShow);
       UpdateWindow(CrtWindow);
       }
}

// Destroy window if required

void _DoneEasyWin(void)
{
    if (Created)
        DestroyWindow(CrtWindow);
    exit(0);
}

// EasyWin unit exit procedure

void far __ExitEasyWin(void)
{
    char *P;
    MSG Message;
    char Title[128];

    if (Created) // && (ErrorAddr = NULL))
        {
        P = _WindowTitle;
        wsprintf(Title, _InactiveTitle, (char far *) P);
        SetWindowText(CrtWindow, Title);
        GetSystemMenu(CrtWindow, TRUE);
        _CheckBreak = FALSE;
        while (GetMessage(&Message, 0, 0, 0))
            {
            TranslateMessage(&Message);
            DispatchMessage(&Message);
            }
        }
}

void __CreateEasyWin(void)
{
    if (__hPrev == 0)
        {
        CrtClass.hInstance = __hInstance;
        CrtClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
        CrtClass.hCursor = LoadCursor(NULL, IDC_ARROW);
        CrtClass.hbrBackground = GetStockObject(WHITE_BRUSH);
        RegisterClass(&CrtClass);
        }
    GetModuleFileName(__hInstance, _WindowTitle, sizeof(_WindowTitle));
    OemToAnsi(_WindowTitle, _WindowTitle);
    __ReadBufFPtr  = _ReadBuf;
    __WriteBufFPtr = _WriteBuf;
}
