
//#define LOCAL_DEBUG
#include "debug.h"

#include "meta.h"
#include "conn.h"
#include "job.h"
#include "header.h"
#include "dlcon.h"
#include "acbuf.h"
#include "tcpconnect.h"
#include "cleaner.h"

#include <sys/select.h>
#include <signal.h>
#include <string.h>
#include <errno.h>
#include <iostream>

using namespace MYSTD;


con::con(int fdId, const char *c) :
	m_confd(fdId),
    m_bStopActivity(false),
    m_pDlClient(NULL),
    m_pTmpHead(NULL)
{
	if(c) // if NULL, pick up later when sent by the wrapper
		m_sClientHost=c;

    LOGSTART2("con::con", "fd: " << fdId << ", clienthost: " << c);

    // actually, just make sure that it runs right before, even if the cleaner thread is busy
    fileitem::DoDelayedUnregAndCheck();

#ifdef DEBUG
    m_nProcessedJobs=0;
#endif

};

con::~con() {
	LOGSTART("con::~con (Destroying connection...)");
	flushShutdownClose(m_confd);

	// our user's connection is released but the downloader task created here may still be serving others
	// tell it to stop when it gets the chance and delete it then

	MYSTD::list<job*>::iterator jit;
	for (jit=m_jobs2send.begin(); jit!=m_jobs2send.end(); jit++)
		delete *jit;

	logstuff.write();
	
    if(m_pDlClient) 
    {
    	m_pDlClient->SignalStop();
    	pthread_join(m_dlerthr, NULL);
    	
    	delete m_pDlClient;
    	m_pDlClient=NULL;
    }
    
    if(m_pTmpHead)
    {
    	delete m_pTmpHead;
    	m_pTmpHead=NULL;
    }
    				
    aclog::flush();

    // some extra work might have been postponed
    g_victor.Do();
}

class SoapPassThrough
{
	protected:
	tTcpHandlePtr m_spOutCon;

public:
	bool TrySoapPassThrough(acbuf &clientBufIn, int fdClient)
	{

#define POSTMARK "POST http://bugs.debian.org:80/"
#define FAKEMARK "POST                          /"

		if(clientBufIn.size()<_countof(POSTMARK)-1)
			return false;
		if(strncmp(clientBufIn.rptr(), POSTMARK, _countof(POSTMARK)-1))
			return false;

		string sErr;
		m_spOutCon = tcpconnect::CreateConnected("bugs.debian.org", "80", sErr);

		if(!m_spOutCon)
			return false;

		acbuf clientBufOut; // use custom buffers?
		clientBufOut.init(32*1024); // out to be enough for any BTS response

		// for convenience
		int ofd = m_spOutCon->GetFD();
		acbuf &serverBufOut = clientBufIn, &serverBufIn = clientBufOut;

		int maxfd=1+MYSTD::max(fdClient, ofd);

		while (true)
		{
			fd_set rfds, wfds;
			FD_ZERO(&rfds);
			FD_ZERO(&wfds);

			// can send to client?
			if(clientBufOut.size()>0)
				FD_SET(fdClient, &wfds);

			// can receive from client?
			if(clientBufIn.freecapa()>0)
				FD_SET(fdClient, &rfds);

			if(serverBufOut.size()>0)
				FD_SET(ofd, &wfds);

			if(serverBufIn.freecapa()>0)
				FD_SET(ofd, &rfds);

			int nReady=select(maxfd, &rfds, &wfds, NULL, NULL);
			if (nReady<0)
				return false;

			if(FD_ISSET(ofd, &wfds))
			{
				// if the incoming request is from a proxy-style acng setup then we need to
				// remove the HOST string from the header and replace it with something harmles.
				// But if there is another web proxy, let it deal with the stuff...
				if(!m_spOutCon->GetProxyData())
				{
					serverBufOut.move();
					char *p = 0;
					serverBufOut.flush();
					while (0 != (p = strstr((char*)serverBufOut.rptr(), POSTMARK)))
						memcpy(p, FAKEMARK, _countof(FAKEMARK) - 1);
				}

				if(serverBufOut.syswrite(ofd)<0)
					return false;
			}

			if(FD_ISSET(fdClient, &wfds))
			{
				if(clientBufOut.syswrite(fdClient)<0)
					return false;
			}

			if(FD_ISSET(ofd, &rfds))
			{
				if(serverBufIn.sysread(ofd)<=0)
					return true;
			}

			if(FD_ISSET(fdClient, &rfds))
			{
				if(clientBufIn.sysread(fdClient)<=0)
					return true;
			}
		}
		return false;
	};
};

void con::WorkLoop() {

	LOGSTART("con::WorkLoop");
    
	signal(SIGPIPE, SIG_IGN);
	
    acbuf inBuf;
    inBuf.init(32*1024);
    
    int maxfd=m_confd;
    while(!m_bStopActivity) {
        fd_set rfds, wfds;
        FD_ZERO(&wfds);
        FD_ZERO(&rfds);
        
        FD_SET(m_confd, &rfds);
        if(inBuf.freecapa()==0)
        	return; // shouldn't even get here
        
        job *pjSender(NULL);
    
        if ( !m_jobs2send.empty())
		{
			pjSender=m_jobs2send.front();
			FD_SET(m_confd, &wfds);
		}
		
        
        ldbg("select con");

        struct timeval tv;
        tv.tv_sec = 90;
        tv.tv_usec = 0;
        int ready = select(maxfd+1, &rfds, &wfds, NULL, &tv);
        
        if(ready == 0)
        {
        	USRDBG(5, "Timeout occurred, apt client disappeared silently?");
        	return;
        }
		else if (ready<0)
		{
			if (EINTR == errno)
				continue;
			
			ldbg("select error in con, errno: " << errno);
			return; // FIXME: good error message?
		}
        
        ldbg("select con back");

        if(FD_ISSET(m_confd, &rfds)) {
            int n=inBuf.sysread(m_confd);
            ldbg("got data: " << n <<", inbuf size: "<< inBuf.size());
            if(n<=0) // error, incoming junk overflow or closed connection
            {
              if(n==-EAGAIN)
                continue;
              else
                return;
            }
        }

        // split new data into requests
        while(inBuf.size()>0) {
        	MYTRY
        	{
				if(!m_pTmpHead)
					m_pTmpHead = new header();
				if(!m_pTmpHead)
					return; // no resources? whatever
				
				int nConsumed=m_pTmpHead->LoadFromBuf(inBuf.rptr(), inBuf.size());
				ldbg("header parsed how? " << nConsumed);
				if(nConsumed==0)
				{ // Either not enough data received, or buffer full; make space and retry
					inBuf.move();
					break;
				}
				if(nConsumed<0)
				{
					 // also must be identified before
					if( acfg::forwardsoap && ! m_sClientHost.empty() )
					{
						SoapPassThrough pt;
						if(pt.TrySoapPassThrough(inBuf, m_confd))
							return;
					}

					ldbg("Bad request: " << inBuf.rptr() );
					return;
				}
				if (nConsumed>0)
					inBuf.drop(nConsumed);
				
				if (m_sClientHost.empty()) // may come from wrapper... MUST identify itself
				{
					if(m_pTmpHead->h[header::XORIG] && *(m_pTmpHead->h[header::XORIG]))
					{
						m_sClientHost=m_pTmpHead->h[header::XORIG];
						continue; // OK
					}
					else
						return;
				}

				ldbg("Parsed REQUEST:" << m_pTmpHead->frontLine);
				ldbg("Rest: " << inBuf.size());
				AddNewJob(m_pTmpHead);
				m_pTmpHead=NULL; // owned by job now
			}
        	MYCATCH(bad_alloc&)
        	{
        		return;
        	}
        }
        
        if(inBuf.freecapa()==0)
        	return; // cannot happen unless being attacked

		if(FD_ISSET(m_confd, &wfds) && pjSender)
		{
			switch(pjSender->SendData(m_confd, m_jobs2send.size()))
			{
				case(job::R_DISCON):
				{
					ldbg("Disconnect advise received, stopping connection");
					return;
				}
				case(job::R_DONE):
				{
					m_jobs2send.pop_front(); 						
					delete pjSender;
					pjSender=NULL;
		
					ldbg("Remaining jobs to send: " << m_jobs2send.size());
					break;
				}
				case(job::R_AGAIN):
						break;
				default:
					break;
			}
        }
	}
}

void * _StartDownloader(void *pVoidDler)
{
	static_cast<dlcon*>(pVoidDler) -> WorkLoop();
	return NULL;
}

bool con::SetupDownloader(const char *pszOrigin)
{
	if (m_pDlClient)
		return true;

	MYTRY
	{
		if(acfg::exporigin)
		{
			string sXff;
			if(pszOrigin)
			{
				sXff = *pszOrigin;
				sXff += ", ";
			}
			sXff+=m_sClientHost;
			m_pDlClient=new dlcon(false, &sXff);
		}
		else
			m_pDlClient=new dlcon(false);
		
		if(!m_pDlClient)
			return false;
	}
	MYCATCH(bad_alloc&)
	{
		return false;
	}

	if (0==pthread_create(&m_dlerthr, NULL, _StartDownloader,
			(void *)m_pDlClient))
	{
		return true;
	}
	delete m_pDlClient;
	m_pDlClient=NULL;
	return false;
}

void con::__tlogstuff::write()
{
	if (sumIn>0)
		aclog::transfer('I', sumIn, client.c_str(), file.c_str());
	
	if(sumOut>0)
		aclog::transfer(bFileIsError ? 'E' : 'O', sumOut, client.c_str(), file.c_str());
}

void con::LogDataCounts(const MYSTD::string & sFile, const char *xff, off_t nNewIn,
		off_t nNewOut, bool bFileIsError)
{
	LOGSTART("con::LogDataCounts");
	string sClient;
	if (!acfg::logxff || !xff) // not to be logged or not available
		sClient=m_sClientHost;
	else if (xff)
	{
		sClient=xff;
		trimString(sClient);
		string::size_type pos = sClient.find_last_of(SPACECHARS);
		if (pos!=stmiss)
			sClient.erase(0, pos+1);
	}
	if(sFile != logstuff.file || sClient != logstuff.client)
	{
		logstuff.write();
		logstuff.reset(sFile, sClient, bFileIsError);
	}
	LOG("heh? state now: " << logstuff.sumIn << " " << logstuff.sumOut);
	logstuff.sumIn+=nNewIn;
	logstuff.sumOut+=nNewOut;

}

void con::__tlogstuff::reset(const MYSTD::string &pNewFile, const MYSTD::string &pNewClient, bool bIsError)
{
	bFileIsError=bIsError;
	file=pNewFile;
	client=pNewClient;
	sumIn=0;
	sumOut=0;
}

job* con::AddNewJob(header *pHead)
				{
					job * j = new job(pHead, this);

					j->PrepareDownload();
					m_jobs2send.push_back(j);
#ifdef DEBUG
					m_nProcessedJobs++;
#endif
					return j;

}
