#include "FileZilla.h"
#include "iothread.h"

const wxEventType fzEVT_IOTHREAD = wxNewEventType();

CIOThreadEvent::CIOThreadEvent(int id /*=wxID_ANY*/)
	: wxEvent(id, fzEVT_IOTHREAD)
{
}

wxEvent* CIOThreadEvent::Clone() const
{
	return new CIOThreadEvent(GetId());
}

CIOThread::CIOThread()
	: wxThreadEx(wxTHREAD_JOINABLE), m_evtHandler(0), m_pFile(0), m_condition(m_mutex)
{
	for (unsigned int i = 0; i < BUFFERCOUNT; i++)
	{
		m_buffers[i] = new char[BUFFERSIZE];
		m_bufferLens[i] = 0;
	}

	m_error = false;
	m_running = false;
	m_appWaiting = false;
	m_threadWaiting = false;
	m_destroyed = false;
	m_wasCarriageReturn = false;
}

CIOThread::~CIOThread()
{
	if (m_pFile)
		delete m_pFile;

	for (unsigned int i = 0; i < BUFFERCOUNT; i++)
		delete [] m_buffers[i];
}

bool CIOThread::Create(wxFile* pFile, bool read, bool binary)
{
	wxASSERT(pFile);
	if (m_pFile)
		delete m_pFile;
	m_pFile = pFile;
	m_read = read;
	m_binary = binary;

	if (read)
	{
		m_curAppBuf = BUFFERCOUNT - 1;
		m_curThreadBuf = 0;
	}
	else
	{
		m_curAppBuf = -1;
		m_curThreadBuf = 0;
	}

	m_running = true;
	wxThreadEx::Create();
	wxThreadEx::Run();

	return true;
}

wxThreadEx::ExitCode CIOThread::Entry()
{
	if (m_read)
	{
		while (m_running)
		{
			int len = ReadFromFile(m_buffers[m_curThreadBuf], BUFFERSIZE);

			wxMutexLocker locker(m_mutex);
			
			if (m_appWaiting)
			{
				if (!m_evtHandler)
				{
					m_running = false;
					break;
				}
				m_appWaiting = false;
				CIOThreadEvent evt;
				wxPostEvent(m_evtHandler, evt);
			}

			if (len == wxInvalidOffset)
			{
				m_error = true;
				m_running = false;
				break;
			}
			
			m_bufferLens[m_curThreadBuf] = len;

			if (!len)
			{
				m_running = false;
				break;
			}

			++m_curThreadBuf %= BUFFERCOUNT;
			if (m_curThreadBuf == m_curAppBuf)
			{
				if (!m_running)
					break;

				m_threadWaiting = true;
				if (m_running)
					m_condition.Wait();
			}
		}
	}
	else
	{
		m_mutex.Lock();
		if (!m_running)
		{
			m_mutex.Unlock();
			return 0;
		}
		if (m_curAppBuf == -1)
		{
			m_threadWaiting = true;
			m_condition.Wait();
		}

		if (!m_running)
		{
			m_mutex.Unlock();
			return 0;
		}
		m_mutex.Unlock();

		while (true)
		{
			m_mutex.Lock();
			while (m_curThreadBuf == m_curAppBuf)
			{
				if (!m_running)
				{
					m_mutex.Unlock();
					return 0;
				}
				m_threadWaiting = true;
				m_condition.Wait();
			}
			m_mutex.Unlock();

			bool writeSuccessful = WriteToFile(m_buffers[m_curThreadBuf], m_bufferLens[m_curThreadBuf]);

			wxMutexLocker locker(m_mutex);

			if (!writeSuccessful)
			{
				m_error = true;
				m_running = false;
			}
			
			if (m_appWaiting)
			{
				if (!m_evtHandler)
				{
					m_running = false;
					break;
				}
				m_appWaiting = false;
				CIOThreadEvent evt;
				wxPostEvent(m_evtHandler, evt);
			}

			if (m_error)
				break;
			
			++m_curThreadBuf %= BUFFERCOUNT;
		}		
	}

	return 0;
}

int CIOThread::GetNextWriteBuffer(char** pBuffer, int len /*=BUFFERSIZE*/)
{
	wxASSERT(!m_destroyed);

	wxMutexLocker locker(m_mutex);

	if (m_error)
		return IO_Error;

	if (m_curAppBuf == -1)
	{
		m_curAppBuf = 0;
		*pBuffer = m_buffers[0];
		return IO_Success;
	}

	m_bufferLens[m_curAppBuf] = len;

	int newBuf = (m_curAppBuf + 1) % BUFFERCOUNT;
	if (newBuf == m_curThreadBuf)
	{
		m_appWaiting = true;
		return IO_Again;
	}

	if (m_threadWaiting)
	{
		m_condition.Signal();
		m_threadWaiting = false;
	}

	m_curAppBuf = newBuf;
	*pBuffer = m_buffers[newBuf];

	return IO_Success;
}

bool CIOThread::Finalize(int len)
{
	wxASSERT(m_pFile);

	if (m_destroyed)
		return true;
	
	Destroy();

	if (m_curAppBuf == -1)
		return true;

	if (m_error)
		return false;

	if (!len)
		return true;

	if (!WriteToFile(m_buffers[m_curAppBuf], len))
		return false;

#ifndef __WXMSW__
	if (!m_binary && m_wasCarriageReturn)
	{
		const char CR = '\r';
		if (m_pFile->Write(&CR, 1) != 1)
			return false;
	}
#endif
	return true;
}

int CIOThread::GetNextReadBuffer(char** pBuffer)
{
	wxASSERT(!m_destroyed);
	wxASSERT(m_read);

	int newBuf = (m_curAppBuf + 1) % BUFFERCOUNT;

	wxMutexLocker locker(m_mutex);
	
	if (newBuf == m_curThreadBuf)
	{
		if (m_error)
			return IO_Error;
		else if (!m_running)
			return IO_Success;
		else
		{
			m_appWaiting = true;
			return IO_Again;
		}
	}
	
	if (m_threadWaiting)
	{
		m_condition.Signal();
		m_threadWaiting = false;
	}

	*pBuffer = m_buffers[newBuf];
	m_curAppBuf = newBuf;

	return m_bufferLens[newBuf];
}

void CIOThread::Destroy()
{
	if (m_destroyed)
		return;
	m_destroyed = true;

	m_mutex.Lock();

	m_running = false;
	if (m_threadWaiting)
	{
		m_threadWaiting = false;
		m_condition.Signal();
	}
	m_mutex.Unlock();

	Wait();
}

int CIOThread::ReadFromFile(char* pBuffer, int maxLen)
{
	// In binary mode, no conversion has to be done.
	// Also, under Windows the native newline format is already identical
	// to the newline format of the FTP protocol
#ifndef __WXMSW__
	if (m_binary)
#endif
		return m_pFile->Read(pBuffer, maxLen);

#ifndef __WXMSW__

	// In the worst case, length will doubled: If reading
	// only LFs from the file
	const int readLen = maxLen / 2;

	char* r = pBuffer + readLen;
	int len = m_pFile->Read(r, readLen);
	if (!len || len == wxInvalidOffset)
		return len;

	const char* const end = r + len;
	char* w = pBuffer;

	// Convert all stand-alone LFs into CRLF pairs.
	while (r != end)
	{
		char c = *r++;
		if (c == '\n')
		{
			if (!m_wasCarriageReturn)
				*w++ = '\r';
			m_wasCarriageReturn = false;
		}
		else if (c == '\r')
			m_wasCarriageReturn = true;
		else
			m_wasCarriageReturn = false;

		*w++ = c;
	}

	return w - pBuffer;
#endif
}

bool CIOThread::WriteToFile(char* pBuffer, int len)
{
	// In binary mode, no conversion has to be done.
	// Also, under Windows the native newline format is already identical
	// to the newline format of the FTP protocol
#ifndef __WXMSW__
	if (m_binary)
	{
#endif
		int written = m_pFile->Write(pBuffer, len);
		return written == len;
#ifndef __WXMSW__
	}
	else
	{
		// On all CRLF pairs, omit the CR. Don't harm stand-alone CRs
		// I assume disk access is buffered, otherwise the 1 byte writes are 
		// going to hurt performance.
		const char CR = '\r';
		const char* const end = pBuffer + len;
		for (char* r = pBuffer; r != end; r++)
		{
			char c = *r;
			if (c == '\r')
				m_wasCarriageReturn = true;
			else if (c == '\n')
			{
				m_wasCarriageReturn = false;
				if (m_pFile->Write(&c, 1) != 1)
					return false;
			}
			else
			{
				if (m_wasCarriageReturn)
				{
					m_wasCarriageReturn = false;
					if (m_pFile->Write(&CR, 1) != 1)
						return false;
				}
				
				if (m_pFile->Write(&c, 1) != 1)
					return false;
			}
		}
		return true;
	}
#endif
}


syntax highlighted by Code2HTML, v. 0.9.1