//=================================================================
//
//        eCosTestUtils.cpp
//
//        Utility functions
//
//=================================================================
//####COPYRIGHTBEGIN####
//
// -------------------------------------------
// The contents of this file are subject to the Cygnus eCos Public License
// Version 1.0 (the "License"); you may not use this file except in
// compliance with the License.  You may obtain a copy of the License at
// http://sourceware.cygnus.com/ecos
// 
// Software distributed under the License is distributed on an "AS IS"
// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.  See the
// License for the specific language governing rights and limitations under
// the License.
// 
// The Original Code is eCos - Embedded Cygnus Operating System, released
// September 30, 1998.
// 
// The Initial Developer of the Original Code is Cygnus.  Portions created
// by Cygnus are Copyright (C) 1998, 1999 Cygnus Solutions.
// All Rights Reserved.
// -------------------------------------------
//
//####COPYRIGHTEND####
//=================================================================
//#####DESCRIPTIONBEGIN####
//
// Author(s):     sdf
// Contributors:  sdf
// Date:          1999-04-01
// Description:   This class contains utility functions for use in the testing infrastructure
// Usage:
//
//####DESCRIPTIONEND####

#include "stdafx.h"
#include "eCosTestUtils.h"
#include "eCosTest.h"

int  nCSOwner=-1;
int  CeCosTestUtils::m_nCriticalSectionLock=0;
bool CeCosTestUtils::bVerbose=false;
FILE *CeCosTestUtils::fTrace=stderr;

#ifdef _WIN32
int CALLBACK CeCosTestUtils::FilterFunction(LPEXCEPTION_POINTERS p)
{
    PEXCEPTION_RECORD pRec=p->ExceptionRecord;
    PCONTEXT       pContext=p->ContextRecord;
    TRACE("!!!Exception!!! address=%08x code=%08x stack:\n",pRec->ExceptionAddress,pRec->ExceptionCode);
    const unsigned char *ESP=(const unsigned char *)pContext->Esp;
    for(int i=0;i<16;i++){
        char s[49];
        static const char hexchar[]="0123456789ABCDEF";
        char *c=s;
        for(int j=0;j<16;j++){
            *c++=hexchar[(*ESP)/16];
            *c++=hexchar[(*ESP)%16];
            *c++=' ';
            ESP++;
        }
        *c='\0';
        TRACE("%08x %s\n",ESP,s);
        ESP+=16;
    }

    return EXCEPTION_EXECUTE_HANDLER;
}
#endif

bool CeCosTestUtils::ParseHostPort (const char * const pszHostPort, CeCosTestUtils::String &strHost, int &nPort)
{
	nPort=0;
	bool rc=false;
	const char *c=strchr(pszHostPort,':');
	if(c){
		nPort=atoi(c+1);
		if(nPort && nPort>0 && nPort<=0xffff){
            int nLength=c-pszHostPort;
            strncpy(strHost.GetBuffer(nLength),pszHostPort,nLength);
			strHost.ReleaseBuffer();
            while(*++c && isdigit(*c));
			rc=('\0'==*c);
		}
	}
	return rc;
}

bool CeCosTestUtils::IsLegalHostPort (const char * const pszHostPort)
{
	int nPort=0;
    CeCosTestUtils::String strHost;
	return ParseHostPort(pszHostPort,strHost,nPort);
}

void CeCosTestUtils::Trace(const char * pszFormat, ...)
{
    if(bVerbose){
        va_list marker;
        va_start (marker, pszFormat);
	    CeCosTestUtils::String str;
        str.vFormat(pszFormat,marker);
        va_end (marker);
	    
        time_t ltime;
        time(&ltime);
        struct tm *now=localtime( &ltime );

        String s;
        void *id=GetThreadId();
        s.Format("[%x %02d:%02d:%02d%s] %s",id,now->tm_hour,now->tm_min,now->tm_sec,nCSOwner==(int)id?"*":"",(const char *)str);
        
        #ifdef _WIN32
        OutputDebugString(s);
        #endif
        fputs(s,fTrace);
	    fflush(fTrace);
    }
}

void CeCosTestUtils::Error(const char * pszFormat, ...)
{
    va_list marker;
    va_start (marker, pszFormat);
	CeCosTestUtils::String str;
    str.vFormat(pszFormat,marker);
    va_end (marker);
	
    time_t ltime;
    time(&ltime);
    struct tm *now=localtime( &ltime );

    String s;
    if(bVerbose){
        void *id=GetThreadId();
        s.Format("[%x %02d:%02d:%02d%s] %s",id,now->tm_hour,now->tm_min,now->tm_sec,nCSOwner==(int)id?"*":"",(const char *)str);
    } else {
        s=str;
    }

    #ifdef _WIN32
    OutputDebugString(s);
    #endif
    fputs(s,fTrace);
	fflush(fTrace);
}


#ifdef _WIN32
static CRITICAL_SECTION cs;
static bCSInitialized=false;
#else
// Static mutex for unix critical section
static pthread_mutex_t cs = PTHREAD_MUTEX_INITIALIZER;
#endif

void CeCosTestUtils::EnterCriticalSection(const char *pszFile,int nLine)
{
    #ifdef VERBOSE
    fprintf(stderr,"%x try CS %s:%d\n",GetThreadId(),pszFile,nLine);//sdf
    #else
    nLine;pszFile; //prevent compiler warning
    #endif
    #ifdef _WIN32
	if(!bCSInitialized){
		InitializeCriticalSection(&cs);
        bCSInitialized=true;
	}
	::EnterCriticalSection(&cs);
    #else
    // Get mutex lock; block until available unless current
    // thread already owns the mutex.
    if ((int)GetThreadId() != nCSOwner)
        pthread_mutex_lock(&cs);
    #endif
    nCSOwner=(int)GetThreadId();
    m_nCriticalSectionLock++;
    #ifdef VERBOSE
    fprintf(stderr,"%x has CS count=%d\n",GetThreadId(),m_nCriticalSectionLock);//sdf
    #endif
}

void CeCosTestUtils::LeaveCriticalSection(const char *pszFile,int nLine)
{
    m_nCriticalSectionLock--;
    nCSOwner=-1;
    #ifdef VERBOSE
    fprintf(stderr,"%x leaves CS count=%d %s:%d\n",GetThreadId(),m_nCriticalSectionLock,pszFile,nLine);//sdf
    #else
    pszFile;nLine; // prevent compiler warnings
    #endif

    assert(m_nCriticalSectionLock>=0);
    #ifdef _WIN32
    ::LeaveCriticalSection(&cs);
    #else
    // Release mutex lock.
    if (0 == m_nCriticalSectionLock)
        pthread_mutex_unlock(&cs);
    else
        nCSOwner=(int)GetThreadId();
    #endif
}

CeCosTestUtils::GarbageCollector::GarbageCollector()
{
    #ifdef _WIN32
	WSADATA wsaData;
	WORD wVersionRequested = MAKEWORD( 2, 0 ); 
	WSAStartup( wVersionRequested, &wsaData );
	#else
    sigset_t mask;

    // Clean out all the signals
    sigemptyset(&mask);

    // Add our sigpipe
    sigaddset(&mask, SIGPIPE);

    sigprocmask(SIG_SETMASK, &mask, NULL);

    #endif
};

CeCosTestUtils::GarbageCollector::~GarbageCollector()
{
	CeCosTest::DeleteAllInstances();
    #ifdef _WIN32
	WSACleanup();
    if(bCSInitialized){
        DeleteCriticalSection(&cs);
        bCSInitialized=false;
    }
    #endif
};


const char * const CeCosTestUtils::Tail(const char * const pszFile)
{
    const char *pszTail=strrchr(pszFile,'/');
    if(0==pszTail){
        pszTail=strrchr(pszFile,'\\');
    }
    return (0==pszTail)?pszFile:pszTail+1;
}

CeCosTestUtils::GarbageCollector CeCosTestUtils::GarbageCollector::gc;

int CeCosTestUtils::AtomicIncrement (int &n)
{
    ENTERCRITICAL;
        int rc=n++;
    LEAVECRITICAL;
    return rc;
}

int CeCosTestUtils::AtomicDecrement (int &n)
{
    ENTERCRITICAL;
        int rc=n--;
    LEAVECRITICAL;
    return rc;
}

CeCosTestUtils::String::String () : m_pszData(0), m_nSize(0)
{
    AllocCopy("");
}

CeCosTestUtils::String::String (const char *pszStr) : m_pszData(0), m_nSize(0)
{
    AllocCopy(pszStr);
}

CeCosTestUtils::String::String (const CeCosTestUtils::String &o) : m_pszData(0), m_nSize(0)
{
	AllocCopy(o.m_pszData);
}

CeCosTestUtils::String::~String()
{
	delete [] m_pszData;
    m_pszData=0;
}

const CeCosTestUtils::String& CeCosTestUtils::String::operator+=(const char *pszStr)
{
	if(0!=pszStr){
        unsigned int nLen=strlen(m_pszData);
		SetLength(nLen+strlen(pszStr));
		strcpy(m_pszData+nLen,pszStr);
	}
	return *this;
}

const CeCosTestUtils::String& CeCosTestUtils::String::operator=(const char *lpsz)
{
	AllocCopy(lpsz);
	return *this;
}

const CeCosTestUtils::String& CeCosTestUtils::String::operator=(const String &o)
{
	AllocCopy(o.m_pszData);
	return *this;
}

const CeCosTestUtils::String& CeCosTestUtils::String::operator+=(char ch)
{
    int nLen=GetLength();
    SetLength(1+nLen);
	m_pszData[nLen]=ch;
	m_pszData[nLen+1]='\0';
    return *this;
}

void CeCosTestUtils::String::Format (const char * const pszFormat,...)
{
	va_list args;
	va_start(args, pszFormat);
	vFormat(pszFormat,args);
	va_end(args);
}

void CeCosTestUtils::String::vFormat(const char * pszFormat, va_list marker)
{
#ifdef _WIN32
    int nLength=1000;
    int n;
    do {
        SetLength(nLength);
        n=vsnprintf( m_pszData, nLength, pszFormat, marker ); 
        nLength+=100;
    } while (n==-1);
    SetLength(n);
    return;
#else
    const char *pszF=pszFormat;
	m_pszData[0]='\0';
	while(*pszFormat){
        //CHECKHEAP;
		if('%'==*pszFormat){
			pszFormat++;
			if('%'==*pszFormat){
				// Simply escaping a '%'
				operator+=('%');
        		//CHECKHEAP;
			} else {
				// Pick an arg and format it
				// Calculate a length good enough for this item.
				// The initial value is ok for integers sans override
				unsigned int nItemLen=12;
				char szFormatItem[20]={'%'}; // We reckon a format item will fit in this
				unsigned int nFormatLen=1; // length of the format item (e.g. the "%08x")
				// We ought to be supporting
				//      %[flags] [width] [.precision] [{h | l | I64 | L}]type
				// We'll suck everything up to a 'flags' character into szFormatItem
				bool bReadWidth=false;
				while(0==strchr("cCdiouxXeEfgGnpsS",*pszFormat)){
					if(!bReadWidth && isdigit(*pszFormat)){
						nItemLen+=(unsigned)atoi(pszFormat); // User is overriding length [probably]
						bReadWidth=true;
					}
					if(2+nFormatLen>=sizeof szFormatItem){
						return;
					}
					szFormatItem[nFormatLen++]=*pszFormat++;
				}
				szFormatItem[nFormatLen++]=*pszFormat;
				szFormatItem[nFormatLen]='\0';
				// Now szFormatItem holds the format string
				// Extract the argument according to type
				union {
					const void *pArg;
					int nArg;
					double dArg;
					const char *pszArg;
				} u;
				bool bDouble=true;
				switch(*pszFormat){
					case 'c':
					case 'C':
					case 'd':
					case 'i':
					case 'o':
					case 'u':
					case 'x':
					case 'X':
						u.nArg=va_arg(marker,int);
						break;
					case 'e':
					case 'E':
					case 'f':
					case 'g':
					case 'G':
						u.dArg=va_arg(marker,double);
						bDouble=true;
						break;
					case 'n':
					case 'p':
						u.pArg=va_arg(marker,const void *);
						break;
					case 's':
					case 'S':
						u.pszArg=va_arg(marker,const char *);
                        if(0==u.pszArg){
                            u.pszArg="(null)";
                        }
                        nItemLen=max(nItemLen,1+strlen(u.pszArg)); // It's a string - make sure we can accommodate it
						break;
					default:
                                            fprintf(stderr, "Don't know how to print this format code : %c\n", *pszFormat);
                                            fprintf(stderr, "Full string is '%s'\n", pszFormat);
						assert(false);
				}
				unsigned int nLen=GetLength();
				SetLength(nLen+nItemLen+100);
				int n=sprintf(m_pszData+nLen,szFormatItem,bDouble?u.dArg:u.nArg);
                if((unsigned)n>nItemLen){
                    if('s'==*pszFormat){
                        fprintf(stderr,"arg=%s\n",u.pszArg);
                    }
                    fprintf(stderr,"offs=%d n=%d\nnItemlen=%d\npszFormat=%s\n",pszFormat-pszF,n,nItemLen,pszF);
                }
                assert((unsigned)n<=nItemLen);
        		//CHECKHEAP;
			}
		} else {
			// pick a single character out of the format string
			operator+=(*pszFormat);
    		//CHECKHEAP;
		}
		pszFormat++;
	}
#endif
}

char CeCosTestUtils::String::operator[](unsigned int nIndex) const
{
	assert(nIndex<GetLength());
	return m_pszData[nIndex];
}

void CeCosTestUtils::String::AllocCopy(const char *pszContents)
{
    if(0==pszContents){
        pszContents="";
    }
    unsigned int nSize=1+strlen(pszContents);
    if(nSize>m_nSize){
        delete [] m_pszData;
    	m_nSize=nSize|255; // 256-byte chunks
        m_pszData=new char[m_nSize];
    }
    strcpy(m_pszData,pszContents);
}

void CeCosTestUtils::String::SetLength(unsigned int nLength)
{
    if(1+nLength>m_nSize){
        int nOldsize=m_nSize;
    	m_nSize=1+(nLength|255); // 256-byte chunks
        char *pszNew=new char[m_nSize];
        memcpy(pszNew,m_pszData,nOldsize);
        delete [] m_pszData;
        m_pszData=pszNew;
    }
    m_pszData[nLength]='\0';
}

void CeCosTestUtils::String::MakeLower()
{
	for(char *c=m_pszData;*c;c++){
		if(isalpha(*c)){
			*c=(char)tolower(*c);
		}
	}
}
void CeCosTestUtils::String::MakeUpper()
{
	for(char *c=m_pszData;*c;c++){
		if(isalpha(*c)){
			*c=(char)toupper(*c);
		}
	}
}

char * CeCosTestUtils::String::GetBuffer(unsigned int nLength)
{
	SetLength(nLength);
	return m_pszData;
}

char * CeCosTestUtils::String::GetBuffer()
{
	return m_pszData;
}

void CeCosTestUtils::String::ReleaseBuffer()
{

}

bool CeCosTestUtils::SetTraceFile(const char *pszFile)
{
    FILE *f=fopen(pszFile,"at");
    if(f){
        if(fTrace!=stderr){
            fclose(fTrace);
        }
        fTrace=f;
    }
    return 0!=f;
}

void * CeCosTestUtils::GetThreadId()
{
    return (void *)
    #ifdef _WIN32
        GetCurrentThreadId();
    #else
        pthread_self();
    #endif
}

// Wait for the specified Boolean to turn true or the timeout to occur
// The value of the Boolean is returned as result
bool CeCosTestUtils::WaitFor (bool &b, CeCosTestUtils::Duration dTimeout)
{
    CeCosTestUtils::Time t=CeCosTestUtils::Time::Now();
    do {
        if(b){
            break;
        }
        Sleep(250);
    } while (CeCosTestUtils::Time::Now()-t<dTimeout);
    return b;
}

void CeCosTestUtils::Sleep(Duration nMsec)
{
    if(nMsec>0){
#ifdef _WIN32
	    ::Sleep(nMsec);
#else
        usleep((int)nMsec * 1000);
#endif
    }
    if(nMsec>0){
    }
}

CeCosTestUtils::Time CeCosTestUtils::Time::Now()
{
#ifdef _WIN32
	__int64 ft;
	SYSTEMTIME st;
	GetSystemTime (&st);
	SystemTimeToFileTime(&st,(FILETIME *)&ft);
	return Time((int)(ft/=10000));
#else
	struct timeval tv;
	gettimeofday(&tv,0);
	Time t;
    return Time(tv.tv_sec*1000+tv.tv_usec/1000);
#endif
}
