#ifndef _ECOSTEST_H
#define _ECOSTEST_H
// eCos testing infrastructure
// This class represents a single eCos test [executable].
// It includes member functions to run the test and to manage
// related system resources.
#include "stdafx.h"
#include "eCosTestUtils.h"
#include "eCosTestSocket.h"
class CPort;

#ifdef _WIN32
    #define CALLBACK    __stdcall               // Calling conventions for a callback
    #define THREAD_ID void *                    // Type of a thread_id
    #define THREADFUNC unsigned long __stdcall  // Result type of the thread function
    #define WOULDBLOCK WSAEWOULDBLOCK           // "Would blocking" error
#else
    #include <pthread.h>
    #define THREAD_ID pthread_t
    #define THREADFUNC void *
    #define WOULDBLOCK EWOULDBLOCK
    #define CALLBACK
#endif

#ifndef min
    #define min(a,b) (a<b?a:b)
#endif

class CeCosTest{
public:
    ///////////////////////////////////////////////////////////////////////////
    // Representation of an elapsed time (units of milliseconds)
    enum {NOTIMEOUT=0}; // No timeout specified

    ///////////////////////////////////////////////////////////////////////////
    // ctors, dtors and their friends
    class ExecutionParameters;
    CeCosTest(const ExecutionParameters &e, const char * const pszExecutable, const char * const pszTitle=0);
    virtual ~CeCosTest();
    // Count of number of instances of this class:
    static int InstanceCount;
    // Delete all heap instances of this class (*must* be allocated on heap)
    static void DeleteAllInstances ();
    // Simply wait for instances to die a natural death
    static bool WaitForAllInstances (int nPoll=1000,CeCosTestUtils::Duration nTimeout=NOTIMEOUT);
    // Tap them on the shoulder (does not wait)
    static void CancelAllInstances ();
    ///////////////////////////////////////////////////////////////////////////

    ///////////////////////////////////////////////////////////////////////////
    // Representation of target:
    enum TargetType {
        TX39_jmr3904,
        TX39_minsim,
        TX39_jmr3904_sim,
        PowerPC_cogent,
        PowerPC_sim,
        SPARClite_sim,
        SPARClite_sleb,
        ARM_PID,
        ARM_AEB,
        MN10300_stdeval1,
        MN10300_minsim,
        MN10300_stdeval1_sim,
        I386_Linux,
        TargetTypeMax };
	static const char * const Image(TargetType t) { return arTargetInfo[min(t,TargetTypeMax)].pszImage; }
    // Translate a string to a target type (returns null if no luck)
    static TargetType TargetTypeValue (const char * const pszStr);
	static TargetType FromStr (const char * const pszStr) { return TargetTypeValue(pszStr); }
    ///////////////////////////////////////////////////////////////////////////

    ///////////////////////////////////////////////////////////////////////////
    // Class used to represent execution parameters (to be passed with request to execute a test)
    // This needs to be serializable (capabable of being [un]marshalled for socket communication)
    // and so we are careful to use an int[] internally to hold the data.
    class ExecutionParameters {
        enum { SERIALIZE_LENGTH=5*sizeof(int) };
    public:
		enum RequestType { RUN, QUERY, LOCK, UNLOCK, STOP, RequestTypeMax};

        static const char * Image (RequestType r) { return arRequestImage[r]; }
        typedef int Data[SERIALIZE_LENGTH/sizeof(int)];
        ExecutionParameters (const Data buf) { memcpy(m_arE,buf,SERIALIZE_LENGTH); }
        const Data * const Marshall() const { return &m_arE;}
        CeCosTestUtils::Duration    ActiveTimeout() const { return (CeCosTestUtils::Duration)m_arE[0]; }
        CeCosTestUtils::Duration    ElapsedTimeout() const { return (CeCosTestUtils::Duration)m_arE[1]; }

        TargetType  Target() const { return (TargetType)m_arE[3]; }
        bool        IsValid() const { return (unsigned)Target()<(unsigned)TargetTypeMax; }
        RequestType Request() const { return (RequestType)m_arE[4];}
        //  nTimeout       : timeout in milliseconds
        //  bSim           : whether execution is to be on a simulator
        void SetActiveTimeout  (CeCosTestUtils::Duration t){m_arE[0]=t;}
        void SetElapsedTimeout (CeCosTestUtils::Duration t){m_arE[1]=t;}
		void SetRequest        (RequestType r){m_arE[4]=r;}

        ExecutionParameters (
            TargetType  Target,
            CeCosTestUtils::Duration    nActiveTimeout=NOTIMEOUT,
            CeCosTestUtils::Duration    nElapsedTimeout=NOTIMEOUT)
        {
            m_arE[0]=(int)nActiveTimeout;
            m_arE[1]=(int)nElapsedTimeout;
            m_arE[2]=0; // unused
            m_arE[3]=(int)Target;
			m_arE[4]=RUN;
        }
        virtual ~ExecutionParameters()
        {}
    protected:
	    static const char * arRequestImage [1+RequestTypeMax];
	    //static const char * arExecutableTypeImage [1+ExecutableTypeMax];
        Data m_arE;
    };
    ///////////////////////////////////////////////////////////////////////////
    
    ///////////////////////////////////////////////////////////////////////////
    // Result status stuff.
    // Order is important - SetStatus can only change status in left-to-right direction
    enum StatusType {NotStarted, NoResult, Inapplicable, Pass, DownloadTimeOut, TimeOut, Cancelled, Fail, StatusTypeMax};
    static StatusType StatusTypeValue (const char * const pszStr);
	static const char * const Image(StatusType s) { return arResultImage[min(s,StatusTypeMax)]; }
    ///////////////////////////////////////////////////////////////////////////

    ///////////////////////////////////////////////////////////////////////////
    // Attributes
    const char * const  Executable()                const { return m_strExecutable;}            // Executable name
    const               TargetType Target()         const { return m_ep.Target();}              // Target
    static bool         Sim(TargetType t)                 { return arTargetInfo[t].bSim; }
    bool                Sim()                       const { return Sim(Target()); }
    const char * const  Title()                     const;                                      // Title
    
    CeCosTestUtils::Duration            ActiveTimeout()             const { return m_ep.ActiveTimeout(); }      // Active timeout
    CeCosTestUtils::Duration            ElapsedTimeout()            const { return m_ep.ElapsedTimeout(); }     // Total timeout

    StatusType          Status()                    const { return m_Status; }                  // Test status
    
    CeCosTestUtils::Duration            Download()                  const { return m_nDownloadTime; }           // Download time
    CeCosTestUtils::Duration            Total()                     const { return m_nTotalTime; }              // Total
    CeCosTestUtils::Duration            MaxInactive()               const { return m_nMaxInactiveTime; }        // Max. inactive
    
    const char * const  Output()                    const { return m_strOutput; }               // Output generated by a test run [for report purposes]
    const CPort * const Port()                      const { return m_pPort; }                   // Port used for a test run [for report purposes]
 
    const char * const  ResultString ()             const;
    ///////////////////////////////////////////////////////////////////////////

    ///////////////////////////////////////////////////////////////////////////
    // Define the characteristics of a callback procedure:

    // A callback procedure.  The result from the procedure determines whether the
    // caller (the thread executing the test) should delete the class instance.  If
    // true is returned, this will happen and the class object must therefore be allocated
    // on the heap.
    typedef void (CALLBACK CallbackProc)(CeCosTest*,void *);
    // Struct to define a callback
    struct Callback {
        CallbackProc *m_pProc;  // Call this function
        void *        m_pParam; // With this parameter
        void Call();
        Callback (CallbackProc *pProc=0,void *pParam=0):
            m_pProc(pProc),
            m_pParam(pParam)
        {
        }
        bool IsNull() { return 0==m_pProc && 0==m_pParam; }
    };
    static const Callback NoCallback;
    void InvokeCallback (const Callback &cb);

    ///////////////////////////////////////////////////////////////////////////
    // Running a test:
    // If pCallback is non-null, behavior will be non-blocking with notification
    // via the callback procedure - otherwise behavior is blocking.
    // On completion of the test the results are logged.
    
    // The result of the function represents the ability to find a host on which to
    // run the test, not the result of the test.
    
    // Run a test locally:
    bool RunLocal (const Callback &cb=NoCallback);

    // Run a test remotely: 
    // If pszRemoteHostPort is given, it sends the test for execution on the given host:post.
    // Otherwise, a suitable host:port is determined from the test resource information.
    bool RunRemote (const char * const pszRemoteHostPort,const Callback &cb=NoCallback);
    void Cancel (); // Stop the run
    ///////////////////////////////////////////////////////////////////////////

    
    ///////////////////////////////////////////////////////////////////////////
    // Resource functions

    static void SetLogFile (const char * const pszFile);   // Define the log file
    
    // Run as a server on given TCP/IP port
    static bool RunAgent(int nTcpPort); 

public:
    struct TargetInfo {
        const char *pszImage;
        const char *pszPrefix;
        bool bSim;
        const char *pszGdbcmd;
    };
    bool InteractiveGdb(const CeCosTestUtils::String &strHost,int nPort,char **argv);
	void SetTimeouts (CeCosTestUtils::Duration dActive,CeCosTestUtils::Duration dElapsed);
	void SetExecutable (const char *pszExecutable);
	static bool Value (
		const char *pszStr, 
		struct tm &t,
		bool &bSim,
		StatusType &status,
		TargetType &target,
    	CeCosTestUtils::String &strExecutionHostPort,
	    CeCosTestUtils::String &strExecutableTail,
	    CeCosTestUtils::String &strTitle,
		int &nFileSize,
		CeCosTestUtils::Duration &nTotalTime,
		CeCosTestUtils::Duration &nMaxInactiveTime,
		CeCosTestUtils::Duration &nDownloadTime,
		CeCosTestUtils::Duration &nElapsedTimeout,
		CeCosTestUtils::Duration &nActiveTimeout,
		int &nDownloadedSize);
	void Trace (const char *pszFormat,...);

    CeCosTestUtils::Time GdbCpuTime();
    static int ReadPipe (void *Handle,CeCosTestUtils::String &str,bool bBlock=false);             // Read from gdb process
    enum ServerStatus {SERVER_BUSY, SERVER_READY, SERVER_CANT_RUN, CONNECTION_FAILED, SERVER_LOCKED, ServerStatusMax};
    static const char * const Image(ServerStatus s) { return arServerStatusImage[min(s,ServerStatusMax)]; }
	static ServerStatus Connect (CeCosTestUtils::String strHost,int port,CeCosTestSocket *&sock, const ExecutionParameters &e,CeCosTestUtils::Duration dTimeout=10*1000);
    // Delay for given count of milliseconds
    // Record test result in log file:
    void LogResult();
    bool ConnectForExecution (CeCosTestUtils::String strHost,int port);
    
    // Log some output.  The accumulated output can be retrieved using Output()
    void Log (const char * const pszFormat,...);
    void LogString (const char *psz);

protected:
    static CeCosTestUtils::String CygPath (const char *pszPath);

    static const char szGdbPrompt[];            // "(gdb) "
    static const unsigned int  nGdbPromptLen;	// Length of the above (null not included)fo
    void LogTimeStampedOutput(const char *psz);
	bool Suck(CeCosTestUtils::Duration d=1000);
	bool AtGdbPrompt();
    bool BreakpointsOperational() const { return /*true;*/ Sim();
    }
        //||(MN10300!=Target() && SPARClite_sleb!=Target() && PowerPC!=Target()); }
	void * m_wPipeHandle;
	void * m_rPipeHandle;
    
    // For Unix to limit calls to ps:
    CeCosTestUtils::Time m_tGdbCpuTime;
    CeCosTestUtils::Time m_tPrevSample;

    bool m_bDriveGdbComplete;
	int m_nStrippedSize;
	void SendKeepAlives(bool &b);
	void WaitForRemoteCompletion();
	CeCosTestUtils::Time GdbTime();
	CeCosTestUtils::String m_strPath;
	void GetPath(CeCosTestUtils::String &strPath);
	void SetPath(const CeCosTestUtils::String &strPath);

    // Function executed by thread creation system call
    static THREADFUNC SThreadFunc (void *pParam);
    
    unsigned int m_nFileSize;                   // Size of executable
    
	CeCosTestUtils::String m_strExecutionHostPort;
    
    ///////////////////////////////////////////////////////////////////////////
    // Stuff to manage running gdb as child subprocess
	bool CheckForTimeout();                     // Check for a timeout - set status and return false if it happens
    enum {PRIORITYLATCH=10*1000};               // Decrease gdb process priority after it uses this much CPU
    bool m_bDownloading;                        // Are we currently downloading executable?
    void LowerGdbPriority ();                   // Lower priority of gdb process
    bool GdbProcessAlive ();                    // Is gdb still alive and kicking?
    CeCosTestUtils::Time m_tBase;                               // Base used for measurement of timeouts
    CeCosTestUtils::Time m_tBase0;                              // Base used for measurement of timeouts
    CeCosTestUtils::Time m_tWallClock0;                         // When the test was actually started
    void * m_pGdbProcesshandle;                 // Handle associated with gdb process
    bool WritePipe (void *Handle,const char * const pszBuf); // Write to gdb process
    void DriveGdb(void *pParam);  // Run gdb
    ///////////////////////////////////////////////////////////////////////////

    ///////////////////////////////////////////////////////////////////////////
    // Socket helpers
	static void GetSimpleHostName(CeCosTestUtils::String &str);

    // Close the socket used by the current class instance
    void CloseSocket ();

    bool send(const void * const pData,unsigned int nLength,const char * const pszMsg="",CeCosTestUtils::Duration dTimeout=10*1000);
    bool recv(const void *pData,unsigned int nLength,const char * const pszMsg="",CeCosTestUtils::Duration dTimeout=10*1000);

    bool sendResult(CeCosTestUtils::Duration dTimeout=10*1000);
    bool recvResult(CeCosTestUtils::Duration dTimeout=10*1000);

    // Used to prevent a cascade of socket error messages:
    bool m_bSocketErrorOccurred;
    ///////////////////////////////////////////////////////////////////////////

    ///////////////////////////////////////////////////////////////////////////
    struct ThreadInfo {
        CeCosTest *pTest;
        void (CeCosTest::*pFunc)(void *);
        void *pParam;
        Callback cb;
        THREAD_ID hThread;
    };
    CeCosTestSocket *m_pSock;

    // While a server is executing a test on behalf of a client, a sequence of messages defined by this struct is sent.
    // When the test execution has completed a message containing "AliveByteZero" is sent.  Before this point,
    // the AliveInfo messages must contain at least one non-zero byte.
    struct AliveInfo {
        bool            Downloading;    // test is downloading
        StatusType      Status;         // current status
        unsigned int    Number;         // for future use
    };
    static AliveInfo AliveInfoZero;

    mutable CeCosTestUtils::String m_strResultString;

    const char * ExecutableTail() const { return CeCosTestUtils::Tail(m_strExecutable); }

    ExecutionParameters m_ep;

    // Chaining to allow *AllInstances functions to work:
    static CeCosTest * pFirstInstance;
    CeCosTest * m_pPrevInstance;
    CeCosTest * m_pNextInstance;

    void RunGdb (const CeCosTestUtils::String &pszExec, const CeCosTestUtils::String *arpszCmds);

    bool OutputContains(const char *psz) const { return 0!=strstr(m_strOutput,psz); }

    // This structure exists to marshall/unmarshall the information transmitted from server to client on completion
    // of a test.  It must contain all the information needed to form an idea of the result of the test (everything
    // in ResultString)
    //
    // This has to be more sophisticated if we support cross-architectural (endianness) execution
    //
    // Do not convert to a class without changing memset in recvResult
    struct Result {
        ExecutionParameters::Data buf;
        StatusType m_Status;
        CeCosTestUtils::Duration  m_nDownloadTime;
        CeCosTestUtils::Duration  m_nTotalTime;
        CeCosTestUtils::Duration  m_nMaxInactiveTime;
        int       m_nStrippedSize;
        char       szOutput[1]; // sized as necessary
    };

    void AcceptThreadFunc (void *pParam);
    void ConnectSocketToSerialThreadFunc(void *pParam);
    
    THREAD_ID RunThread(void (CeCosTest::*pThreadFunc)(void *), void *pParam, const Callback &cb);
    
    // Stuff.
    // Thread function used by RunLocal to execute a non-blocking test locally
    void LocalThreadFunc (void *pParam=0);
    // Thread function used by RunRemote to execute a non-blocking test remotely
    void RemoteThreadFunc (void *pParam=0);
    
    CeCosTestUtils::String m_strExecutable;
    CeCosTestUtils::String m_strTitle;

    void SetStatus (StatusType status);
    StatusType m_Status;

    CeCosTestUtils::Duration  m_nDownloadTime;
    CeCosTestUtils::Duration  m_nTotalTime;
    CeCosTestUtils::Duration  m_nMaxInactiveTime;

    CPort * m_pPort;

    static CeCosTestUtils::String strLogFile;
    CeCosTestUtils::String m_strOutput;

    static const TargetInfo arTargetInfo[1+CeCosTest::TargetTypeMax];

    static const char * const arResultImage[1+StatusTypeMax];
    static const char * const arServerStatusImage[1+ServerStatusMax];
    bool m_bConnectSocketToSerialThreadDone;
}; // class CeCosTest

    
    
#endif
