#include <wx/wx.h>

#include "LisaCanvas.h"

#include "keyscan.h"

extern "C"
{
    #include "vars.h"
    int32 reg68k_external_execute(void);
    void cpu68k_init(void);
    void cpu68k_reset(void);
    void cpu68k_step(void);
    void unvars(void);
}
int effective_lisa_vid_size_x, effective_lisa_vid_size_y;

int screen_origin_x, screen_origin_y;

int initialize_all_subsystems(LisaConfig *lisa_config);

LisaCanvas *me;
bool floppy_just_ejected = false;
bool contrast_changed = false;

#define min(x,y) ((x)<(y)?(x):(y))
#define max(x,y) ((x)>(y)?(x):(y))

#define LISA_X          0
#define LISA_Y          0
#define LISA_WIDTH   1484
#define LISA_HEIGHT  1030

#define FLOPPY_X     1099
#define FLOPPY_Y      481
#define FLOPPY_WIDTH  331
#define FLOPPY_HEIGHT 191

#define POWER_X         0
#define POWER_Y       738
#define POWER_WIDTH  1484
#define POWER_HEIGHT  292

#define VIDEO_X       (m_skin?140:0)
#define VIDEO_Y       (m_skin?124:0)
#define VIDEO_WIDTH   720
#define VIDEO_HEIGHT  500 /*XXX shouldn't this be 364*1.5? */

#define FLOPPY_NONE    -1
#define FLOPPY_ANIM0    0
#define FLOPPY_ANIM1    1
#define FLOPPY_ANIM2    2
#define FLOPPY_PRESENT  3
#define FLOPPY_ANIM_TICK 500

#define POWER_ANIM_NONE  -1
#define POWER_ANIM_STEPS 12
#define POWER_ANIM_TICK  40

#define KEYDIR_DOWN   -1
#define KEYDIR_UP      1
#define KEYDIR_ASCII   2

/* How many cycles should the CPU run for after receiving mouse/kbd event */
#define EVENT_HANDLE_CYCLES 10000

/* How often (in ms) should the emulation run */
#define EMULATION_TICK 20

#ifdef __WXOSX__
#include <sys/param.h>
#include <CoreFoundation/CoreFoundation.h>

/*
 * Returns the absolute path of the Resources directory for this app's
 * bundle. Used for loading images etc on Mac OS X.
 *
 * NB: Be sure to free the path returned by this once you're done with it!
 */

char *getResourcesDir(void)
{
    CFURLRef pathCFURLRef;
    CFStringRef pathCFString;
    char *path;
    int pathLen;

    path = (char *)malloc(MAXPATHLEN);
    pathCFURLRef = CFBundleCopyBundleURL(CFBundleGetMainBundle()); // Get path to application bundle.
    pathCFString = CFURLCopyFileSystemPath(pathCFURLRef, kCFURLPOSIXPathStyle);
    CFStringGetCString(pathCFString, path, MAXPATHLEN, CFStringGetSystemEncoding());
    CFRelease(pathCFString);
    CFRelease(pathCFURLRef);

    pathLen = strlen(path);
    path[pathLen++] = '/';

    pathCFURLRef = CFBundleCopyResourcesDirectoryURL (CFBundleGetMainBundle()); // Get relative path to resources directory.
    pathCFString = CFURLCopyFileSystemPath(pathCFURLRef, kCFURLPOSIXPathStyle);
    CFStringGetCString(pathCFString, path + pathLen, MAXPATHLEN, CFStringGetSystemEncoding());
    CFRelease(pathCFString);
    CFRelease(pathCFURLRef);

    return path;
}
#endif

enum
{
    ID_FLOPPY_TIMER = 11001,
    ID_POWER_TIMER,
    ID_EMULATION_TIMER,
};

BEGIN_EVENT_TABLE(LisaCanvas, wxScrolledWindow)
    EVT_PAINT(LisaCanvas::OnPaint)
    EVT_SIZE(LisaCanvas::OnSize)
    EVT_MOTION(LisaCanvas::OnMouseEvent)
    EVT_LEFT_DOWN(LisaCanvas::OnMouseEvent)
    EVT_LEFT_UP(LisaCanvas::OnMouseEvent)
    EVT_KEY_DOWN(LisaCanvas::OnKeyDown)
    EVT_KEY_UP(LisaCanvas::OnKeyUp)
    EVT_CHAR(LisaCanvas::OnChar)
    EVT_TIMER(ID_FLOPPY_TIMER, LisaCanvas::OnFloppyTimer)
    EVT_TIMER(ID_POWER_TIMER, LisaCanvas::OnPowerTimer)
    EVT_TIMER(ID_EMULATION_TIMER, LisaCanvas::OnEmulationTimer)
END_EVENT_TABLE()

/* Load the skin images in from files and create wxMemoryDCs for them */
void LisaCanvas::SkinLoad(void)
{
#ifdef __WXOSX__
    char *rscDir = getResourcesDir();
    char *imgName = (char *)malloc(MAXPATHLEN);

    sprintf(imgName, "%s/lisaface.png", rscDir);
    m_lisa_bmp = new wxBitmap(imgName, wxBITMAP_TYPE_PNG);
    m_lisa_dc = new wxMemoryDC(*m_lisa_bmp);

    sprintf(imgName, "%s/floppy0.png", rscDir);
    m_floppy0_bmp = new wxBitmap(imgName, wxBITMAP_TYPE_PNG);
    m_floppy0_dc = new wxMemoryDC(*m_floppy0_bmp);

    sprintf(imgName, "%s/floppy1.png", rscDir);
    m_floppy1_bmp = new wxBitmap(imgName, wxBITMAP_TYPE_PNG);
    m_floppy1_dc = new wxMemoryDC(*m_floppy1_bmp);

    sprintf(imgName, "%s/floppy2.png", rscDir);
    m_floppy2_bmp = new wxBitmap(imgName, wxBITMAP_TYPE_PNG);
    m_floppy2_dc = new wxMemoryDC(*m_floppy2_bmp);

    sprintf(imgName, "%s/floppy3.png", rscDir);
    m_floppy3_bmp = new wxBitmap(imgName, wxBITMAP_TYPE_PNG);
    m_floppy3_dc = new wxMemoryDC(*m_floppy3_bmp);

    sprintf(imgName, "%s/floppyN.png", rscDir);
    m_floppyN_bmp = new wxBitmap(imgName, wxBITMAP_TYPE_PNG);
    m_floppyN_dc = new wxMemoryDC(*m_floppyN_bmp);

    sprintf(imgName, "%s/power_on.png", rscDir);
    m_poweron_bmp = new wxBitmap(imgName, wxBITMAP_TYPE_PNG);
    m_poweron_dc = new wxMemoryDC(*m_poweron_bmp);

    free(rscDir);
#endif
}

/* Deallocate all the skin bitmaps and DCs. */
void LisaCanvas::SkinDispose(void)
{
    delete m_lisa_dc;
    delete m_floppy0_dc;
    delete m_floppy1_dc;
    delete m_floppy2_dc;
    delete m_floppy3_dc;
    delete m_floppyN_dc;
    delete m_poweron_dc;
    delete m_lisa_bmp;
    delete m_floppy0_bmp;
    delete m_floppy1_bmp;
    delete m_floppy2_bmp;
    delete m_floppy3_bmp;
    delete m_floppyN_bmp;
    delete m_poweron_bmp;
}

/* Create a VNC-style 'dot' cursor */
void LisaCanvas::CreateCursor(void)
{
    m_dot_cursor = NULL;
#if defined(__WXX11__) || defined(__WXGTK__) || defined(__WXOSX__)

#ifdef __WXOSX__
   /* LSB (0x01) is leftmost. First byte is top row. 1 is black/visible*/
   char cursor_bits[] = {0x00, 0x0e, 0x0e, 0x0e, 0x00};
   char mask_bits[]   = {0x1f, 0x1f, 0x1f, 0x1f, 0x1f};
#else
   char cursor_bits[] = {0xe0, 0xee, 0xee, 0xee, 0xe0};
   char mask_bits[]   = {0xe0, 0xe0, 0xe0, 0xe0, 0xe0};
#endif

   wxBitmap bmp = wxBitmap(cursor_bits, 8, 5);
   bmp.SetMask(new wxMask(wxBitmap(mask_bits, 8, 5)));

   wxImage img = bmp.ConvertToImage();
   img.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, "2");
   img.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, "2");

   m_dot_cursor = new wxCursor(img);
#endif
}

/* Create the video wxMemoryDC, and set up our fast drawing structures */
void LisaCanvas::InitVideo(void)
{
    /* Init Lisa's display bitmap */
    m_video_bmp = new wxBitmap(VIDEO_WIDTH, VIDEO_HEIGHT);
    m_video_dc = new wxMemoryDC(*m_video_bmp);

    m_video_byte_bmp = new wxBitmap(8, 257);
    m_video_byte_dc = new wxMemoryDC(*m_video_byte_bmp);

    m_vram = (unsigned char *) calloc(1, 32768);

    m_black = NULL;
    m_med_grey = NULL;
    m_white = NULL;

    UpdateContrast();
}

/*
 * Draw the temporary bitmaps used to render the onto the Lisa video DC.
 * These need to be updated every time the contrast setting changes.
 */
void LisaCanvas::UpdateContrast()
{
    /*
     * The Lisa hardware has a brightness potentiometer which we ignore for
     * the moment. contrast is in the range 0x00-0xff, where 0xff is a
     * completely back screen. The Lisa OS 3.1 preference's brightest setting
     * is 0x58, and the darkest is 0xa4. LOS's power off sequence dims the
     * screen in steps of 4 from the current value to x0fc.
     */

    /*
     * For the moment just keep the brightness logic really simple: black is
     * black, and white is the the brightest grey allowed by the contrast
     * variable. There are better approximations than this, but we can worry
     * about them later.
     */
    if (m_black) {
        delete m_black; m_black = NULL;
    }
    if (m_med_grey) {
        delete m_med_grey; m_med_grey = NULL;
    }
    if (m_white) {
        delete m_white; m_white = NULL;
    }
    int c = 0xff - contrast;
    m_black    = new wxColour(0,   0,   0  );
    m_med_grey = new wxColour(c/2, c/2, c/2);
    m_white    = new wxColour(c,   c,   c  );

    /*
     * Redraw our byte map. It has 257 rows of 8 pixels. The first 256 rows
     * represent what combination of 8 pixels to draw when a give byte is
     * found in the Lisa's video memory. The last row has a 50% grey used
     * for the 'enhanced' video mode.
     */
    m_video_byte_dc->SetBackground(wxBrush(*m_white));
    m_video_byte_dc->Clear();

    m_video_byte_dc->SetPen(wxPen(*m_black));
    int i, j;
    for(j=0; j<256; j++) {
        for(i=0; i<8; i++) {
            if (j & (0x80>>i)) m_video_byte_dc->DrawPoint(i, j);
        }
    }
    m_video_byte_dc->SetPen(wxPen(*m_med_grey));
    for(i=0;i<8;i++) {
        m_video_byte_dc->DrawPoint(i, 256);
    }

    /* Mark the entire screen as needing refreshing */
    m_video_dc->SetBackground(wxBrush(*m_black));
    m_video_dc->Clear();
    m_redraw_video_dc = true;

    contrast_changed = false;
}

LisaCanvas::LisaCanvas(wxWindow *parent)
           : wxScrolledWindow(parent, -1, wxPoint(0,0), wxSize(-1,-1),
           wxHSCROLL|wxVSCROLL)
{
    /* Initialise state */
    m_power = false;
    m_paused = false;
    m_power_step = POWER_ANIM_NONE;
    m_floppy = FLOPPY_NONE;
    m_floppy_to_insert = NULL;
    m_targ_floppy = FLOPPY_NONE;
    m_throttle_speed = 5000000;
    m_raw_keyboard = false;
    m_video_mode = VIDEO_UNCORRECTED;

    SkinLoad();

    CreateCursor();

    InitVideo();

    /*
     * We don't want to call UpdateWindowUI on the parent, because this
     * constructor is probably being called from the parent's constructor.
     */
    DoSetSkinOn(true, false);

    /* Black background for video when emu is off and no skin visible */
    SetBackgroundColour(*wxBLACK);

    m_floppy_timer = new wxTimer(this, ID_FLOPPY_TIMER);
    m_power_timer = new wxTimer(this, ID_POWER_TIMER);
    m_emulation_timer = new wxTimer(this, ID_EMULATION_TIMER);

    m_lisa_config = new LisaConfig();
    m_file_config = new wxFileConfig(_T("LisaEm"),
                        _T("sunder.NET"),
                        _T("/Users/bfoley/lisaem.conf"), //XXX
                        _T("/Users/bfoley/lisaem.conf"), //XXX
                        wxCONFIG_USE_LOCAL_FILE,
                        wxConvAuto());
    m_lisa_config->Load(m_file_config, floppy_ram);

    m_lisa_configframe = NULL;

    me = this; /* XXX. I know! Horrible! */
}

LisaCanvas::~LisaCanvas()
{
    m_floppy_timer->Stop();
    m_power_timer->Stop();
    m_emulation_timer->Stop();

    if (m_floppy_timer)     delete m_floppy_timer;
    if (m_power_timer)      delete m_power_timer;
    if (m_emulation_timer)  delete m_emulation_timer;

    if (m_video_dc)         delete m_video_dc;
    if (m_video_bmp)        delete m_video_bmp;

    if (m_dot_cursor)       delete m_dot_cursor;

    if (m_floppy_to_insert) free(m_floppy_to_insert);

    delete m_lisa_config;
    delete m_file_config;

    free(m_vram);

    SkinDispose();
}

void LisaCanvas::InsertFloppy(const wxString& floppy)
{
    if (m_targ_floppy == FLOPPY_PRESENT) return;

    m_targ_floppy = FLOPPY_PRESENT;
    m_floppy = FLOPPY_NONE;

    /*
     * We can't actually insert the floppy unless the emulation is running
     * because of how interrupts are handled, so queue up a a floppy to
     * insert.
     */
    const char *s = floppy.fn_str();
    m_floppy_to_insert = strdup((char *) s);
    m_floppy_timer->Start(FLOPPY_ANIM_TICK);
}

bool LisaCanvas::IsFloppyPresent()
{
    return m_targ_floppy == FLOPPY_PRESENT;
}

void LisaCanvas::EjectFloppy()
{
    if (m_targ_floppy == FLOPPY_NONE) return;

    m_targ_floppy = FLOPPY_NONE;
    m_floppy = FLOPPY_PRESENT;

    m_floppy_timer->Start(FLOPPY_ANIM_TICK);
}

/* Accessor for the emulation power state. Starts/stops the emu running */
void LisaCanvas::SetPowerOn(bool power)
{
    if (power == m_power) return;

    m_power = power;
    m_paused = false;
    if (m_power) {
        m_power_step = 0;
        StartEmulation();
    } else {
        m_power_step = POWER_ANIM_STEPS;
        StopEmulation();
    }

    m_power_timer->Start(POWER_ANIM_TICK);

    /* If power has been switched on, show the power light immediately */
    if (m_power) {
        RefreshVirtualRect(POWER_X, POWER_Y, POWER_WIDTH, POWER_HEIGHT);
    }
}
/*
 * Accessor for the emulation power state.
 * NB: The emu can be running but paused.
 */
bool LisaCanvas::IsPowerOn()
{
    return m_power;
}

void LisaCanvas::StartEmulation()
{
    int ret=initialize_all_subsystems(m_lisa_config);
    if (ret != 0) {
        /* If -2 failed to load rom, pop up frame, and ask to reconfigure */
        printf("initialize_all_subsystems failed: %d\n", ret);
    }
    m_emulation_timer->Start(EMULATION_TICK);
    GetParent()->UpdateWindowUI();
}

/*
 * Accessor for whether or not the emulation is paused.
 * Pause/resume the emulation if it's running.
 */
void LisaCanvas::SetPaused(bool paused)
{
    if (!m_power) return;
    if (m_paused == paused) return;

    if (paused) {
        m_emulation_timer->Stop();
    } else {
        m_emulation_timer->Start();
    }
    m_paused = paused;
    GetParent()->UpdateWindowUI();
}

/*
 * Accessor for whether or not the emulation is paused.
 * NB, by definition if the power is off, the emulation is not paused.
 */
bool LisaCanvas::IsPaused()
{
    return m_paused;
}

void LisaCanvas::StopEmulation()
{
    m_emulation_timer->Stop();
    unvars();
    GetParent()->UpdateWindowUI();
}

void LisaCanvas::OnFloppyTimer(wxTimerEvent& WXUNUSED(event))
{
    printf("OnFloppyTimer: targ: %d cur: %d\n", m_targ_floppy, m_floppy);
    if (m_targ_floppy == FLOPPY_NONE) {
        m_floppy--;
    } else {
        m_floppy++;
    }

    if (m_floppy == m_targ_floppy) {
        m_floppy_timer->Stop();
        GetParent()->UpdateWindowUI();
        printf("OnFloppyTimer Final: %d %d\n", m_floppy, m_targ_floppy);
    }

    if (m_skin) {
        RefreshVirtualRect(FLOPPY_X, FLOPPY_Y, FLOPPY_WIDTH, FLOPPY_HEIGHT);
    }
}

void LisaCanvas::OnPowerTimer(wxTimerEvent& WXUNUSED(event))
{
    if (m_power) {
        if (m_power_step++ == POWER_ANIM_STEPS) {
            m_power_step = POWER_ANIM_NONE;
            m_power_timer->Stop();
        }
    } else {
        if (m_power_step-- == 0) {
            m_power_step = POWER_ANIM_NONE;
            m_power_timer->Stop();
            /* Power is finally off, update power light */
            if (m_skin) {
                RefreshVirtualRect(POWER_X, POWER_Y, POWER_WIDTH, POWER_HEIGHT);
            }
        }
    }

    RefreshVirtualRect(VIDEO_X, VIDEO_Y, VIDEO_WIDTH, VIDEO_HEIGHT);
}

/* Accessor for the emulation top speed */
int LisaCanvas::GetThrottleSpeed(void)
{
    return m_throttle_speed;
}

/* Accessor for the emulation top speed */
void LisaCanvas::SetThrottleSpeed(int hertz)
{
    m_throttle_speed = hertz;
    GetParent()->UpdateWindowUI();
}

/* Accessor for whether we're passing raw keypress events to the emulator */
bool LisaCanvas::IsRawKeyboard(void)
{
    return m_raw_keyboard;
}

/* Accessor for whether we're passing raw keypress events to the emulator */
void LisaCanvas::SetRawKeyboard(bool raw)
{
    m_raw_keyboard = raw;
    GetParent()->UpdateWindowUI();
}

/* Accessor for how to render video on the simulated display */
int LisaCanvas::GetVideoMode()
{
    return m_video_mode;
}

/* Accessor for how to render video on the simulated display */
void LisaCanvas::SetVideoMode(int mode)
{
    if (mode == m_video_mode) return;
    m_video_mode = mode;

    /*
     * Mark the video as needing to be updated, displayed to screen, and
     * update the Frame's UI (eg menu options)
     */
    m_redraw_video_dc = true;
    RefreshVirtualRect(VIDEO_X, VIDEO_Y, VIDEO_WIDTH, VIDEO_HEIGHT);
    GetParent()->UpdateWindowUI();
}

void LisaCanvas::RunEmulationFor(int cycles)
{
    XTIMER prev_clock=cpu68k_clocks;
    do {
        reg68k_external_execute();

        seek_mouse_event();
        if (floppy_6504_wait && floppy_6504_wait<128) {
            floppy_6504_wait--;  // this is dumb! it should be a timer!
        }

        get_next_timer_event();

        /* If the power's been switched off, don't try to run any further */
        if (!m_power) break;
    } while (cpu68k_clocks - prev_clock < cycles);
}

void LisaCanvas::OnEmulationTimer(wxTimerEvent& WXUNUSED(event))
{
    static wxLongLong ts_prev = 0;
    wxLongLong cur = ::wxGetLocalTimeMillis();

    XTIMER prev_clock = cpu68k_clocks;

    if (floppy_just_ejected) {
        EjectFloppy();
        floppy_just_ejected = false;
    }

    if (m_floppy_to_insert) {
        floppy_insert(m_floppy_to_insert);
        free(m_floppy_to_insert);
        m_floppy_to_insert = NULL;
    }

    /*
     * When the powerup animation is running, only run the emu for a few
     * clock cycles. This helps keep the animation smooth.
     */
    if (m_power_step != POWER_ANIM_NONE) {
        RunEmulationFor(1000);
    } else {
        RunEmulationFor(m_throttle_speed / (1000/EMULATION_TICK));
    }
    check_running_lisa_os();

    UpdateVideo();

    if (ts_prev != 0) {
        //int diff = (cur - ts_prev).GetLo();
        //int diff2 = (::wxGetLocalTimeMillis() - cur).GetLo();
        //XXX if (diff2 > 5) printf("Emu tick: %d ms since last,  %d ms for %d CPU cycles\n", diff, diff2, (int)(cpu68k_clocks - prev_clock));
    }
    ts_prev = cur;

    /* Don't draw any video if we're still powering up */
    if (m_power_step != POWER_ANIM_NONE) return;

    /* Refresh the video */
    if (m_dirty_x_max != -1) {
        int width  = (m_dirty_x_max - m_dirty_x_min) + 1;
        int height = (m_dirty_y_max - m_dirty_y_min) + 1;

        //XXX printf("refreshing (%d,%d)-(%d,%d)\n", m_dirty_x_min, m_dirty_y_min, m_dirty_x_max, m_dirty_y_max);

        RefreshVirtualRect(VIDEO_X + m_dirty_x_min, VIDEO_Y + m_dirty_y_min,
            width, height);

        m_dirty_x_min = 999; m_dirty_x_max = -1;
        m_dirty_y_min = 999; m_dirty_y_max = -1;

    }

}

/*
 * Draw the latest changes to the video ram onto our video DC, and update
 * the dirty retangle to tell us what's changed.
 */
void LisaCanvas::UpdateVideo()
{
    if (contrast_changed) {
        UpdateContrast();
        m_dirty_x_min = 0;
        m_dirty_y_min = 0;
        m_dirty_x_max = VIDEO_WIDTH;
        m_dirty_y_max = VIDEO_HEIGHT;
    }

    switch (m_video_mode) {
        case VIDEO_CORRECTED:
            UpdateVideoCorrected(); break;
        case VIDEO_ENHANCED:
            UpdateVideoEnhanced(); break;
        default:
            UpdateVideoUncorrected();
    }

    m_redraw_video_dc = false;
}

/*
 * Redraw the video using a 1 to 1 correspondance between Lisa pixels and
 * display pixels. This makes the Lisa screen look squashed vertically, as
 * Lisa pixels are 1.5x taller than they are wide, but current displays all
 * use square pixels.
 */
void LisaCanvas::UpdateVideoUncorrected(void)
{
    int i, x, y;

    i = 0;
    for(y = 0; y < 364; y++) {
        for(x = 0; x < 720 ; x+=8) {
            unsigned char c = lisaram[videolatchaddress+i];
            if (c != m_vram[i] || m_redraw_video_dc) {
                m_dirty_x_min = min(m_dirty_x_min, x);
                m_dirty_x_max = max(m_dirty_x_max, x + 7);
                m_dirty_y_min = min(m_dirty_y_min, y + 68);
                m_dirty_y_max = max(m_dirty_y_max, y + 68);
                m_video_dc->Blit(x, y + 68, 8, 1, m_video_byte_dc,
                    0, c, wxCOPY, false);
            }
            i++;
        }
    }
    memcpy(m_vram, &lisaram[videolatchaddress], 32768);
}

/*
 * Redraw the video in 'corrected' mode. This just does nearest neighbour
 * scaling in the vertical direction.
 */
void LisaCanvas::UpdateVideoCorrected(void)
{
    int x, y;

    for(y = 0; y < 500; y++) {
        int i =  (y * 364 / 500) * 90;
        for(x = 0; x < 720 ; x+=8) {
            unsigned char c = lisaram[videolatchaddress + i];
            if (c != m_vram[i] || m_redraw_video_dc) {
                m_dirty_x_min = min(m_dirty_x_min, x);
                m_dirty_x_max = max(m_dirty_x_max, x + 7);
                m_dirty_y_min = min(m_dirty_y_min, y);
                m_dirty_y_max = max(m_dirty_y_max, y);
                m_video_dc->Blit(x, y, 8, 1, m_video_byte_dc,
                    0, c, wxCOPY, false);
            }
            i++;
        }
    }
    memcpy(m_vram, &lisaram[videolatchaddress], 32768);
}

/*
 * Redraw the video in 'enhanced' mode. This tries to choose intelligent
 * values for the pixels based on the three scanlines (top, middle, bottom):
 */
void LisaCanvas::UpdateVideoEnhanced(void)
{
    /*
     * This routine ends up drawing identically to (although much slower than)
     * UpdateVideoCorrected, but displays any 50% grey pattern that it detects
     * as an actual grey.
     * It detects patterns by looking at three scanlines: the top, middle (or
     * current line) and bottom. It looks at 2x3 pixel blocks at a time to
     * find the characteristic grey patterns.
     */
    const static bool graymap[4*4*4]={
        0,0,0,0,  0,0,1,0,  0,1,0,0,  0,0,0,0,
        0,0,0,0,  0,0,0,0,  1,1,0,1,  0,1,0,0,
        0,0,0,0,  1,0,1,1,  0,0,0,0,  0,0,1,0,
        0,0,0,0,  0,0,1,1,  0,1,0,1,  0,0,0,0
    };

    int x, y;
    for(y = 0; y < 500; y++) {
        int line  = y * 364 / 500;
        int ti = max(line - 1, 0  ) * 90;
        int mi =     line           * 90;
        int bi = min(line + 1, 363) * 90;

        for(x = 0; x < 720 ; x+=8) {
            unsigned char t = lisaram[videolatchaddress + ti];
            unsigned char m = lisaram[videolatchaddress + mi];
            unsigned char b = lisaram[videolatchaddress + bi];

            int t3 = (t & 0xc0) >> 6, m3 = (m & 0xc0) >> 6, b3 = (b & 0xc0) >> 6;
            int t2 = (t & 0x30) >> 4, m2 = (m & 0x30) >> 4, b2 = (b & 0x30) >> 4;
            int t1 = (t & 0x0c) >> 2, m1 = (m & 0x0c) >> 2, b1 = (b & 0x0c) >> 2;
            int t0 =  t & 0x03,       m0 =  m & 0x03,       b0 =  b & 0x03;

            if (t != m_vram[ti] || m != m_vram[mi] ||
                b != m_vram[bi] || m_redraw_video_dc) {

                m_dirty_x_min = min(m_dirty_x_min, x);
                m_dirty_x_max = max(m_dirty_x_max, x + 7);
                m_dirty_y_min = min(m_dirty_y_min, y);
                m_dirty_y_max = max(m_dirty_y_max, y);

                m_video_dc->Blit(x, y, 8, 1, m_video_byte_dc,
                    0, m, wxCOPY, false);

                if (graymap[(t0 << 4) | (m0 << 2) | b0]) m_video_dc->Blit(
                    x + 6, y, 2, 1, m_video_byte_dc, 0, 256, wxCOPY, false);
                if (graymap[(t1 << 4) | (m1 << 2) | b1]) m_video_dc->Blit(
                    x + 4, y, 2, 1, m_video_byte_dc, 0, 256, wxCOPY, false);
                if (graymap[(t2 << 4) | (m2 << 2) | b2]) m_video_dc->Blit(
                    x + 2, y, 2, 1, m_video_byte_dc, 0, 256, wxCOPY, false);
                if (graymap[(t3 << 4) | (m3 << 2) | b3]) m_video_dc->Blit(
                    x    , y, 2, 1, m_video_byte_dc, 0, 256, wxCOPY, false);

            }
            ti++;
            mi++;
            bi++;
       }
   }
   memcpy(m_vram, &lisaram[videolatchaddress], 32768);
}

/* Accessor -- returns true if the skin is visible, false if not */
bool LisaCanvas::IsSkinOn(void)
{
    return m_skin;
}

/*
 * Accessor -- turns on/off the skin, and updates the canvas (and its
 * parent window) if any changes occur.
 */
void LisaCanvas::SetSkinOn(bool skin)
{
    if (skin == m_skin) return;
    DoSetSkinOn(skin, true);
}

/*
 * Turn on/off skin. Recalculate virtual window sizes, set maximum parent
 * frame size, resize the parent frame if necessary (ie it needs to be shrunk)
 * and invalidate the contents of the window.
 */
void LisaCanvas::DoSetSkinOn(bool skin, bool update_parent)
{
    int cw, ch, pmaxw, pmaxh;

    m_skin = skin;
    if (m_skin) {
        cw = LISA_WIDTH;
        ch = LISA_HEIGHT;
    } else {
        cw = VIDEO_WIDTH;
        ch = VIDEO_HEIGHT;
    }

    /* Set the size we want this window to be */
    SetMaxSize(wxSize(cw, ch));
    SetVirtualSize(wxSize(cw, ch));
    SetScrollbars(1, 1, cw, ch);

    /*
     * Calculate the overhead required by the parent window, and set the
     * parent's maximum size based on this.
     */
    wxSize pws = GetParent()->GetSize();
    wxSize pcs = GetParent()->GetClientSize();

    pmaxw = cw + pws.GetWidth() - pcs.GetWidth();
    pmaxh = ch + pws.GetHeight() - pcs.GetHeight();

    GetParent()->SetMaxSize(wxSize(pmaxw, pmaxh));

    /*
     * By changing the parent window's max size, we might make its current
     * size larger than the max in some dimension. Force the window size to
     * honour its maximum. Somehow you'd expect that wx would do this for you
     * itself, but looks like it doesn't...
     */
    pmaxw = min(pmaxw, pws.GetWidth());
    pmaxh = min(pmaxh, pws.GetHeight());

    GetParent()->SetSize(pmaxw, pmaxh);

    /*
     * This handles the case when we toggle from skinned window to
     * unskinned, and the window size happens to be exactly the virtual size
     * of the unskinned one. Without calling this fixup, the scrollbars
     * stay in place even though they're not needed, because normally the
     * scrollbar fixup is only called when the window size changes, not
     * when the virtual size changes. As per the comment in OnSize, this
     * is an unfortunate hack :(
     */
    FixupScrollbars();

    /*
     * Get the parent UI to update (so the statusbar etc repaint properly),
     * and refresh our client area since we've just toggled between skin/none
     */
    if (update_parent) GetParent()->UpdateWindowUI();
    Refresh();
}

void LisaCanvas::OnSize(wxSizeEvent& WXUNUSED(event))
{
    /*
     * This is a hack, but unfortunately a necessary one. wxScrolledWindow
     * in wxWindows 2.8 (and presumably earlier) mishandles the situation
     * where you have a wxSW whose window size is smaller than its virtual
     * size (and so scrollbars are visible), which is then resized so
     * WS = VS. The problem is the scrollbars are visible, so client
     * size < VS, and so the scrollbars remain visible. It stays like this
     * until the window is made big enough so that
     * WS = (VS.GetWidth() + scrollbarwidth, VS.GetHeight() + scrollbarheight)
     * So we have to patch this one up by hand. <sigh>
     */
    FixupScrollbars();
}

/* Work around a bug in wxWindows 2.8 as described in OnSize(...) */
void LisaCanvas::FixupScrollbars()
{
    wxSize ws = GetSize();
    wxSize ms = GetMaxSize();

    if (ms.GetWidth() == ws.GetWidth()) {
        SetScrollbar(wxHORIZONTAL, 0, 0, 0);
    }
    if (ms.GetHeight() == ws.GetHeight()) {
        SetScrollbar(wxVERTICAL, 0, 0, 0);
    }
}

/*
 * Even though wxScrolledWindow is supposed to implement a nice canvas
 * abstraction for you, you have to do the mapping from virtual to
 * client coordinates when you're doing a RefreshRect().
 */
void LisaCanvas::RefreshVirtualRect(int x, int y, int width, int height)
{
    int cx, cy, cwidth, cheight;
    wxRect rect = wxRect(x, y, width, height);

    GetViewStart(&cx, &cy);
    GetClientSize(&cwidth, &cheight);

    /* Offset our rectangle to translate from virtual to client coordinates */
    rect.Offset(-cx, -cy);

    /*
     * If the refresh rect goes off the top/left of the client rect, crop
     * the rect to the top/left and decrease the width/height appropriately.
     */
    if (rect.x < 0) { rect.width  += rect.x; rect.x = 0; }
    if (rect.y < 0) { rect.height += rect.y; rect.y = 0; }

    /*
     * If the refresh rect goes off the bottom/right of the client rect,
     * crop it.
     */
    if (rect.x + rect.width > cwidth) rect.width = max(0, cwidth - rect.x);
    if (rect.y + rect.height > cheight) rect.height = max(0, cheight - rect.y);

    /* If we end up with a non-empty rectangle, refresh it! */
    if (rect.width > 0 && rect.height > 0) RefreshRect(rect);
}

/*
 * Take the specified rectange in virtual (ie canvas) coordinates, convert
 * it to client coords and clip it. If the resulting rectangle is non-empty
 * (ie the bitmap dst isn't the edge of the window), blit it.
 */
void LisaCanvas::BlitVirtualRect(wxDC* src, int x, int y, int width, int height)
{
    wxRect rect = wxRect(x, y, width, height);
    wxPaintDC dc(this);

    int cx, cy, cwidth, cheight; /* client rect in virtual coordinates */
    int sx, sy;                  /* Offset into bitmap */

    sx = 0; sy = 0;
    GetViewStart(&cx, &cy);
    GetClientSize(&cwidth, &cheight);

    /* Offset blit destination rect to translate virtual->client coords */
    rect.Offset(-cx, -cy);

    /*
     * If the blit destination rect goes off the left/top of the client,
     * crop the dest rect, and update offset into bitmap accordingly.
     */
    if (rect.x < 0) { sx -= rect.x; rect.width  += rect.x; rect.x = 0; }
    if (rect.y < 0) { sy -= rect.y; rect.height += rect.y; rect.y = 0; }

    /*
     * If the blit destination rect goes off the bottom/right of the client,
     * crop the dest rect.
     */
    if (rect.x + rect.width > cwidth) rect.width = max(0, cwidth - rect.x);
    if (rect.y + rect.height > cheight) rect.height = max(0, cheight - rect.y);

    if (rect.width > 0 && rect.height > 0) {
        dc.Blit(rect.x, rect.y, rect.width, rect.height, src, sx, sy, wxCOPY);
    }
}

void LisaCanvas::OnPaint(wxPaintEvent& WXUNUSED(event))
{
    wxRect video_rect = wxRect(VIDEO_X, VIDEO_Y, VIDEO_WIDTH, VIDEO_HEIGHT);
    if (m_skin) {
        /* Draw any skin that's necessary */
        BlitVirtualRect(m_lisa_dc, LISA_X, LISA_Y, LISA_WIDTH, LISA_HEIGHT);

        /* Draw any floppy animation that's necessary */
        wxMemoryDC *floppy_dc;
        switch (m_floppy) {
            case FLOPPY_ANIM0:   floppy_dc = m_floppy0_dc; break;
            case FLOPPY_ANIM1:   floppy_dc = m_floppy1_dc; break;
            case FLOPPY_ANIM2:   floppy_dc = m_floppy2_dc; break;
            case FLOPPY_PRESENT: floppy_dc = m_floppy3_dc; break;
            default:             floppy_dc = m_floppyN_dc;
        }
        BlitVirtualRect(floppy_dc, FLOPPY_X, FLOPPY_Y, FLOPPY_WIDTH, FLOPPY_HEIGHT);

        /* If the power's on (or we're shutting down), draw the power light */
        if (m_power || m_power_step != POWER_ANIM_NONE) {
            BlitVirtualRect(m_poweron_dc, POWER_X, POWER_Y, POWER_WIDTH, POWER_HEIGHT);
        }
    }

    /* If we're not powering up/down and the power is on, draw the video */
    if (m_power_step == POWER_ANIM_NONE && m_power) {
        BlitVirtualRect(m_video_dc, VIDEO_X, VIDEO_Y, VIDEO_WIDTH, VIDEO_HEIGHT);
        return;
    }

    /* If we're powering up/down, show the 'lightning flash' on the screen */
    if (m_power_step != POWER_ANIM_NONE) {
        wxPaintDC dc(this);
        DoPrepareDC(dc);

        int cx = VIDEO_X + VIDEO_WIDTH  / 2;
        int cy = VIDEO_Y + VIDEO_HEIGHT / 2;
        int w = m_power_step;
        int h = VIDEO_HEIGHT * m_power_step / POWER_ANIM_STEPS;

        /* Blacken the screen */
        dc.SetPen(*wxBLACK_PEN);
        dc.SetBrush(*wxBLACK_BRUSH);
        dc.DrawRectangle(VIDEO_X, VIDEO_Y, VIDEO_WIDTH, VIDEO_HEIGHT);

        /* Draw the central flash */
        dc.SetPen(*wxWHITE_PEN);
        dc.SetBrush(*wxWHITE_BRUSH);
        dc.DrawRectangle(cx - w / 2, cy - h / 2, w, h);
    }
}

/*
 * Handle all of MouseDown/MouseUp/MouseMove here because of the way
 * the COPS chip receives handles the real Lisa mouse
 */
void LisaCanvas::OnMouseEvent(wxMouseEvent& event)
{
    wxPoint pos = event.GetPosition();

    /* Take into account the fact that the window might be scrolled */
    int cx, cy;
    GetViewStart(&cx, &cy);
    pos.x += cx;
    pos.y += cy;

    /*
     * Change the cursor to a dot when inside the video area.
     * NB, *if* this has a significant overhead, it might be better to
     * make the video rectangle a sub-window of this one, and just set
     * the cursor for the sub-window. This would let the window system
     * do all the tracking for us, and would avoid the cost of lots of
     * unnecessary SetCursor() calls.
     */
    wxRect video_rect = wxRect(VIDEO_X, VIDEO_Y, VIDEO_WIDTH, VIDEO_HEIGHT);
    if (m_dot_cursor) {
        if (video_rect.Contains(pos)) {
            SetCursor(*m_dot_cursor);
        } else {
            SetCursor(wxNullCursor);
        }
    }

    /* If the emulation is running, let it know the mouse has been moved */
    if (m_power && video_rect.Contains(pos)) {
        static wxLongLong lastup;
        wxLongLong now = ::wxGetLocalTimeMillis();

        int up = event.LeftUp();

        int x, y;
        x = pos.x - VIDEO_X;
        if (m_video_mode == VIDEO_UNCORRECTED) {
            y = pos.y - VIDEO_Y - 68;
        } else {
            y = (pos.y - VIDEO_Y) * 364 / 500;
        }

        add_mouse_event(x, y, up?-1:event.LeftDown()?1:0);

        // double click hack  - fixme BUG BUG BUG - fixme
        if (up) {
            if (now-lastup < 500) {
                 add_mouse_event(x, y,  1);
                 add_mouse_event(x, y, -1);
                 add_mouse_event(x, y,  1);
                 add_mouse_event(x, y, -1);
            }
            lastup = now;
        }
        RunEmulationFor(EVENT_HANDLE_CYCLES);
    }
}

void LisaCanvas::OnKeyDown(wxKeyEvent& event)
{
    /* Let the emulation know about the keydown (in raw kbd mode) */
    if (m_raw_keyboard) {
        SendKeyEvent(event.GetKeyCode(), KEYDIR_DOWN);
    } else {
        event.Skip();
    }
}

void LisaCanvas::OnKeyUp(wxKeyEvent& event)
{
    /* Let the emulation know about the keyup (in raw kbd mode) */
    if (m_raw_keyboard) {
        SendKeyEvent(event.GetKeyCode(), KEYDIR_UP);
    } else {
        event.Skip();
    }
}

void LisaCanvas::OnChar(wxKeyEvent& event)
{
    if (m_raw_keyboard) {
        event.Skip();
        return;
    }

    /* Let the emulation know about a simulated keypress (in 'ASCII' mode) */
    int kc = event.GetKeyCode();

    /* Avoid inserting extra ASCII 1,2,4 when shift/alt/control are down. */
    if (kc == WXK_SHIFT || kc == WXK_ALT || kc == WXK_CONTROL) return;
    SendKeyEvent(kc, KEYDIR_ASCII);
}

void LisaCanvas::SendKeyEvent(int keycode, int keydir)
{
    if (!m_power || m_paused) return;

    int lisakey = 0;
    switch (keycode)
    {
        case 'a':                  lisakey = KEYCODE_A;          break;
        case 'b':                  lisakey = KEYCODE_B;          break;
        case 'c':                  lisakey = KEYCODE_C;          break;
        case 'd':                  lisakey = KEYCODE_D;          break;
        case 'e':                  lisakey = KEYCODE_E;          break;
        case 'f':                  lisakey = KEYCODE_F;          break;
        case 'g':                  lisakey = KEYCODE_G;          break;
        case 'h':                  lisakey = KEYCODE_H;          break;
        case 'i':                  lisakey = KEYCODE_I;          break;
        case 'j':                  lisakey = KEYCODE_J;          break;
        case 'k':                  lisakey = KEYCODE_K;          break;
        case 'l':                  lisakey = KEYCODE_L;          break;
        case 'm':                  lisakey = KEYCODE_M;          break;
        case 'n':                  lisakey = KEYCODE_N;          break;
        case 'o':                  lisakey = KEYCODE_O;          break;
        case 'p':                  lisakey = KEYCODE_P;          break;
        case 'q':                  lisakey = KEYCODE_Q;          break;
        case 'r':                  lisakey = KEYCODE_R;          break;
        case 's':                  lisakey = KEYCODE_S;          break;
        case 't':                  lisakey = KEYCODE_T;          break;
        case 'u':                  lisakey = KEYCODE_U;          break;
        case 'v':                  lisakey = KEYCODE_V;          break;
        case 'w':                  lisakey = KEYCODE_W;          break;
        case 'x':                  lisakey = KEYCODE_X;          break;
        case 'y':                  lisakey = KEYCODE_Y;          break;
        case 'z':                  lisakey = KEYCODE_Z;          break;
        case 'A' :                 lisakey = KEYCODE_A;          break;
        case 'B' :                 lisakey = KEYCODE_B;          break;
        case 'C' :                 lisakey = KEYCODE_C;          break;
        case 'D' :                 lisakey = KEYCODE_D;          break;
        case 'E' :                 lisakey = KEYCODE_E;          break;
        case 'F' :                 lisakey = KEYCODE_F;          break;
        case 'G' :                 lisakey = KEYCODE_G;          break;
        case 'H' :                 lisakey = KEYCODE_H;          break;
        case 'I' :                 lisakey = KEYCODE_I;          break;
        case 'J' :                 lisakey = KEYCODE_J;          break;
        case 'K' :                 lisakey = KEYCODE_K;          break;
        case 'L' :                 lisakey = KEYCODE_L;          break;
        case 'M' :                 lisakey = KEYCODE_M;          break;
        case 'N' :                 lisakey = KEYCODE_N;          break;
        case 'O' :                 lisakey = KEYCODE_O;          break;
        case 'P' :                 lisakey = KEYCODE_P;          break;
        case 'Q' :                 lisakey = KEYCODE_Q;          break;
        case 'R' :                 lisakey = KEYCODE_R;          break;
        case 'S' :                 lisakey = KEYCODE_S;          break;
        case 'T' :                 lisakey = KEYCODE_T;          break;
        case 'U' :                 lisakey = KEYCODE_U;          break;
        case 'V' :                 lisakey = KEYCODE_V;          break;
        case 'W' :                 lisakey = KEYCODE_W;          break;
        case 'X' :                 lisakey = KEYCODE_X;          break;
        case 'Y' :                 lisakey = KEYCODE_Y;          break;
        case 'Z' :                 lisakey = KEYCODE_Z;          break;
        case '0' :                 lisakey = KEYCODE_0;          break;
        case '1' :                 lisakey = KEYCODE_1;          break;
        case '2' :                 lisakey = KEYCODE_2;          break;
        case '3' :                 lisakey = KEYCODE_3;          break;
        case '4' :                 lisakey = KEYCODE_4;          break;
        case '5' :                 lisakey = KEYCODE_5;          break;
        case '6' :                 lisakey = KEYCODE_6;          break;
        case '7' :                 lisakey = KEYCODE_7;          break;
        case '8' :                 lisakey = KEYCODE_8;          break;
        case '9' :                 lisakey = KEYCODE_9;          break;
        case WXK_NUMPAD0:          lisakey = KEYCODE_0NUM;       break;
        case WXK_NUMPAD1:          lisakey = KEYCODE_1NUM;       break;
        case WXK_NUMPAD2:          lisakey = KEYCODE_2NUM;       break;
        case WXK_NUMPAD3:          lisakey = KEYCODE_3NUM;       break;
        case WXK_NUMPAD4:          lisakey = KEYCODE_4NUM;       break;
        case WXK_NUMPAD5:          lisakey = KEYCODE_5NUM;       break;
        case WXK_NUMPAD6:          lisakey = KEYCODE_6NUM;       break;
        case WXK_NUMPAD7:          lisakey = KEYCODE_7NUM;       break;
        case WXK_NUMPAD8:          lisakey = KEYCODE_8NUM;       break;
        case WXK_NUMPAD9:          lisakey = KEYCODE_9NUM;       break;
        case WXK_BACK:             lisakey = KEYCODE_BACKSPACE;  break;
        case WXK_TAB:              lisakey = KEYCODE_TAB;        break;
        case WXK_RETURN:           lisakey = KEYCODE_RETURN;     break;
        case WXK_ESCAPE:           lisakey = KEYCODE_CLEAR;      break;
        case WXK_SPACE:            lisakey = KEYCODE_SPACE;      break;
        case WXK_DELETE:           lisakey = KEYCODE_BACKSPACE;  break;
        case        '~':
        case        '`':           lisakey = KEYCODE_TILDE;      break;

        case        '_':
        case        '-':           lisakey = KEYCODE_MINUS;      break;

        case        '+':
        case        '=':           lisakey = KEYCODE_PLUS;       break;

        case        '{':
        case        '[':           lisakey = KEYCODE_OBRAK;      break;

        case        '}':
        case        ']':           lisakey = KEYCODE_CBRAK;      break;


        case        '|':
        case        '\\':          lisakey = KEYCODE_BSLASH;     break;

        case        ':':
        case        ';':           lisakey = KEYCODE_COLON;      break;

        case        '"':
        case        '\'':          lisakey = KEYCODE_QUOTE;      break;

        case        '<':
        case        ',':           lisakey = KEYCODE_COMMA;      break;

        case        '>':
        case        '.':           lisakey = KEYCODE_DOT;        break;

        case        '?':
        case        '/':           lisakey = KEYCODE_FSLASHQ;    break;

        case WXK_NUMPAD_LEFT:      lisakey = KEYCODE_CURSORL;    break;
        case WXK_NUMPAD_UP:        lisakey = KEYCODE_CURSORU;    break;
        case WXK_NUMPAD_RIGHT:     lisakey = KEYCODE_CURSORR;    break;
        case WXK_NUMPAD_DOWN :     lisakey = KEYCODE_CURSORD;    break;


        case WXK_NUMPAD_INSERT:    lisakey = KEYCODE_LENTER;     break;
        case WXK_NUMPAD_DELETE:    lisakey = KEYCODE_BACKSPACE;  break;

        case WXK_NUMPAD_MULTIPLY:  lisakey = KEYCODE_STAR_NUM;   break;
        case WXK_NUMPAD_ADD:       lisakey = KEYCODE_PLUS_NUM;   break;
        case WXK_NUMPAD_SEPARATOR:
        case WXK_NUMPAD_SUBTRACT:  lisakey = KEYCODE_MINUS_NUM;  break;
        case WXK_NUMPAD_DECIMAL:   lisakey = KEYCODE_DOTNUM;     break;
        case WXK_NUMPAD_DIVIDE:    lisakey = KEYCODE_FSLASH;     break;

        case WXK_NUMPAD_ENTER:     lisakey = KEYCODE_ENTERNUM;   break;
        case WXK_NUMLOCK:          lisakey = KEYCODE_CLEAR;      break;
    }

    if (keydir == KEYDIR_ASCII) {
        keystroke_cops(keycode);
    } else {
        if (keydir == KEYDIR_DOWN) lisakey |= KEY_DOWN;
        if (lisakey & ~KEY_DOWN) send_cops_keycode(lisakey);
    }

    /*
     * Run the emulation for a short amount of time so it can receive
     * and handle the event in a timely fashion.
     */
    RunEmulationFor(EVENT_HANDLE_CYCLES);
}

/*
 * Ripped from lisaem_wx.cpp. Needs to be cleaned up a bit: there *needs*
 * to be a seperation of concerns: the emulation itself should be
 * reponsible for setting up/tearing down itself. The GUI can poke it
 * with events (eg "there's a profile connected", "we use this ROM",
 * "power on now please", "reset everything"), but the GUI shouldn't need
 * to know how to do it. All the GUI does is watch the emulation and
 * represent it on screen, and take user input and channel it into the emu.
 * This will pay back bigtime when we have a couple of frontends: eg the
 * SDL console driven version, and a proper Cocoa frontend.
 */


uint8 evenparity(uint8 data)            // from http://graphics.stanford.edu/~seander/bithacks.html#ParityParallel
{
 uint32 v=data;
//v ^= v >> 16;       // not needed since we're working on a byte - only here for completeness, but commented out
//v ^= v >> 8;        // ditto
  v ^= v >> 4;
  v &= 0xf;
  return !(  (0x6996 >> v) & 1  );
}

/* Connects a Dual Parallel port card to the specified slot (numbered 0-2). */
void connect_2x_parallel_to_slot(int slot)
{
    int low, high, v;
    switch (slot)
    {
        case 0:
            get_exs0_pending_irq = get_exs0_pending_irq_2xpar;
            low = Ox0000_slot1; high = Ox2000_slot1; v = 3;
            break;
        case 1:
            get_exs1_pending_irq = get_exs1_pending_irq_2xpar;
            low = Ox4000_slot2; high = Ox6000_slot2; v = 5;
            break;
        case 2:
            get_exs2_pending_irq = get_exs2_pending_irq_2xpar;
            low = Ox8000_slot3; high = Oxa000_slot3; v = 7;
            break;
        default:
           //ALERT_LOG(0, "Unknown slot number %d, should be 0-2!", slot);
           return;
    }

    mem68k_memptr    [low] = lisa_mptr_2x_parallel_l;
    mem68k_fetch_byte[low] = lisa_rb_2x_parallel_l;
    mem68k_fetch_word[low] = lisa_rw_2x_parallel_l;
    mem68k_fetch_long[low] = lisa_rl_2x_parallel_l;
    mem68k_store_byte[low] = lisa_wb_2x_parallel_l;
    mem68k_store_word[low] = lisa_ww_2x_parallel_l;
    mem68k_store_long[low] = lisa_wl_2x_parallel_l;

    mem68k_memptr    [high] = lisa_mptr_2x_parallel_h;
    mem68k_fetch_byte[high] = lisa_rb_2x_parallel_h;
    mem68k_fetch_word[high] = lisa_rw_2x_parallel_h;
    mem68k_fetch_long[high] = lisa_rl_2x_parallel_h;
    mem68k_store_byte[high] = lisa_wb_2x_parallel_h;
    mem68k_store_word[high] = lisa_ww_2x_parallel_h;
    mem68k_store_long[high] = lisa_wl_2x_parallel_h;

    reset_via(v);
    reset_via(v+1);
    via[v].active = 1;
    via[v+1].active = 1;
}

/* Connects a printer/profile to the specified VIA */
void connect_device_to_via(int v, wxString device, wxString *file)
{
    if (device.IsSameAs("ADMP", false))
    {
        via[v].ADMP = v;
        ImageWriter_LisaEm_Init(v);                 // &ADMP,pcl,ps,xlib
        //ALERT_LOG(0, "Attached ADMP to VIA#%d", v);
        return;
    }

    if (device.IsSameAs("PROFILE", false))
    {
        if (file->Len()==0)
        {
         static const wxString def[]={  _T(""),_T(""),
                            _T("lisaem-profile.dc42"),
                            _T("lisaem-s1h-profile.dc42"),
                            _T("lisaem-s1l-profile.dc42"),
                            _T("lisaem-s2h-profile.dc42"),
                            _T("lisaem-s2l-profile.dc42"),
                            _T("lisaem-s3h-profile.dc42"),
                            _T("lisaem-s3l-profile.dc42") };

         if (v>1 && v<9) *file =def[v];
         else return;
        }
        DEBUG_LOG(0, "Attempting to attach VIA#%d to profile %s", v, file->c_str());

        via[v].ProFile = (ProFileType *)malloc(sizeof(ProFileType));
        int i = profile_mount((char *)file->c_str(), via[v].ProFile);
        if (i) {
            free(via[v].ProFile);
            via[v].ProFile = NULL;
            DEBUG_LOG(0, "Couldn't get profile because: %d",i);
        } else {
            ProfileReset(via[v].ProFile);
        }
    }
}

/*
 * Connect some (virtual) device to one of the serial ports. 0 is Serial A,
 * 1 is Serial B. A number of the parameters passed in may be modified by
 * this code.
 */
void connect_device_to_serial(int port, FILE **scc_port_F, uint8 *serial,
    wxString *setting, wxString *param, int *scc_telnet_port)
{
    if (port !=0 && port != 1) {
        DEBUG_LOG(0, "Warning Serial port is not A/B: %d", port);
        return;
    }

    if (setting->IsSameAs("LOOPBACK",false)) {
        *scc_port_F = NULL;
        *serial = SCC_LOOPBACKPLUG;
        return;
    }

    if (setting->IsSameAs("NOTHING", false) || setting->IsSameAs("NULL", false) || setting->Len()==0) {
        *scc_port_F = NULL;
        *serial = SCC_NOTHING;
        *setting = "NOTHING";  // fill in for writing into ini file
        return;
    }

    if (setting->IsSameAs("FILE", false))
    {
        *scc_port_F = fopen(*param, "r+b");
        if (!*scc_port_F) {
            wxString err = wxString();
            err.Printf("Could not map Serial port %c to file %s", (port==0?'A':'B'), param->c_str());
            wxMessageBox(err, _T("Serial port configuration"),  wxICON_INFORMATION | wxOK);
            *scc_port_F = NULL;
            *serial = SCC_NOTHING;
        } else {
            *serial = SCC_FILE;
        }
        return;
    }

    if (setting->IsSameAs("PIPE", false)) {
        *scc_port_F = popen(*param, "r+b");
        if (!*scc_port_F) {
            wxString err = wxString();
            err.Printf("Could not map Serial port %c to pipe %s", (port==0?'A':'B'), param->c_str());
            wxMessageBox(err, _T("Serial port configuration"),  wxICON_INFORMATION | wxOK);
            *scc_port_F = NULL;
            *serial = SCC_NOTHING;
        } else {
            *serial = SCC_PIPE;
        }
        return;
    }

#ifndef __MSVCRT__
    if (setting->IsSameAs("TELNETD", false)) {
        unsigned long x;
        *scc_port_F = NULL;
        *serial = SCC_TELNETD;

        *scc_telnet_port = (param->ToULong(&x, 10)==false) ? 9300+port : x;
        ALERT_LOG(0,"Connecting TELNETD 127.0.0.1:%d on serial port %d",*scc_telnet_port,port);

        init_telnet_serial_port(!port);     // serial a=port 1, serial b=port 0 (reversed from config!)
        ALERT_LOG(0,"return from init_telnet_serial_port %d",port);

        return;
    }
#endif

    // Not sure if we'll keep the image writer a separate program or if it
    // will be built in in the future.  This is if it's built in:
    if (setting->IsSameAs("IMAGEWRITER", false)) {
        *scc_port_F = NULL;
        *serial = SCC_IMAGEWRITER_PS;
        if (port) scc_b_IW=1; else scc_a_IW=0;
        ImageWriter_LisaEm_Init(port);
        return;
    }

    DEBUG_LOG(0, "Warning: unrecognised Serial %c setting '%s'", port==0?'A':'B', setting->c_str());
}

 int initialize_all_subsystems(LisaConfig *lisaconfig)
 {
   /*
   In the beginning there was data.  The data was without form and null,
   and darkness was upon the face of the console; and the Spirit of IBM
   was moving over the face of the market.  And DEC said, "Let there be
   registers"; and there were registers.  And DEC saw that they carried;
   and DEC separated the data from the instructions.  DEC called the data
   Stack, and the instructions they called Code.  And there was evening
   and there was morning, one interrupt.         -- Rico Tudor            */

       if (me->IsPowerOn()) {//ALERT_LOG(0,"Already running!");
        return 0;}

#ifdef __WXOSX__
#ifdef wxMAC_USE_CORE_GRAPHICS

       if (wxMAC_USE_CORE_GRAPHICS!=0)
          {
            messagebox("wxWidgets was compiled with wxMAC_USE_CORE_GRAPHICS=1 which causes serious disply issues!",
                       "Miscompiled wxWidgets!");
          }
#endif
#endif


       buglog=stderr;

       setstatusbar("initializing all subsystems...");

       // initialize parity array (it's 2x faster to use an array than to call the fn)
       setstatusbar("Initializing Parity calculation cache array...");
       {uint16 i;
       for (i=0; i<256; i++) eparity[i]=evenparity((uint8) i);
       }

       segment1=0; segment2=0; start=1;

       // needs to be above read_config_files
       scc_a_port=NULL;
       scc_b_port=NULL;
       scc_a_IW=-1;
       scc_b_IW=-1;
       serial_a=0;
       serial_b=0;

       init_floppy(lisaconfig->iorom);

       bitdepth=8;                             // have to get this from the X Server side...


       DEBUG_LOG(0,"serial number");
       serialnum[0]=0x00;                      // "real" ones will be loaded from the settings file
       serialnum[1]=0x00;
       serialnum[2]=0x00;
       serialnum[3]=0x00;
       serialnum[4]=0x00;
       serialnum[5]=0x00;
       serialnum[6]=0x00;
       serialnum[7]=0x00;
       serialnumshiftcount=0; serialnumshift=0;


       // Is there no explicit way to init the COPS?
       DEBUG_LOG(0,"copsqueue");
       copsqueuelen = 0;
       memset(copsqueue,0,MAXCOPSQUEUE);
       lisa_clock.year = 0;         // flag to get real time from host OS on first Lisa timer request.


       setstatusbar("Initializing irq handlers");
       init_IRQ();


       setstatusbar("Initializing vias");
       init_vias();


       setstatusbar("Initializing profile hd's");
       init_Profiles();


       setstatusbar("Initializing Lisa RAM");
       TWOMEGMLIM=0x001fffff;

      // Simulate physical Lisa memory boards
      switch(lisaconfig->mymaxlisaram)
      {
       case 512  : maxlisaram=0x100000;  minlisaram=0x080000;  break;     // single 512KB board in slot 1
       case 1024 : maxlisaram=0x180000;  minlisaram=0x080000;  break;     // two 512KB boards in slot1, slot 2
       case 1536 : maxlisaram=0x200000;  minlisaram=0x080000;  break;     // 512KB board in slot1, 1024KB board in slot 2
       default:    maxlisaram=0x200000;  minlisaram=0x000000;  break;     // two 1024KB boards in slot 1,2
      }

       // maxlisaram! New!  And! Improved! Now available in bytes at a register Store Near You!  OpCodes are Standing by!
       // Act now!  don't delay!  Limited Time Offer!  JSR Now!  Restrictions Apply!  Before engaging in any strenous programming
       // activity, you should always consult your MMU today!  Not 0xFD1C insured!


      TWOMEGMLIM=maxlisaram-1;
   videolatchaddress=maxlisaram-0x10000;

   videolatch=(maxlisaram>>15)-1; // last page of ram is by default what the video latch gets set to.
   lastvideolatch=videolatch;  lastvideolatchaddress=videolatchaddress;

   if (lisaram) free(lisaram);          // remove old junk if it exists

   lisaram=(uint8 *)malloc(maxlisaram+512);     // added 512 bytes at the end to avoid overflows.
   memset(lisaram,0xff,maxlisaram+128);

   if (lisaconfig->kbid) set_keyboard_id(lisaconfig->kbid);  else set_keyboard_id(-1);

    setstatusbar("Initializing Lisa Serial Number");
    // Parse sn and correct if needed  ////////////////////////////////////////////////////////////////////////////////////////
	{


        //char mybuffer[5];
        int i,j; char c;
        char *s=(char *)(lisaconfig->myserial.c_str());
        for (i=0; (c=s[i]); i++)
            if ( !ishex(c) )
			{
                //ALERT_LOG(1,"Warning, invalid serial number: (%s) reset to default.\n",s );
                lisaconfig->myserial.Printf("%s",LISA_CONFIG_DEFAULTSERIAL); break;
			}

        if (i<31) DEBUG_LOG(1,"Warning: serial # less than 32 bytes (%d)!\n",i);

        for (i=0,j=0; i<32; i+=2,j++)
                serialnum240[j]=(gethex(s[i])<<4)|gethex(s[i|1]);

        vidfixromchk(serialnum240);



        lisaconfig->myserial.Printf("%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
                        serialnum240[0],                 serialnum240[1],
                        serialnum240[2],                 serialnum240[3],
                        serialnum240[4],                 serialnum240[5],
                        serialnum240[6],                 serialnum240[7],
                        serialnum240[8],                 serialnum240[9],
                        serialnum240[10],                serialnum240[11],
                        serialnum240[12],                serialnum240[13],
                        serialnum240[14],                serialnum240[15]);

        DEBUG_LOG(0,"Serial # used: %s", lisaconfig->myserial.c_str());


    }//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    setstatusbar("Initializing Serial Ports");

    // If either serial port is set as loopback, both must be.
    if (lisaconfig->serial2_setting.IsSameAs("LOOPBACK", false) ||
        lisaconfig->serial1_setting.IsSameAs("LOOPBACK", false)) {
        lisaconfig->serial1_setting = "LOOPBACK";
        lisaconfig->serial2_setting = "LOOPBACK";
    }


    connect_device_to_serial(0, &scc_a_port_F, &serial_a,
        &lisaconfig->serial1_setting, &lisaconfig->serial1_param,
        &scc_a_telnet_port);


    connect_device_to_serial(1, &scc_b_port_F, &serial_b,
        &lisaconfig->serial2_setting, &lisaconfig->serial2_param,
        &scc_b_telnet_port);

    DEBUG_LOG(0,"Initializing SCC Z8530");
    initialize_scc();

    if (!lisaram) {
        wxMessageBox(_T("Could not allocate memory for the Lisa to use."),
            _T("Out of Memory!"), wxICON_INFORMATION | wxOK);
        return 23;
    }

  DEBUG_LOG(0,"setting motherboard latches");
  softmemerror=0; harderror=0; videoirq=0; bustimeout=0; videobit=0;

  DEBUG_LOG(0,"Checking host CPU bit order sanity.");
  reg68k_sanity_check_bitorder();

  setstatusbar("Initializing Generator CPU functions.");
  cpu68k_init();

  setstatusbar("Initializing MMU");
  init_lisa_mmu();

  setstatusbar("Initializing Generator CPU IPC Allocator");
  init_ipct_allocator();

  setstatusbar("Initializing Lisa Boot ROM");

  if (!lisaconfig->rompath.Len() )
  {
    wxMessageBox(_T(
   "Please configure the Lisa before starting it. Specifially, it needs a ROM as well as a Profile definition."
                   ), _T("Configuration Needed!"), wxICON_INFORMATION | wxOK);

          if (!me->m_lisa_configframe)    me->m_lisa_configframe=new LisaConfigFrame( wxT("Preferences"), lisaconfig);
          #if defined(__WXMOTIF__)
          int x, y;

          me->m_lisa_configframe->GetSize(&x, &y);
          x+=WINXDIFF; y+=WINYDIFF;
          me->m_lisa_configframe->SetSize(wxDefaultCoord, wxDefaultCoord,x,y);
          #endif

          me->m_lisa_configframe->Show();

          return -2;
  }


  if (lisaconfig->rompath.Len())
  {
      DEBUG_LOG(0,"Loading Lisa ROM");
      if      (read_dtc_rom((char *)(lisaconfig->rompath.c_str()),   lisarom)==0)
               {
                if (checkromchksum())
                   {
                    if (!yesnomessagebox(wxT("BOOT ROM checksum doesn't match, this may crash the emulator.  Continue anyway?"),
                         wxT("ROM Checksum mismatch"))  ) return -2;
                   }
                 DEBUG_LOG(0,"Load of DTC assembled ROM successful");
                 fixromchk();
               }
      else if (read_split_rom((char *)(lisaconfig->rompath.c_str()), lisarom)==0)
               {
                if (checkromchksum())
                   {
                    if (!yesnomessagebox(wxT("BOOT ROM checksum doesn't match, this may crash the emulator.  Continue anyway?"),
                         wxT("ROM Checksum mismatch"))  ) return -2;
                   }

                  DEBUG_LOG(0,"Load of split ROM");
                  fixromchk();
               }
      else if (read_rom((char *)(lisaconfig->rompath.c_str()),       lisarom)==0)
               {
                if (checkromchksum())
                   {
                    if (!yesnomessagebox(wxT("BOOT ROM checksum doesn't match, this may crash the emulator.  Continue anyway?"),
                         wxT("ROM Checksum mismatch"))  ) return -2;
                   }

                  DEBUG_LOG(0,"Load of normal ROM successful");
                  fixromchk();
               }
      else
      {

          wxMessageBox(_T("Could not open/find a suitable boot ROM"), _T("ROM Needed!!!"), wxICON_INFORMATION | wxOK);
          // Homer: No TV and no beer make Homer something something");
          // Marge: Go crazy?");
          // Homer: Don't mind if I do!");
          return -2;
      }
      //lisaconfig->save();
  }
  else
  {
          wxMessageBox(_T("Could not open/find a suitable boot ROM or path is empty."), _T("ROM Needed"), wxICON_INFORMATION | wxOK);
          return -3;
  }

  setstatusbar("Initializing Lisa Display");
  if (has_xl_screenmod())
  {

   // This warning flag isn't really a configuration setting as such, but leave it here for the moment
   if (!lisaconfig->saw_3a_warning)
   wxMessageBox(_T("This is the XL Screen Modification ROM! You will only be able to run MacWorks."),
                    _T("3A ROM!"),   wxICON_INFORMATION | wxOK);


   lisaconfig->saw_3a_warning=1;
   lisa_vid_size_x=608;
   lisa_vid_size_y=431;
   effective_lisa_vid_size_x=608;
   effective_lisa_vid_size_y=431;

   screen_origin_x=140+56;
   screen_origin_y=130+34;

   lisa_vid_size_x=608;
   lisa_vid_size_y=431;

   lisa_vid_size_xbytes=76;
   has_lisa_xl_screenmod=1;

   setvideomode(0x3a);
  }
  else
  {
   lisa_vid_size_x=720;
   lisa_vid_size_y=364;
   effective_lisa_vid_size_x=720;
   effective_lisa_vid_size_y=500;

   screen_origin_x=140;
   screen_origin_y=130;

   lisa_vid_size_x=720;
   lisa_vid_size_y=364;


   lisa_vid_size_xbytes=90;
   has_lisa_xl_screenmod=0;

   setvideomode(lisa_ui_video_mode);


  }

  ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////

  setstatusbar("Initializing Parallel Port");
  // Handle Parallel Ports

  // Connect profile/printer to builtin parallel port
  connect_device_to_via(2, lisaconfig->parallel, &lisaconfig->parallelp);

  // ----------------------------------------------------------------------------------

  // Dual Parallel Port Expansion Cards

  setstatusbar("Initializing Expansion Slots");
  memset(dualparallelrom,0xff,2048);

  // First, we have to load the card ROM, without it, it won't work.
   if (lisaconfig->dualrom.Len())
   {
    if (read_parallel_card_rom((char *)(lisaconfig->dualrom.c_str()))==0)
       {
        // for now only Dual Parallel Port cards are supported.

          if (lisaconfig->slot1.IsSameAs("dualparallel",false) )
            {
                    connect_2x_parallel_to_slot(0);
                    connect_device_to_via(3, lisaconfig->s1h, &lisaconfig->s1hp);
                    connect_device_to_via(4, lisaconfig->s1l, &lisaconfig->s1lp);
            }

          if (lisaconfig->slot2.IsSameAs("dualparallel",false) )
            {
                    connect_2x_parallel_to_slot(1);
                    connect_device_to_via(5, lisaconfig->s2h, &lisaconfig->s2hp);
                    connect_device_to_via(6, lisaconfig->s2l, &lisaconfig->s2lp);
            }


          if (lisaconfig->slot3.IsSameAs("dualparallel",false))
            {
                    connect_2x_parallel_to_slot(2);
                    connect_device_to_via(7, lisaconfig->s3h, &lisaconfig->s3hp);
                    connect_device_to_via(8, lisaconfig->s3l, &lisaconfig->s3lp);
            }


       }
    else
       {
        wxMessageBox(_T("Could not open/find a suitable Parallel Card ROM. Expansion slots disabled."),
                     _T("Parallel Card ROM Needed"),
                        wxICON_INFORMATION | wxOK);
       }
   }

    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////



    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    DEBUG_LOG(0,"initializing video");
    disable_vidram(); videoramdirty=32768;

    //DEBUG_LOG(0,"Calling Redraw Pixels");
    //LisaRedrawPixels();


    setstatusbar("Initializing CPU Registers");
    cpu68k_reset();

    DEBUG_LOG(0,"done initializing...");

    debug_off();

    setstatusbar("Executing Lisa Boot ROM");

    refresh_rate_used=refresh_rate;       // powering (back) on, so use the user selected refresh rate.

    my_lisaframe->reset_throttle_clock();
    return 0;
  } ///////////////////// end of load.///////////////////




/*
 * This is here for the C code in the emu to talk to the canvas. For the
 * moment we need it to keep compatibility with the lisaem_wx.cpp frontend.
 * It really isn't very nice though; I'm sure there are better ways of doing
 * most, if not all, of it: callbacks, the canvas itself watching for
 * events once every iteration etc.
 * As DNA put it:
 * For a while they flew on, motionless against the starry sweep of the
 * Galaxy, itself motionless against the infinite sweep of the Universe.
 * And then they turned around. "It'll have to go," the men of Krikkit said.
 */
void invalidate_configframe(void)
{
    printf("invalidate_configframe()\n");
}

extern "C" void eject_floppy_animation(void)
{
    floppy_just_ejected = true;
}

extern "C" void floppy_motor_sounds(int track)
{
    printf("floppy_motor_sounds(%d)\n", track);
}

extern "C" void messagebox(char *s, char *t)
{
    printf("messagebox(\"%s\", \"%s\")\n", s, t);
}

extern "C" void lisa_powered_off(void)
{
    me->SetPowerOn(false);
}

extern "C" void lisa_rebooted(void)
{
    me->SetPowerOn(false);
    me->SetPowerOn(true);
}

extern "C" void save_pram(void)
{
    printf("save_pram()\n");
}

extern "C" void contrastchange(void)
{
    contrast_changed = true;
    // printf("contrastchange() (contrast: 0x%02x)\n", contrast);
}



 // just to be a bastard!  why? because split roms are lame!
 extern "C" void rename_rompath(char *rompath)
 {
   if (!my_lisaconfig)  return;

   my_lisaconfig->rompath=rompath;
   my_lisaconfig->Save(pConfig, floppy_ram);

   if (my_LisaConfigFrame) my_LisaConfigFrame->m_rompath->SetValue(my_lisaconfig->rompath);
   DEBUG_LOG(0,"saved %s as default\n",rompath);
 }



extern "C" void LisaScreenRefresh(void)
{
    printf("LisaScreenRefresh()\n");
}


extern "C" void sound_off(void)
{

    if (cpu68k_clocks-my_lisaframe->lastclk<50000) return;  // prevent sound from shutting down immediately
    wxSound::Stop();
    //FILE *f; f=fopen("z:\\sound.log","a+"); fprintf(f,"stopped sound at %016llx\n",cpu68k_clocks); fclose(f);
}



extern "C" void sound_play(uint16 t2)
{


    int samples=22050*2;                // a second


    int data_size=0;
    int cycles, halfcycles;
    unsigned char vhigh, vlow; //, l;


    if (t2>0xaf) return;                                  // out of range
    if (my_lisaframe->lastt2==t2 && (cpu68k_clocks-my_lisaframe->lastclk < 50000)) return;  // duplicate call if timing <1/100th of a second

    my_lisaframe->lastt2=t2;
    my_lisaframe->lastclk=cpu68k_clocks;


    static const unsigned char header[45]=
    {
        // 0     1    2    3    4    5    6    7    8    9    a    b    c    d    e    f
/*00*/  0x52,0x49,0x46,0x46,   0,   0,0x00,0x00,0x57,0x41,0x56,0x45,0x66,0x6D,0x74,0x20,  // RIFFF-..WAVEfmt
/*10*/  0x10,0x00,0x00,0x00,   1,   0,0x01,0x00,0x22,0x56,0x00,0x00,0x22,0x56,0x00,0x00,  //  ........"V.."V..
/*20*/  0x01,0x00,0x08,0x00,0x64,0x61,0x74,0x61,0x22,0x2D,0x00,0x00//0x80,0x80,0x80,0x80  //  ....data"-......
    };           //        d    a    t    a [LSB____________MSB]

    static unsigned char data[22050*5+45+1024];
    unsigned char *dataptr;
    //data=(unsigned char *)malloc(45+samples+1024);  // allocate a buffer, make it 22Kbps
    memcpy(data,header,45);        // copy the header over it.


    data[0x16]=1;      // 1=1 channel = mono, 2= 2 channels = stereo
    data[0x17]=0;


    data[0x18]=0x22;   // bit rate LSB.   0x5622=22050Hz   22Khz
    data[0x19]=0x56;   //                 0x2D22=11554Hz   11Khz
    data[0x1a]=0x00;   //                 0xAC44=44100Hz   44Khz
    data[0x1b]=0x00;   // bit rate MSB


    data[0x1c]=0x22;   // bytes per second
    data[0x1d]=0x56;   // should match bit rate for 8 bit samples
    data[0x1e]=0x00;
    data[0x1f]=0x00;

    data[0x20]=1;      // Bytes Per Sample:
    data[0x21]=0;      // 1=8 bit Mono, 2=8 bit Stereo or 16 bit Mono, 4=16 bit Stereo

    data[0x22]=8;      // 8, or 16  bits/sample
    data[0x23]=0;

    dataptr=&data[0x2c]; data_size=0;

    volume=(volume & 0x0f);

    vlow =128-(volume<<3);
    vhigh=128+(volume<<3);

    cycles=t2;
    if (!(my_lisaconfig->iorom & 0x20)) cycles=cycles*8/10;       // cycles *=0.8
    cycles=(cycles>>1)+(cycles>>2);                // cycles *=0.75; (1/2 + 1/4th)
    halfcycles=cycles>>1;                          // cycles /=2;

    int i=0;
    if (t2>0x90) samples=cycles;                   // prevent click from turning into beep
    while (samples>=cycles)
    {
        i++;
     memset(dataptr,vhigh,halfcycles); dataptr+=halfcycles;
     memset(dataptr,vlow, halfcycles); dataptr+=halfcycles;
     samples-=cycles; data_size+=cycles;
    }

    data[0x28]=((data_size)    ) & 0xff;
    data[0x29]=((data_size)>>8 ) & 0xff;
    data[0x2a]=((data_size)>>16) & 0xff;
    data[0x2b]=((data_size)>>24) & 0xff;

    data[0x04]=((data_size+36)    ) & 0xff;
    data[0x05]=((data_size+36)>>8 ) & 0xff;
    data[0x06]=((data_size+36)>>16) & 0xff;
    data[0x07]=((data_size+36)>>24) & 0xff;


    dataptr=&data[0];

    if (my_lisa_sound!=NULL) delete my_lisa_sound;
    my_lisa_sound=new wxSound;  //my_lisa_sound->Create(dataptr,data_size);

    // this should work, but it doesn't on all platforms
    //my_lisa_sound->Create(data_size,dataptr);
    //my_lisa_sound->Play(wxSOUND_ASYNC);

    // so instead, use the super lame ass way to play a sound
    FILE *f;
    f=fopen("tmpsnd.wav","wb");
    if (f) {
             fwrite(dataptr,data_size+45,1,f); fclose(f);
             my_lisa_sound->Create(_T("tmpsnd.wav"),false);
             // we do not check for sound_effects_on here as this is the Lisa beeping.
             my_lisa_sound->Play(wxSOUND_ASYNC);
             unlink("tmpsnd.wav");
           }
    errno=0;  // avoid interfearance with other stuff like libdc42.
    return;
}


