/* ================================================================================== */
/* Copyright (c) 1998-1999 3Com Corporation or its subsidiaries. All rights reserved. */
/* ================================================================================== */

#include "EmulatorCommon.h"

#include "CPU_REG.h"
#include "Platform.h"
#include "Strings.r.h"

#include "CPU_MT.h"

// -----------------------------------------------------------------------------
// constructor / destructor
// -----------------------------------------------------------------------------

CPU::CPU()
    : _beginSemaphore(0), _endSemaphore(0)
{
    _running = false;
    _quitRequest = false;

    // start the emulator thread (which will immediately block)
    start_undetached();
}

// ---------------------------------------------------------------------------
CPU::~CPU()
{ 
    // Free up the memory.
    Emulator::Dispose();
}

// -----------------------------------------------------------------------------
// public methods
// -----------------------------------------------------------------------------

// -----------------------------------------------------------------------------
// Initialize the CPU
ErrCode CPU::initialize(const AppPreferences::Configuration& cfg)
{
	try
	{
		Emulator::Initialize (cfg);
	}
	catch (ErrCode errCode)
	{
		return errCode;
	}

	_running = false;
	_quitRequest = false;

	return 0;
}

// -----------------------------------------------------------------------------
// Initialize the CPU
ErrCode CPU::load (const FileReference& ramFile)
{
	try
	{
		Emulator::Load (ramFile, true, true, true);
	}
	catch (ErrCode errCode)
	{
		return errCode;
	}

	_running = false;
	_quitRequest = false;

	return 0;
}

// -----------------------------------------------------------------------------
// shut down this CPU thread and let it die
void CPU::dispose()
{
    // Should already be stopped, but let's just make sure:
    stop();

    // Tell the thread to quit.
    _quitRequest = true;

    // OK, start it up again so that it can die. This causes the destructor to get run
    // when the run method exits...
    run();

    // wait for the CPU thread ("this") to finish.
    //! race condition? what if the thread has already finished?
    //! experimentation seems to show that Omnithreads handles this
    //! case OK, but nonetheless, this warrants re-thinking.
    this->join( NULL );
}

// -----------------------------------------------------------------------------
// reset the emulator
void CPU::reset( void )
{
    stop();
    Emulator::Reset();
    run();
}

// -----------------------------------------------------------------------------
// Start up the CPU thread.  If the thread is already running, do nothing.
// Otherwise, unsignal the event that flags when the CPU stops, and unblock
// the thread by signalling gCPUBeginEvent.
void CPU::run( void )
{
    if ( _running )
        return;

    _beginSemaphore.post();
}


// ---------------------------------------------------------------------------
// Stop the CPU thread, returning whether or not the thread was running when
// this function was called (true == it was running, false == it was already
// stopped).  If the thread is running, this function asks it to stop, and
// then waits FOREVER for it to stop.
bool CPU::stop( void )
{
    if ( !_running )
        return false;

    // Ask the CPU loop to exit.
    // NOTE: this is not done is a strictly thread-safe manner, as the emulator
    // has no knowledge of our threading system. Nonetheless, it works OK since
    // this routine just sets a simple variable and exits. Should SetBreakReason
    // ever become more complex, we could have a problem.
    Emulator::SetBreakReason( kBreak_StopRequest );
  
    _endSemaphore.wait();
  
    return true;	// Say that we stopped it.
}


// -----------------------------------------------------------------------------
// send a pen down or up event to the Emulator. The CPU is temporarily stopped.
void CPU::pen( bool down, int x, int y )
{
    // Don't do anything if the CPU is already stopped.
    // It might, for example, be in the debugger.
  
    if ( stop() )
    {
        Hardware::PenEvent( down, x, y );
        run();
    }
}

// ---------------------------------------------------------------------------
// send a button down or up event to the Emulator.
// The CPU is temporarily stopped.
void CPU::button( bool down, int button )
{
    // Don't do anything if the CPU is already stopped.
    // It might, for example, be in the debugger.
  
    if ( stop() )
    {
        Hardware::ButtonEvent( down, button );
        run();
    }
}


// ---------------------------------------------------------------------------
// send a hotsync button down or up event to the Emulator.
// The CPU is temporarily stopped.
void CPU::hotsync( bool down )
{
    // Don't do anything if the CPU is already stopped.
    // It might, for example, be in the debugger.
  
    if ( stop() )
    {
        Hardware::HotSyncEvent( down );
        run();
    }
}


// ---------------------------------------------------------------------------
// send a key event to the Emulator. The CPU is temporarily stopped.
void CPU::putkey( unsigned char key )
{
    // Don't do anything if the CPU is already stopped.
    // It might, for example, be in the debugger.
  
    if ( stop() )
    {
        Hardware::KeyboardEvent( key );
        run();
    }
}

// ---------------------------------------------------------------------------
// send a key event to the Emulator. The CPU is temporarily stopped.
bool CPU::executeuntilatrap( void )
{
    // Tell the CPU emulator to exit on the next "ATrap"
    bool old = Emulator::SetBreakOnException( kException_Trap15, true );

    // Wait for it to do so (one second).
    bool ok = ( _endSemaphore.trywait() > 0 );
    if ( !ok )
    {
        // give it a second:
        omni_thread::sleep( 1, 0 );
	ok = ( _endSemaphore.trywait() > 0 );
    }

    // Restore the old setting.
    (void) Emulator::SetBreakOnException( kException_Trap15, old );

    return ok;
}

// -----------------------------------------------------------------------------
// private methods
// -----------------------------------------------------------------------------

// -----------------------------------------------------------------------------
// CPU thread main loop.
void* CPU::run_undetached( void* data )
{
    while (1)
    {
        // wait for the begin signal...
        _beginSemaphore.wait();

        if ( _quitRequest )
            break;
    
        _running = true;
        Emulator::Execute();
        _running = false;

        // signal stopped
        _endSemaphore.post();
    }

    return NULL;
}

// -----------------------------------------------------------------------------
// CPU Stopper class
// -----------------------------------------------------------------------------

// -----------------------------------------------------------------------------
// Stack object used to temporarily stop the emulator
CPUStopper::CPUStopper( CPU* cpu, const char* msg )
        : _cpu( cpu )
{
    if ( _cpu == NULL )
    {
        _ok = true;
    }
    else
    {
        // Stop the CPU on an ATrap instruction.
        _ok = _cpu->executeuntilatrap();
        
        if (!_ok)
        {
            // See comment in ../SrcWin/CPU_MT.cpp
            char buffer[200];
            string	format( Platform::GetString( kStr_EmulatorOff ) );
            sprintf( buffer, format.c_str (), msg );
            string	app( Platform::GetString( kStr_AppName ) );
            Platform::CommonDialog( app.c_str(), buffer, Errors::kErrorAlert | Errors::kOK );
        }
    }
}

// -----------------------------------------------------------------------------
// destructor lets the CPU run again
CPUStopper::~CPUStopper()
{
    if ( _ok && (_cpu != NULL))
    {
        _cpu->run();
    }
}
