#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>
#include <stdarg.h>
#include <time.h>
#include <sys/time.h>
#include <unistd.h>
#include <sys/resource.h> 
#include <ctype.h>
#include "../eCosTest.h"
#include "../eCosTestUtils.h"


void CeCosTest::RunGdb(const CeCosTestUtils::String &strExec, const CeCosTestUtils::String *arstrCmds)
{
    const char *pszExecutable=(const char *)m_strExecutable;
    int pipe_ends_w[2];
    if (pipe(pipe_ends_w) < 0 ) { 
        Log("Failed to create pipe_ends_w - %s\n",strerror(errno));
    } else {
        int pipe_ends_r[2];
        if (pipe(pipe_ends_r) < 0 ) { 
            Log("Failed to create pipe_ends_r - %s\n",strerror(errno));
        } else {
			
		    ENTERCRITICAL;
			// Ensure no one else has the lock such that the child might block in future
            int new_pid = fork();
			// This leave is executed in *both* the child and parent
			LEAVECRITICAL;

            switch (new_pid) {
                // Fork failed
                case -1:
                    Log("Failed to create gdb process - %s\n",strerror(errno));
                    break;
                case 0:
				    // Process is created (we're the child)
                    // No point in calling Log in this process

                    // Input to child process
                    if (dup2(pipe_ends_w[0], 0) < 0) {
                        TRACE("dup2 error\n");
                        exit(1);
                    }

                    // Output from process
                    if (dup2(pipe_ends_r[1], 2) < 0) {
                        TRACE("dup2 error\n");
                        exit(2);
                    }
                    if (dup2(pipe_ends_r[1], 1) < 0) {
                        TRACE("dup2 error\n");
                        exit(3);
                    }
                    setvbuf(stdout,0,_IONBF,0);
                    setvbuf(stderr,0,_IONBF,0);
				    execlp(strExec, (const char *)strExec, "-nw", pszExecutable, NULL);
                    TRACE("exec error - %s\n",strerror(errno));
                    exit(4);
                default:
                    // Process is created (we're the parent)

                    TRACE("Forked to create gdb process '%s %s %s' - pid is <%d>\n", (const char *)strExec, "-nw", pszExecutable, new_pid);
                    if (fcntl(pipe_ends_r[0], F_SETFL, O_NONBLOCK) <0) {
                        Log("Couldn't set pipe non-blocking - %s\n",strerror(errno));
                    } else {
					    m_pGdbProcesshandle=(void *)new_pid;
					    VTRACE("RunGdb():Calling DriveGdb\n");
                        m_rPipeHandle=(void *)pipe_ends_r[0];
                        m_wPipeHandle=(void *)pipe_ends_w[1];
					    DriveGdb ((void *)arstrCmds);
					    // Finished one way or another. Kill gdb now
					    Trace("Finished processing this test. Killing gdb\n");
					    if ( kill(new_pid, SIGTERM) == 0 ) {
						    Trace("waitpid <%d>", new_pid);
						    int i;
						    for(i=0;i<10;i++){
							    int status;
							    switch(waitpid(new_pid,&status,WNOHANG)){
								    case 0:
                                        CeCosTestUtils::Sleep(1000);
									    continue;
								    case -1:
									    Log("Failed to wait for gdb process to die\n");
									    SetStatus(TimeOut);
									    break;
								    default:
									    break;
							    }
							    break;
						    }
						    if(10==i){
							    Log("Failed to wait for gdb process to die\n");
							    SetStatus(TimeOut);
						    }
						    close(new_pid);
					    } else {
						    Log("Failed to kill gdb process %d\n",new_pid);
					    }
					    Trace("Total elapsed time is %d\n", m_nTotalTime);
					    Trace("Killed\n");
    				}
                    break;
            }
            VTRACE("Closing pipe_ends_r[]\n");
            close (pipe_ends_r[0]);
            close (pipe_ends_r[1]);
        }
        close (pipe_ends_w[0]);
        close (pipe_ends_w[1]);
    }
}

bool CeCosTest::GdbProcessAlive()
{
	int status;
	return 0==waitpid((int)m_pGdbProcesshandle,&status,WNOHANG);
}

void CeCosTest::LowerGdbPriority()
{
	;
}

CeCosTestUtils::Time CeCosTest::GdbCpuTime()
{
    if(GdbProcessAlive()){
        CeCosTestUtils::Time now=CeCosTestUtils::Time::Now();
        if(now-m_tPrevSample>1000){
            m_tPrevSample=now;
            CeCosTestUtils::String strCmd;
            strCmd.Format("ps Sh %u",m_pGdbProcesshandle);
            FILE *f=popen(strCmd,"r");
            if(f){
                char buf[100];
                fgets(buf,sizeof(buf)-1,f);
                pclose(f);
                // Output is something like "19617  p1 S    1:23 ..."
                const char *pcSecs=strchr(buf,':');
                if(pcSecs){
                    const char *pcMins=pcSecs++;
                    do {
                        --pcMins;
                    } while(pcMins>=buf&&isdigit(*pcMins));
                    pcMins++;
                    int t=atoi(pcMins)*60+atoi(pcSecs);
                    VTRACE("ps Sh %d=='%s' -> cpu=%d\n",m_pGdbProcesshandle,buf,t);
                    m_tGdbCpuTime=CeCosTestUtils::Time(1000*t);
                }
            }
        }
    } else {
        m_tGdbCpuTime=0;
    }
    return m_tGdbCpuTime;
}

bool CeCosTest::WritePipe (void *Handle,const char *pszBuf)
{
	int write_fd = (int)Handle;
    int dwWritten;
    int nToWrite=strlen(pszBuf);
    do {
        dwWritten = write(write_fd, pszBuf, nToWrite);
        if(-1==dwWritten){
            Log("pipe write error - %s\n",strerror(errno));
            SetStatus(NoResult);
            break;
        }
        nToWrite-=(int)dwWritten;
        pszBuf+=(int)dwWritten;
		CeCosTestUtils::Duration dTimeout=m_bDownloading?ElapsedTimeout():ActiveTimeout();
        if(!CheckForTimeout()){
            return false;
        }
		if (Sim() && GdbCpuTime()-m_tBase>PRIORITYLATCH){
			LowerGdbPriority();
		}
    } while (nToWrite>0);
	return true;
}

int CeCosTest::ReadPipe (void *Handle,CeCosTestUtils::String &str,bool bBlocking /* This param ignored */)
{
	const int bufsize=4096;
    char *buf=str.GetBuffer(bufsize);
    int rc = read((int)Handle, buf, bufsize-1);
    if(-1==rc && EAGAIN==errno){
        rc=0;
    }
    buf[max(0,rc)]='\0';
    return rc;
}

void CeCosTest::GetPath(CeCosTestUtils::String &strPath)
{
	strPath=getenv("PATH");
}

void CeCosTest::SetPath(const CeCosTestUtils::String &strPath)
{
	CeCosTestUtils::String str;
	str.Format("PATH=%s",(const char *)strPath);
	putenv(str);
}
