//
// Console class for PTC 2.0 C++ API
// Copyright (c) 1998 Glenn Fiedler (ptc@gaffer.org)
// This source code is licensed under the GNU LGPL
//

// adapted for dos 2.0.8

// include files
#include "Key.h"
#include "Area.h"
#include "Clip.h"
#include "Color.h"
#include "Error.h"
#include "Console.h"
#include <fstream.h>

#include <stdlib.h>   // for strtol()
#include <stdio.h>    // for printf()
#include <conio.h>    // for getch(), kbhit()

#include "VBE.h"
#include "VGA.h"

//
//  PTC 2.0 C++ API extension
//  Keyboard functions
//

#include "KeyConv.h"

char8 *keyBuffer;
void (__interrupt __far *oldKeyHandler)();
static int keyInstalled = false;
static int keyCode;
static int keyAdd;
static int keyPressed;

void __interrupt __far keyHandler()
{
  // read scan code
  char8 scan_code = inp(0x60);

  // clear keyboard controller on XT machines
  char8 temp = inp(0x61);
  outp(0x61, temp | 0x80);
  outp(0x61, temp & 0x7F);

  // store scan code in buffer
  if (scan_code == 0xe0) {
    keyAdd=128;
  } else {
    if (scan_code > 127) {
      scan_code = keyAdd+(scan_code&0x7f);
      keyCode = keyConversionTable[scan_code];
       keyBuffer[keyCode] = 0;
//      keyPressed = false;     // when console::read then false
    } else {
      scan_code = keyAdd+scan_code;
      keyCode = keyConversionTable[scan_code];
      keyBuffer[keyCode] = 1;
      keyPressed = true;
    }
    keyAdd = 0;
  }

  // acknowledge interrupt
  outp(0x20, 0x20);
}

void keyClear(void)
{
  for (int i=0; i < 256; i++)
    keyBuffer[i] = 0;
  keyPressed = false;
  keyCode = 0;
  keyAdd = 0;
}

void keyInstall(void)
{
  if (keyInstalled == true)
    throw Error("keyboard handler already installed");
  keyBuffer = new char8 [256];
  keyClear();
  oldKeyHandler = _dos_getvect(0x09);
  _dos_setvect(0x09,keyHandler);
  keyInstalled = true;
}

void keyRemove(void)
{
  if (keyInstalled == false)
    throw Error("no keyboard handler installed");
  _dos_setvect(0x09,oldKeyHandler);
  delete [] keyBuffer;
  keyInstalled = false;
}

bool Console::isKeyPressed(int code)
{
  if (m_keyboard_interrupt) {
    // return status from keyboard buffer
    return keyBuffer[code];
  } else {
    if (code == keyCode)
      return true;
    else
      return false;
  }
}

//
//
//

Console::Console()
{
    //
    // Console constructor
    // -------------------
    //
    // When a console is created it is initially closed and unlocked.
    //
    // Most functions of the console cannot be used until the
    // console has been opened with "Console::open".
    //

    printf("using ptc dos 2.0.8 ");
#ifdef X86_ASSEMBLER
    printf("with assembly optimalisation");
#endif
    printf("\n");

    // defaults
    m_width  = 0;
    m_height = 0;
    m_pitch  = 0;
    m_open   = false;
    m_locked = false;

    // ptc dos defaults
    m_vbe = 0;
    m_vga = 0;
    m_offscreen = 0;

    // configuration defaults:
    m_allow_linear = true;
    m_allow_banked = true;
    m_force_offscreen = false;
    m_flexible = true;
    m_wait_retrace = true;
    m_wide_dac = true;
    m_pmode_interface = false;
    m_vga_modes = true;
    m_fakemode_type = FAKEMODE2A;
    m_default_width = 320;
    m_default_height = 200;
    m_keyboard_interrupt = true;

    // configure console
    configure("ptc.cfg");
}


Console::~Console()
{
    //
    // Console destructor
    // ------------------
    //    
    // The destructor automatically closes the console if it is open.
    //

    // close
    close();
}




bool Console::option(const char option[])
{
    // 
    // Console option string
    // ---------------------
    //
    // Pass a platform specific option string to the console.
    //
    // Returns 'true' if the option string was recognized, 'false'
    // if the console did not recognize or accept it.
    //
    // This implementation also passes the option string to the
    // copy object in order to provide control over copying
    // from the console to other surfaces. See "Copy::option"
    // for more details on copy option strings.
    //

    // option handled flag
    bool recognized = true;

    // [platform dependent option string interpretation code]

    // these all use strnicmp so that trailing spaces are ignored.
    // makes it much simpler to add strings to a config file..
    // "echo no vga modes >> ptc.cfg"
    if (!strnicmp(option, "no vbe modes", 12)) {
        m_allow_linear = false;
        m_allow_banked = false;
    } else if (!strnicmp(option, "no linear modes", 15)) {
        m_allow_linear = false;
    } else if (!strnicmp(option, "no banked modes", 15)) {
        m_allow_banked = false;
    } else if (!strnicmp(option, "force offscreen buffering", 25)) {
        m_force_offscreen = true;
    } else if (!strnicmp(option, "force exact match", 17)) {
        m_flexible = false;
    } else if (!strnicmp(option, "no retrace checking", 19)) {
        m_wait_retrace = false;
    } else if (!strnicmp(option, "disable wide dac", 16)) {
        m_wide_dac = false;
    } else if (!strnicmp(option, "no vga modes", 12)) {
        m_vga_modes = false;
    } else if (!strnicmp(option, "enable pmode interface", 22)) {
        m_pmode_interface = true;
    } else if (!strnicmp(option, "fakemode ", 9)) {
        const char *fmtype = &option[9];
        if (!strnicmp(fmtype, "1a", 2)) m_fakemode_type = FAKEMODE1A;
        else if (!strnicmp(fmtype, "1b", 2)) m_fakemode_type = FAKEMODE1B;
        else if (!strnicmp(fmtype, "1c", 2)) m_fakemode_type = FAKEMODE1C;
        else if (!strnicmp(fmtype, "2a", 2)) m_fakemode_type = FAKEMODE2A;
        else if (!strnicmp(fmtype, "2b", 2)) m_fakemode_type = FAKEMODE2B;
        else if (!strnicmp(fmtype, "2c", 2)) m_fakemode_type = FAKEMODE2C;
        else if (!strnicmp(fmtype, "3a", 2)) m_fakemode_type = FAKEMODE3A;
        else if (!strnicmp(fmtype, "3b", 2)) m_fakemode_type = FAKEMODE3B;
        else if (!strnicmp(fmtype, "3c", 2)) m_fakemode_type = FAKEMODE3C;
        else recognized = false;
    } else if (!strnicmp(option, "default width ", 14)) {
        long dw = strtol(&option[14], NULL, 10);
        if (dw != 0) m_default_width = dw;
    } else if (!strnicmp(option, "default height ", 15)) {
        long dw = strtol(&option[15], NULL, 10);
        if (dw != 0) m_default_height = dw;
    } else if (!strnicmp(option, "no keyboard interrupt", 21)) {
        m_keyboard_interrupt = false;
    } else {
        // option not recognised
        recognized = false;
    }

    // pass the option along to the copy object as well
    if (m_copy.option(option)) recognized = true;

    // return recognized flag
    return recognized;
}




void Console::open(const char title[])
{
    //
    // Open console
    // ------------
    //
    // This function opens a console with the default width and height
    // and pixel format.
    //
    // This open function should always succeed, returning the best
    // mode is available on the platform. ie. a 32bit truecolor display
    // mode will be the ideal, an indexed 8bit mode the last resort.
    //
    // It is up to the implementer to determine the best default
    // pixel format for the console.
    //
    // See the other open functions below for more details.
    //

    // default format
    Format format(32,0x00FF0000,0x0000FF0000,0x000000FF);

    // open console
    open(title,format);
}


void Console::open(const char title[],const Format &format)
{
    //
    // Open console
    // ------------
    //
    // This function opens a console with the default width and height
    // available.
    //
    // It is up to the implementer to determine what width and height
    // should be the default for the platform. If in doubt, leave the
    // choice up to the window manager (if applicable), or pick the
    // most commonly supported display mode (eg. 320x200 on PC VGA etc,
    // or 640x480 under VESA and DirectX).
    //
    // See the other open function below for more details.
    //

    // open console
    open(title,m_default_width,m_default_height,format);
}


void Console::open(const char title[],int width,int height,const Format &format)
{
    //
    // Open console
    // ------------
    //
    // Opening a console means different things on different platforms.
    // Under window systems opening a console generally means opening
    // a window to display the console output, under fullscreen graphics
    // systems opening a console corresponds to setting a display mode.
    //
    // It is transparent to the user what actually happens when a console
    // is opened. The only thing that matters is that somehow the console
    // provides graphical output to the user.
    //
    // If the platform supports it, the title of the window, application, 
    // or whatever the operating system uses to identify the title of the 
    // application should be set to the 'title' parameter. For example, 
    // under Win32 the title bar of the window would be set to 'title'.
    // Under DOS the title would be ignored because the platform does not
    // support it.
    //
    // The width and height parameters are only hints, if for example, a
    // display mode exists that matches them exactly then that display mode
    // should always be set, however if it is not possible to set a display
    // mode matching the width and height requested, then a display mode, 
    // window or whatever can be opened with the 'best match' to the width
    // and height. What the best match is, of course is entirely dependent
    // on the platform. The only criteria for the match is this: it must be
    // possible to copy a surface of size width,height to the console. If
    // this is not possible, then the call to open should fail.
    //
    // As with the width and height parameters, the 'format' parameter
    // is only a hint and the implementer is free to open a display with
    // any pixel format they want - provided that it is possible to convert
    // from the 'format' specified to the pixel format of the console.
    //
    // The open function must always make sure that it closes any existing
    // display windows that have been opened in this class, and that it
    // can correctly manage multiple consoles being opened at once in one
    // program. For example, its perfectly valid in a windowed environment
    // to have multiple consoles in a program, each console created just
    // creates its own window - however, in a fullscreen graphics system,
    // it is not allowed because there is only one display. So, if you
    // are coding for a windowed system, make sure that multiple consoles
    // work, and if you are coding for a fullscreen system, make sure that
    // when more than one console is created you fail by throwing an "Error".
    //

    // close
    close();

    // [platform dependent code to open the console]

    // don't allow multiple consoles
    if( m_open )
      return;

    // first try VBE
    try {

        // set up flags to VBE constructor
        int vbeflags = (m_flexible)
                     | (m_wide_dac<<1)
                     | (m_pmode_interface<<2)
                     | (m_allow_banked<<3)
                     | (m_allow_linear<<4);

        m_vbe = new VBE(width, height, format.bytes()-1, format.direct(), vbeflags);

        // setup data
        m_width  = m_vbe->width();
        m_height = m_vbe->height();
        m_pitch  = m_vbe->pitch();
        m_bufmode = m_vbe->pages();
        m_banked = m_vbe->banked();
        m_format = m_vbe->format();

        // set bufmode to 1 when system memory buffering is forced
        if (m_force_offscreen) m_bufmode = 1;

        if (m_bufmode > 1) {
            // set up double/triple buffering
            m_displaypage = 0;
            m_drawpage = 1;
            m_sparepage = 2;
        } else m_drawpage = 0;

        if ((m_bufmode == 1) || m_banked) {
            m_pitch = m_width * m_format.bytes();
            m_offscreen = new char[m_pitch*m_height];
            if (!m_offscreen) throw Error("out of memory");
            memset(m_offscreen, 0, m_pitch*m_height);
        }
    }
    catch (Error e)
    {
        // if VGA allowed, try it, otherwise rethrow
        if (!m_vga_modes) throw Error("unable to find a video mode");
        if (m_vbe) {
            delete m_vbe;
            m_vbe = 0;
        }

        int vgamodetype = 0;    // assume fakemode
        if (format.bits() == 8) {
            vgamodetype = 2;
            // rgb332?
            if (format.direct()) vgamodetype = 1;
        }

        m_vga = new VGA(width, height, vgamodetype, m_fakemode_type);

        // setup data
        m_width = m_vga->width();
        m_height = m_vga->height();
        m_format = m_vga->format();
        m_pitch = m_vga->pitch();
        m_bufmode = m_vga->pages();

        // setup offscreen buffer
        m_offscreen = new char[m_height*m_pitch];
        if (!m_offscreen) throw Error("out of memory");
        memset(m_offscreen, 0, m_height*m_pitch);
    }

    // clear palette
    palette(Palette());

    // setup keyboard interrupt hook
    if (m_keyboard_interrupt)
        keyInstall();

    // console is open
    m_open = true;

    //
    // On some platforms the 'pitch' is different to what is coded above.
    // In this case you must query the platform for the pitch of the display,
    // instead of just calculating it from the width.
    //
    // See Console::pitch for more information.
    //
}


void Console::close()
{
    //
    // Close console
    // -------------
    //
    // Closing a console leaves any fullscreen display mode or closes
    // any display windows that were created with the last call to "open".
    //
    // Close should be able to handle being called multiple times without
    // problems. For example, if close is called and the console is not
    // open then close should just return without doing anything.
    //
    // Note that close makes sure that if the console is locked then
    // it is cleaned up by calling unlock.
    //
    // Any key presses that remain buffered since the last call to "open"
    // are flushed by close. This prevents garbage key strokes being passed
    // on from one display to another, or being passed back to the operating
    // system after the program exits.
    //

    // check if open
    if (m_open)
    {
        // unlock the console if required
        if (m_locked) unlock();

        // flush all key presses
        while (key()) read();

        // [platform dependent code to close the console]
        if (m_vbe) {
            delete m_vbe;
            m_vbe  = 0;
        }

        if (m_vga) {
            delete m_vga;
            m_vga = 0;
        }

        if (m_offscreen) {
            delete[] m_offscreen;
            m_offscreen = 0;
        }

        // defaults
        m_width  = 0;
        m_height = 0;
        m_pitch  = 0;
        m_format = Format();
        m_locked = false;

        // remove keyboard
        if (m_keyboard_interrupt) {
          keyRemove();
          m_keyboard_interrupt = false;
        }

        // console is closed
        m_open = false;
    }
}




void Console::flush()
{
    //
    // Flush console operations
    // ------------------------
    //
    // This function operates in the style of glFlush
    //
    // This function is only valid if the console is open and unlocked.
    //

    // debug checks
    check_open();
    check_unlocked();

    // [platform dependent code to flush all console operations]
}


void Console::finish()
{
    //
    // Finish console operations
    // -------------------------
    //
    // This function operates in the style of glFinish
    //
    // This function is only valid if the console is open and unlocked.
    //

    // debug check
    check_open();
    check_unlocked();

    // [platform dependent code to finish all console operations]
}

inline void Console::update_page_variables()
{
    if (m_bufmode > 1) {
        int tmp = m_displaypage;
        m_displaypage = m_drawpage;
        if (m_bufmode > 2) {
            m_drawpage = m_sparepage;
            m_sparepage = tmp;
        } else {
            m_drawpage = tmp;
        }
    }
}


void Console::update()
{
    //
    // Update console
    // --------------
    //
    // This function updates the screen with the contents of the console.
    //
    // On different platforms, the exact implementation of Console::update
    // will vary widely. 
    //
    // The best case scenario is that where there are multiple video pages 
    // in memory, and Console::update just swaps the pages and makes the page 
    // that was just rendered to the current visual page. If the platform
    // supports synchronizing page flips to the vertical retrace then
    // it should be used by default.
    //
    // Another situation is where you have only one video memory page (the
    // visual page). In this case you should typically render to buffer in
    // system memory, and on each call to update just copy the system memory
    // surface to the display memory.
    //
    // The final scenario is where there is no direct access to video memory.
    // This is quite typical under windowed systems, and in this case update
    // would just copy the contents of the console to the screen. For example,
    // under Win32 it would just draw the console bitmap inside the window.
    //
    // It is up to the implementer to determine what type of strategy they
    // will use when coding the console for their platform.
    //
    // The contents of the back buffer (ie. console rendering page) are 
    // undefined after an update. If the entire buffer is not drawn over,
    // for example, copying a surface over the whole console each frame,
    // then the console must be cleared using Console::clear in the areas
    // that were not covered.
    //
    // This function is only valid if the console is open and unlocked.
    //

    // debug check
    check_open();
    check_unlocked();

    // [platform dependent code for updating the console]
    if (m_vbe) {
        if (!m_banked) {
            if (m_bufmode > 1) {
                // flip to last drawn page
                m_vbe->page_flip(m_drawpage, m_wait_retrace);
                update_page_variables();
            } else {
                // wait for retrace if requested
                if (m_wait_retrace) m_vbe->page_flip(0, 1);

                char *d = (char *)m_vbe->lock(0);
                char *s = m_offscreen;
                int dpitch = m_vbe->pitch();
                for (int i=0; i<m_height; i++) {
                    memcpy(d, s, m_pitch);
                    d += m_vbe->pitch();
                    s += m_pitch;
                }
                m_vbe->unlock();
            }
        } else {
            m_vbe->banked_load(m_offscreen, m_drawpage, m_wait_retrace);
            update_page_variables();
        }
    } else if (m_vga) {
        if (m_vga->fakemode()) {
            m_vga->fakemode_load(m_offscreen, m_wait_retrace);
        } else {
            if (m_wait_retrace) m_vga->wait_retrace();
            memcpy(m_vga->lock(), m_offscreen, m_pitch*m_height);
            m_vga->unlock();
        }
    }
}

inline void Console::area_copy(char *dest, char *src, const Area &area,
    int fmtbytes, int width, int pitch)
{
    int btc = (area.right() - area.left()) * fmtbytes;
    int lbs = area.left() * fmtbytes;
    int wbs = width * fmtbytes;

    dest += area.top() * pitch;
    src += area.top() * wbs;
    for (int y=area.top(); y < area.bottom(); y++) {
        memcpy(dest+lbs, src+lbs, btc);
        dest += pitch;
        src += wbs;
    }
}


void Console::update(const Area &area)
{
    //
    // Update console area
    // -------------------
    //
    // This function updates the contents of the console, hinting that only
    // the part of the console contained in the 'area' parameter needs to 
    // actually be updated.
    //
    // This can give quite a speedup under windowed systems where you would
    // otherwise have to tell the gui to draw the entire console bitmap each
    // call to update.
    //
    // However, the area parameter is only a hint. There is no guarantee that
    // only this area will be updated. For example, a page flipping system
    // cannot update only a small area of the console, it can only update
    // all or nothing. In the case of page flipping systems, just ignore the
    // area (as coded below) and update the whole console.
    //
    // If you replace the code below with your own area update, please make
    // sure you copy the debug check code from the update function above
    // into your area update code.
    //
    // This function is only valid if the console is open and unlocked.
    //

    // debug check
    check_open();
    check_unlocked();

    // [platform dependent code for updating the console]
    if (m_vbe) {
        if (m_banked) {
            m_vbe->banked_area_load(m_offscreen, area.top(), area.left(),
                area.bottom(), area.right(), m_drawpage, m_wait_retrace);
            update_page_variables();
        } else if (m_bufmode > 1) {
            // area updates are impossible in double/triple buffered modes.
            update();
        } else {
            if (m_wait_retrace) m_vbe->page_flip(0, 1);

            // copy area to screen
            area_copy((char *)m_vbe->lock(0), m_offscreen,
                area, m_format.bytes(), m_width, m_vbe->pitch());

            m_vbe->unlock();
        }
    } else if (m_vga) {
        if (m_vga->fakemode()) {
            update();
        } else {
            // wait for retrace if necessary
            if (m_wait_retrace) m_vga->wait_retrace();

            // copy area to screen
            area_copy((char *)m_vga->lock(), m_offscreen,
                area, m_format.bytes(), m_width, m_vga->pitch());

            m_vga->unlock();
        }
    }

}




bool Console::key()
{
    //
    // Check for key press
    // -------------------
    //
    // This function should return 'true' if a key has been pressed
    // and is ready to be read from the keyboard buffer with 'read'.
    // 
    // The operation of this function is similar to the 'kbhit' 
    // function that PC programmers are used to under DOS and Win32
    // console applications.
    //
    // Console::key returns immediately whether or not a key has
    // been pressed.
    //
    // This function is only valid if the console is open.
    //

    // debug check
    check_open();
    
    // [platform dependent code to check if a key has been pressed]
    if (m_keyboard_interrupt)
      return keyPressed;
    else
      return kbhit();
}

                         
Key Console::read()
{
    //
    // Read key
    // --------
    //
    // This function reads a key press from the key buffer. If there
    // is no key presses stored in the key buffer then Console::read
    // will block until a key has been pressed.
    //
    // The operation of this function is similar to the 'getch'
    // function that PC programmers are used to under DOS and Win32
    // console applications.
    // 
    // The return value of this function is a "Key" object. It is the
    // responsibility of the implementer to translate from the native
    // platform keyboard input to the format that PTC uses. See the
    // "Key" object for more information.
    //
    // This function is only valid if the console is open.
    //

    // debug check
    check_open();

    // [platform dependent code to read a buffered key press]
/*
    int code = getch();    // may implement read() using direct BIOS data area access.
    bool alt = false;
    bool shift = false;
    bool control = false;
*/
    int code;
    bool alt,shift,control;
    if (m_keyboard_interrupt) {
      code = keyCode;
      alt = keyBuffer[Key::ALT];
      shift = keyBuffer[Key::SHIFT];
      control = keyBuffer[Key::CONTROL];
      keyPressed = false;
    }
    else {
      code = getch();
      alt = false;
      shift = false;
      control = false;
    }

    // [platform dependent code to translate keyboard input to PTC "Key" code]

    // return key object
    return Key(code,alt,shift,control);
}




void Console::copy(BaseSurface &surface)
{
    //
    // Copy console to surface
    // -----------------------
    //
    // This function copies the contents of the console to the
    // surface specified by the parameter 'surface'.
    //
    // If the width and height of the console does not match the
    // width and height of the surface then the contents of the
    // console will be stretched to fix the destination surface
    // if possible. If not possible, and exception should be
    // thrown to indicate failure.
    //
    // If the pixel formats of the console and surface are not
    // identical then the console pixels will be converted to
    // the destination pixels during the copy if possible. If
    // a conversion is not possible an exception will be thrown
    // to indicate failure.
    //
    // This function is only valid if the console is open and unlocked.
    //

    // debug check
    check_open();
    check_unlocked();
    
    //
    // a) unlockable console
    //

    // [platform dependent code to copy console to surface]

    //
    // b) lockable console
    //

    // lock console
    void *pixels = lock();

    try
    {
        // load surface pixels to other surface
        surface.load(pixels,width(),height(),format(),palette());

        // unlock
        unlock();
    }
    catch (Error &error)
    {
        // unlock
        unlock();

        // error message
        throw Error("failed to copy console to surface",error);
    }
}


void Console::copy(BaseSurface &surface,const Area &source,const Area &destination)
{
    //
    // Copy console area to surface area
    // ---------------------------------
    //
    // This function copies the area 'source' of the console to the
    // area 'destination' of the surface specified by the parameter 
    // 'surface'.
    //
    // If the width and height of the source and destination rectangles 
    // do not match then the source console pixels should be stretched 
    // to fit the destination surface area. If this is not possible an
    // exception should be thrown to indicate failure.
    //
    // This function is only valid if the console is open and unlocked.
    //

    // debug check
    check_open();
    check_unlocked();
    
    //
    // a) unlockable console
    //

    // [platform dependent code to copy console area to surface area]

    //
    // b) lockable console
    //

    // lock console
    void *pixels = lock();

    try
    {
        // load surface pixels to other surface
        surface.load(pixels,width(),height(),source,destination,format(),palette());

        // unlock
        unlock();
    }
    catch (Error &error)
    {
        // unlock
        unlock();

        // error message
        throw Error("failed to copy console area to surface area",error);
    }
}




void* Console::lock()
{
    //
    // Lock console
    // ------------
    //
    // This function locks the console pixels and returns a pointer
    // to the console pixels with which the user can read and write
    // directly to the console.
    //
    // Every successful lock (one that does not throw an exception)
    // must be matched with a call to unlock. The time between lock
    // and unlock should not be kept to a minimum. Locking the console
    // once and using that pointer for the rest of the program is NOT
    // acceptable.
    //
    // Only one lock is allowed at a time on a surface. When the console
    // is already locked and lock is called, an exception must be thrown
    // to indicate this.
    //
    // Other console operations such as "load", "save", "copy" may not
    // be called while a console is locked, typically because these
    // functions depend on lock/unlock internally and cannot work when
    // the console is already locked (only one lock at a time).
    //
    // While the console is locked, it must remain static. No parameters
    // such as width, height, pitch, format may change, and the pixels
    // pointer may not change. While the console is not locked, all of
    // the parameters may change under certain situations (ie. width
    // and height may change if the user resizes a window etc.)
    //
    // Even the pointer to the console pixels may change while the console
    // is unlocked. After Console::unlock is called, the pixels pointer
    // returned is invalid and cannot be used for reading or writing.
    //
    // Having support for console locking is NOT manditory. On some 
    // platforms (usually windowed platforms) locking the display memory 
    // directly is not possible. These platforms typically load pixels
    // to a bitmap area that does not allow direct read/write access
    // to this bitmap (ie. GDI under Win32 with HBITMAPs).
    //
    // When locking is not supported by the platform just throw an error
    // as below to indicate failure. If locking is supported, remove
    // the error throw in a) and implement the platform dependent code 
    // to lock the console pixels in b).
    //
    // This function is only valid if the console is open and unlocked.
    //

    // debug check
    check_open();
    
    // fail if the console is already locked
    if (m_locked) throw Error("console is already locked");

    //
    // a) unlockable console
    //

    //
    // b) lockable console
    //

    // [platform dependent code to get pointer to console pixels]

    // get pixels pointer
    void *pixels=NULL;
    if (m_vbe) {
        if ((m_bufmode > 1) && (!m_banked)) {
            pixels = m_vbe->lock(m_drawpage);
        } else return (void *)m_offscreen;
    } else if (m_vga) {
        pixels = (void *)m_offscreen;
    }
    if( pixels == NULL )
      throw Error("there doesn't appear to be a video interface.");

    // we are locked
    m_locked = true;

    // return pixels
    return pixels;
}


void Console::unlock()
{
    //
    // Unlock console
    // --------------
    //
    // This function unlocks the console, which was previously
    // locked with a call to "Console::lock".
    //
    // See the lock function for more information on locking
    // and unlocking consoles.
    //
    // This function is only valid if the console is open.
    //

    // debug check
    check_open();
    
    // fail if the console is not locked
    if (!m_locked) throw Error("console is not locked");

    //
    // a) unlockable console
    //

    //
    // b) lockable console
    //

    // [platform dependent code to unlock console pixels]
    if (m_vbe) m_vbe->unlock();

    // we are unlocked
    m_locked = false;
}




void Console::load(const void *pixels,int width,int height,const Format &format,const Palette &palette)
{
    //
    // Load pixels to console
    // ----------------------
    //
    // This function loads the pixels specified by 'pixels' to the console.
    //
    // The parameters 'width', 'height', 'format' describe the pixels to
    // be loaded. If the width and height do not match the size of the 
    // console, if possible the console should stretch the pixels when
    // they are loaded to the console. If stretching is not supported
    // an exception should be thrown.
    //
    // The 'palette' parameter is a reference to a Palette object that
    // interally is stored as a 256 entry array of 32bit color integers.
    // This palette is used to lookup from 8bit indexed color pixels to
    // truecolor pixels. When loading 8bit indexed pixels to a console
    // that is 8bit indexed as well, the pixels are just copied without 
    // regard for the palette specified (ie. they use the console palette).
    //
    // If the console is lockable then you can use option b) below, and the
    // copy object will do all the work for you! However, if you console
    // implementation is not lockable (see Console::lock) then you will
    // have to load pixels to the console yourself using whatever method 
    // the platform supports.
    //
    // When a console is locked, Console::load is undefined. You can only
    // load pixels to a console while it is unlocked.
    //
    // This function is only valid if the console is open and unlocked.
    //

    // debug check
    check_open();
    check_unlocked();
    
    //
    // a) unlockable console
    //

    // [platform dependent code to load pixels to console]

    //
    // b) lockable console
    //

    // lock console
    void *console_pixels = lock();

    try
    {
        // request format conversion
        m_copy.request(format,this->format());
    
        // set copy palettes
        m_copy.palette(palette,this->palette());

        // copy pixels to surface
        m_copy.copy(pixels,0,0,width,height,width*format.bytes(),console_pixels,0,0,this->width(),this->height(),this->pitch());

        // unlock
        unlock();
    }
    catch (Error &error)
    {
        // unlock
        unlock();

        // error message
        throw Error("failed to load pixels to console",error);
    }
}


void Console::load(const void *pixels,int width,int height,const Area &source,const Area &destination,const Format &format,const Palette &palette)
{
    //
    // Load pixels to console area
    // ---------------------------
    //
    // This function loads an area of the pixels specified in the 'pixels'
    // parameter to an area of the console.
    //
    // If the source an destination area width and height do not match
    // then the source pixels loaded should be stretched to fit the
    // destination console area.
    //
    // See the previous Console::load function for more information on
    // loading pixels to the console.
    //
    // This function is only valid if the console is open and unlocked.
    //

    // debug check
    check_open();
    check_unlocked();
    
    //
    // a) unlockable console
    //

    // [platform dependent code to load pixels to console area]

    //
    // b) lockable console
    //

    // lock console
    void *console_pixels = lock();

    try
    {
        // clip source and destination areas
        Area clipped_source,clipped_destination;
        Clip::clip(Area(0,0,width,height),source,clipped_source,
                   Area(0,0,this->width(),this->height()),destination,clipped_destination);

        // request format conversion
        m_copy.request(format,this->format());
    
        // set copy palettes
        m_copy.palette(palette,this->palette());

        // copy pixels to surface
        m_copy.copy(pixels,clipped_source.left(),clipped_source.top(),clipped_source.width(),clipped_source.height(),width*format.bytes(),
                    console_pixels,clipped_destination.left(),clipped_destination.top(),clipped_destination.width(),clipped_destination.height(),pitch());

        // unlock
        unlock();
    }
    catch (Error &error)
    {
        // unlock
        unlock();
        
        // error message
        throw Error("failed to load pixels to console area",error);
    }
}




void Console::save(void *pixels,int width,int height,const Format &format,const Palette &palette)
{
    //
    // Save console pixels
    // -------------------
    //
    // This function saves the contents of the current rendering
    // buffer to the pixel array pointed to by the 'pixels' parameter.
    //
    // The width and height parameters describe the dimensions of
    // the destination pixel buffer. If the width and height do not
    // match the width and height of the console then the console
    // pixels should be stretched to fit if possible. If not possible
    // to stretch the pixels then an exception should be thrown to
    // indicate failure.
    //
    // The format parameter describes the pixel format of the
    // destination pixels. If the pixel format does not match the 
    // format of the console then the console pixels will be
    // converted to the destination pixels format if possible.
    // If not possible and exception should be thrown.
    //
    // The palette parameter passed describes the palette of the
    // destination pixels. If the destination pixel format is
    // not indexed color then this parameter is ignored.
    //
    // This function is only valid if the console is open and unlocked.
    //

    // debug check
    check_open();
    check_unlocked();
    
    //
    // a) unlockable console
    //

    // [platform dependent code to save console pixels]

    //
    // b) lockable console
    //

    // lock console
    void *console_pixels = lock();

    try
    {
        // request format conversion
        m_copy.request(m_format,format);
    
        // set copy palettes
        m_copy.palette(this->palette(),palette);

        // copy console pixels to 'pixels' buffer
        m_copy.copy(console_pixels,0,0,this->width(),this->height(),this->pitch(),pixels,0,0,width,height,width*format.bytes());

        // unlock
        unlock();
    }
    catch (Error &error)
    {
        // unlock
        unlock();
        
        // error message
        throw Error("failed to save console pixels",error);
    }
}


void Console::save(void *pixels,int width,int height,const Area &source,const Area &destination,const Format &format,const Palette &palette)
{
    //
    // Save console area pixels
    // ------------------------
    //
    // This function saves an area of the console to an area of
    // the pixels buffer pointed to by the 'pixels' parameter.
    //
    // If the source area and the destination area do not match
    // then the console pixels will be stretched to fit the
    // destination rectangle if possible. If it is not possible
    // to stretch the pixels then an exception will be thrown.
    //
    // For information about the width, height, format and palette
    // parameters see the previous save function.
    //
    // This function is only valid if the console is open and unlocked.
    //

    // debug check
    check_open();
    check_unlocked();
    
    //
    // a) unlockable console
    //

    // [platform dependent code to save console pixel area]

    //
    // b) lockable console
    //

    // lock console
    void *console_pixels = lock();

    try
    {
        // clip source and destination areas
        Area clipped_source,clipped_destination;
        Clip::clip(Area(0,0,this->width(),this->height()),source,clipped_source,
                   Area(0,0,width,height),destination,clipped_destination);

        // request format conversion
        m_copy.request(this->format(),format);
    
        // set copy palettes
        m_copy.palette(this->palette(),palette);

        // copy console pixels to 'pixels' buffer
        m_copy.copy(console_pixels,clipped_source.left(),clipped_source.top(),clipped_source.width(),clipped_source.height(),pitch(),
                    pixels,clipped_destination.left(),clipped_destination.top(),clipped_destination.width(),clipped_destination.height(),width*format.bytes());
    }
    catch (Error &error)
    {
        // unlock
        unlock();

        // error message
        throw Error("failed to save console area pixels",error);
    }
}




void Console::clear()
{
    //
    // Clear console
    // -------------
    //
    // This function clears the console to black in direct color,
    // and to index zero in indexed color.
    //
    // This function is only valid if the console is open and unlocked.
    //

    // debug check
    check_open();
    check_unlocked();
    
    // check console format
    if (format().direct())
    {
        // direct color
        clear(Color(0,0,0,0));
    }
    else
    {
        // indexed color
        clear(Color(0));
    }
}


void Console::clear(const Color &color)
{
    //
    // Clear console
    // -------------
    //
    // This function clears the console to the specified color.
    //
    // This function is only valid if the console is open and unlocked.
    // 

    // debug check
    check_open();
    check_unlocked();
    
    //
    // a) unlockable console
    //

    // [platform dependent code to clear console]

    //
    // b) lockable console
    //

    // lock console
    void *pixels = lock();

    try
    {
        // request clear
        m_clear.request(format());

        // clear console
        m_clear.clear(pixels,0,0,width(),height(),pitch(),color);

        // unlock
        unlock();
    }
    catch (Error &error)
    {
        // unlock
        unlock();

        // error message
        throw Error("failed to clear console",error);
    }
}


void Console::clear(const Color &color,const Area &area)
{
    //
    // Clear console area
    // ------------------
    //
    // This function clears an area of the console to the specified 
    // color.
    //
    // This function is only valid if the console is open and unlocked.
    //

    // debug check
    check_open();
    check_unlocked();
    
    //
    // a) unlockable console
    //

    // [platform dependent code to clear console area]

    //
    // b) lockable console
    //

    // lock console
    void *pixels = lock();

    try
    {
        // clip area
        Area clipped_area = Clip::clip(Area(0,0,width(),height()),area);

        // request clear
        m_clear.request(format());

        // clear console area
        m_clear.clear(pixels,clipped_area.left(),clipped_area.top(),clipped_area.width(),clipped_area.height(),pitch(),color);

        // unlock
        unlock();
    }
    catch (Error &error)
    {
        // unlock
        unlock();

        // error message
        throw Error("failed to clear console area",error);
    }
}




void Console::palette(const Palette &palette)
{
    //
    // Set console palette
    // -------------------
    //
    // This function sets the palette of the console.
    //
    // Even though the console palette exists only for indexed
    // pixel formats, a call to Console::palette should fail
    // silently when called while the display is in a direct color
    // format. This is because it is possible to request an indexed 
    // color format in open but retrieve a direct color format,
    // and its a silly pain in the arse to always have to check
    // if the format is indexed color everytime before setting 
    // the palette.
    //
    // The 'palette' parameter is a reference to a palette object
    // that internally stores the palette as an array of 256 color values
    // packed into a 32 bit unsigned integer:
    //
    // [a][r][g][b]
    //
    // To get a pointer to the palette data use the function Palette::data()
    //
    // Each component takes up 8bits. Zero is is black and 255
    // is maximum intensity.
    //
    // The color values can be extracted from the color integer 
    // as follows:
    //
    // char8 a = (char8) ( (color & 0xFF000000) >> 24 );
    // char8 r = (char8) ( (color & 0x00FF0000) >> 16 );
    // char8 g = (char8) ( (color & 0x0000FF00) >> 8  );
    // char8 b = (char8) ( (color & 0x000000FF) >> 0  );
    //
    // This function is only valid if the console is open.
    //

    // debug check
    check_open();
    
    // fail silently if not indexed color
    if (!format().indexed()) return;

    // get palette data
    const void *data = palette.data();

    // [platform dependent code to set the console palette]
    m_palette = palette;
    if (m_vbe) {
        m_vbe->palette(palette.data());
    } else if (m_vga) {
        m_vga->palette(palette.data());
    }
}


const Palette& Console::palette() const
{
    //
    // Get console palette
    // -------------------
    //
    // This function gets the palette of the console and
    // returns a palette object containing the palette data.
    //
    // This function is only valid if the console is open.
    //

    // debug check
    check_open();
    
    // get palette
    return m_palette;
}




int Console::width() const
{
    //
    // Get width of console
    // --------------------
    //
    // This function returns the width of the console in pixels.
    //
    // Currently this function is implemented by just returning
    // a stored width value at Console.open. If the width of the
    // console can possibly change (ie. user resizes a window and
    // the console is lockable) then the width of the console
    // should be queried from the platform each time this function
    // is called.
    //
    // This function is only valid if the console is open.
    //

    // debug check
    check_open();
    
    // get width
    return m_width;
}


int Console::height() const
{
    // 
    // Get height of console
    // ---------------------
    //
    // This function returns the height of the console in pixels.
    //
    // See the notes on Console::width above for more information
    // about reporting the dimensions of the console.
    //
    // This function is only valid if the console is open.
    //

    // debug check
    check_open();
    
    // get height
    return m_height;
}


int Console::pitch() const
{
    //
    // Get pitch of console
    // --------------------
    //
    // This function returns the pitch of the console in bytes.
    //
    // 'pitch' is defined as the number of bytes to add to a char8*
    // (byte) pointer to go from pixel [x][y] to pixel [x][y+1].
    //
    // Pitch can be any value, even negative (for bottom-up images),
    //
    // On most fullscreen platforms pitch is easily obtainable. Use
    // this value (usually something like "bytes per scanline" etc)
    // instead of just hardcoding pitch in Console.open to be equal
    // to width*format.bytes().
    //
    // On some platforms the pitch of the console can change while
    // the console is open. In this case the pitch should be queried
    // each time this function is called, instead of stored in the
    // m_pitch variable in Console::open.
    //
    // This function is only valid if the console is open.
    //

    // debug check
    check_open();
    
    // get pitch
    return m_pitch;
}


const Format& Console::format() const
{
    //
    // Get format of console
    // ---------------------
    //
    // This function returns the pixel format of the console.
    //
    // The pixel format can change while a console is open in some
    // cases. One example would be, windowed DirectX output under Win32,
    // and the user changes the display settings to another mode
    // with a different pixel format. However, in the vast majority of
    // cases the format will remain constant while the console is open.
    //
    // This function is only valid if the console is open.
    //

    // debug check
    check_open();
    
    // get format
    return m_format;
}


const char* Console::name() const
{
    //
    // Get name of console
    // -------------------
    //
    // This function returns a string representing the name of the console.
    //
    // The console name is used to identify the platform. For example, a 
    // DirectX implementation of PTC would return "DirectX" as the console
    // name, a VGA implementation would return "VGA", and so on.
    //
    // Use the standard capitalization of the platform name, and if the
    // platform name is an acronym (ie. VGA, VESA, GGI) use all upper case.
    //
    // This function is valid whether the console is opened or closed.
    //

    // get name
    return "DOS";
}




void Console::configure(const char filename[])
{
    //
    // Configure console options
    // -------------------------
    //
    // This function reads console options from the text file specified by the
    // filename parameter and pipes them through the "option" function. If the
    // configuration file does not exist then this function fails silently.
    //
    // The console constructor calls this function with the file "ptc.cfg" so 
    // that the end user of a program developed with ptc can have some level of
    // control over its operation. This is necessary because in most cases the 
    // application developer does not usually think of providing this level of 
    // flexibility to the user.
    //

    // open configuration file
    ifstream file(filename,ios::in | ios::nocreate);
    
    // check file
    if (!file) return;

    // read option strings
    while (!file.eof())
    {
        // option line
        char line[1024];

        // read line from file
        file.getline(line,1024);

        // process option
        option(line);
    }
}




void Console::check_open() const
{
    //
    // Check if console is open
    // ------------------------
    //
    // This function is used in Console member functions that
    // are only defined while the console is open.
    //
    // If __DEBUG__ is defined, then it throws an exception
    // to indicate that the console operation is not valid
    // while the console is closed.
    //
    // If __DEBUG__ is not defined, it performs no checking
    // whether or not the console is open. This is for efficiency,
    // nobody wants a check on every console.width/console.height
    // in release build!
    //
    // This function is not a part of the official PTC specification,
    // it is just here to provide useful debugging information, as
    // accidently calling functions on a closed console is a common
    // mistake made by new users.
    //
    // I recommend that you provide a similar sort of #ifdef __DEBUG__
    // checks inside your code, if you choose not to use this exact
    // open checking scheme. It is best to provide useful information
    // like this to correct errors early, and it saves people hours
    // of tracking down an obscure bug, only to find out at the end,
    // "oh, the console wasnt open... thats why my system exploded!"
    // :)
    //

    #ifdef __DEBUG__

        // check that console is open
        if (!m_open) throw Error("console is not open");

    #else

        // no checking in release build

    #endif
}


void Console::check_unlocked() const
{
    //
    // Check if console is unlocked
    // ----------------------------
    //
    // This function is used in Console member functions that
    // are only defined while the console is unlocked.
    //
    // If __DEBUG__ is defined, then it throws an exception
    // to indicate that the console operation is not valid
    // while the console not unlocked.
    //
    // If __DEBUG__ is not defined, it performs no checking
    // whether or not the console is open. This is for efficiency,
    // nobody wants a check on every console.width/console.height
    // in release build!
    //
    // This function is not a part of the official PTC specification,
    // it is just here to provide useful debugging information.
    //

    #ifdef __DEBUG__

        // check that console is unlocked
        if (!m_locked) throw Error("console is not unlocked");

    #else

        // no checking in release build

    #endif
}

// API extensions:

bool Console::vrstate()
{
    return m_wait_retrace;
}

void Console::vrstate(bool newstate)
{
    m_wait_retrace = newstate;
}


