//-----------------------------------------------------------------------------
//
// SerialControllerImpl.cpp
//
// POSIX implementation of a cross-platform serial port
//
// Copyright (c) 2010, Greg Satz <satz@iranger.com>
// All rights reserved.
//
//	SOFTWARE NOTICE AND LICENSE
//
//	This file is part of OpenZWave.
//
//	OpenZWave is free software: you can redistribute it and/or modify
//	it under the terms of the GNU Lesser General Public License as published
//	by the Free Software Foundation, either version 3 of the License,
//	or (at your option) any later version.
//
//	OpenZWave is distributed in the hope that it will be useful,
//	but WITHOUT ANY WARRANTY; without even the implied warranty of
//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//	GNU Lesser General Public License for more details.
//
//	You should have received a copy of the GNU Lesser General Public License
//	along with OpenZWave.  If not, see <http://www.gnu.org/licenses/>.
//
//-----------------------------------------------------------------------------
#include <sys/select.h>
#include <unistd.h>
#include <pthread.h>
#include "Defs.h"
#include "platform/Thread.h"
#include "platform/Event.h"
#include "SerialControllerImpl.h"
#include "platform/Log.h"

#ifdef __ANDROID__
#include "android.h"
#endif

#ifdef __sun
// SunOS doesn't have the cfsetspeed convenience function.
int
cfsetspeed(struct termios *tios, speed_t speed)
{
	return (cfsetispeed(tios, speed) || cfsetospeed(tios, speed));
}
#endif

namespace OpenZWave
{
	namespace Internal
	{
		namespace Platform
		{

//-----------------------------------------------------------------------------
// <SerialControllerImpl::SerialControllerImpl>
// Constructor
//-----------------------------------------------------------------------------
			SerialControllerImpl::SerialControllerImpl(SerialController* _owner) :
					m_owner(_owner), m_hSerialController(-1), m_pThread( NULL)
			{
			}

//-----------------------------------------------------------------------------
// <SerialControllerImpl::~SerialControllerImpl>
// Destructor
//-----------------------------------------------------------------------------
			SerialControllerImpl::~SerialControllerImpl()
			{
				flock(m_hSerialController, LOCK_UN);
				if (m_hSerialController >= 0)
					close(m_hSerialController);
			}

//-----------------------------------------------------------------------------
// <SerialControllerImpl::Open>
// Open the serial port 
//-----------------------------------------------------------------------------
			bool SerialControllerImpl::Open()
			{
				// Try to init the serial port
				if (!Init(1))
				{
					// Failed.  We bail to allow the app a chance to take over, rather than retry
					// automatically.  Automatic retries only occur after a successful init.
					return false;
				}

				// Start the read thread
				m_pThread = new Thread("SerialController");
				m_pThread->Start(SerialReadThreadEntryPoint, this);

				return true;
			}

//-----------------------------------------------------------------------------
// <SerialControllerImpl::Close>
// Close the serial port 
//-----------------------------------------------------------------------------
			void SerialControllerImpl::Close()
			{
				if (m_pThread)
				{
					m_pThread->Stop();
					m_pThread->Release();
					m_pThread = NULL;
				}
				close(m_hSerialController);
				m_hSerialController = -1;
			}

//-----------------------------------------------------------------------------
// <SerialReadThreadEntryPoint>
// Entry point of the thread for receiving data from the serial port
//-----------------------------------------------------------------------------
			void SerialControllerImpl::SerialReadThreadEntryPoint(Event* _exitEvent, void* _context)
			{
				SerialControllerImpl* impl = (SerialControllerImpl*) _context;
				if (impl)
				{
					impl->ReadThreadProc(_exitEvent);
				}
			}

//-----------------------------------------------------------------------------
// <SerialControllerImpl::ReadThreadProc>
// Handle receiving data
//-----------------------------------------------------------------------------
			void SerialControllerImpl::ReadThreadProc(Event* _exitEvent)
			{
				uint32 attempts = 0;
				while (!_exitEvent->IsSignalled())
				{
					// Init must have been called successfully during Open, so we
					// don't do it again until the end of the loop
					if (-1 != m_hSerialController)
					{
						// Enter read loop.  Call will only return if
						// an exit is requested or an error occurs
						Read(_exitEvent);

						// Reset the attempts, so we get a rapid retry for temporary errors
						attempts = 0;
					}

					if (attempts < 25)
					{
						// Retry every 5 seconds for the first two minutes...
						if (Wait::Single(_exitEvent, 5000) >= 0)
						{
							// Exit signalled.
							break;
						}
					}
					else
					{
						// ...retry every 30 seconds after that
						if (Wait::Single(_exitEvent, 30000) >= 0)
						{
							// Exit signalled.
							break;
						}
					}

					Init(++attempts);
				}
			}

//-----------------------------------------------------------------------------
// <SerialControllerImpl::Init>
// Initialize the serial port
//-----------------------------------------------------------------------------
			bool SerialControllerImpl::Init(uint32 const _attempts)
			{

				string device = m_owner->m_serialControllerName;

				Log::Write(LogLevel_Info, "Trying to open serial port %s (attempt %d)", device.c_str(), _attempts);

#ifdef __NetBSD__
				m_hSerialController = open( device.c_str(), O_RDWR | O_NOCTTY | O_NONBLOCK);
#else
				m_hSerialController = open(device.c_str(), O_RDWR | O_NOCTTY, 0);
#endif

				if (-1 == m_hSerialController)
				{
					//Error
					Log::Write(LogLevel_Error, "ERROR: Cannot open serial port %s. Error code %d", device.c_str(), errno);
					goto SerialOpenFailure;
				}

				if (flock(m_hSerialController, LOCK_EX | LOCK_NB) != 0)
				{
					Log::Write(LogLevel_Error, "ERROR: Cannot get exclusive lock for serial port %s. Error code %d", device.c_str(), errno);
					goto SerialOpenFailure;
				}

				int bits;
				bits = 0;
				ioctl(m_hSerialController, TIOCMSET, &bits);

				// Configure the serial device parameters
				// Build on the current configuration
				struct termios tios;

				bzero(&tios, sizeof(tios));
				tcgetattr(m_hSerialController, &tios);
				switch (m_owner->m_parity)
				{
					case SerialController::Parity_None:
						tios.c_iflag = IGNPAR;
						break;
					case SerialController::Parity_Odd:
						tios.c_iflag = INPCK;
						tios.c_cflag = PARENB | PARODD;
						break;
					default:
						Log::Write(LogLevel_Error, "ERROR: Parity not supported");
						goto SerialOpenFailure;
				}
				switch (m_owner->m_stopBits)
				{
					case SerialController::StopBits_One:
						break;		// default
					case SerialController::StopBits_Two:
						tios.c_cflag |= CSTOPB;
						break;
					default:
						Log::Write(LogLevel_Error, "ERROR: Stopbits not supported");
						goto SerialOpenFailure;
				}
				tios.c_iflag |= IGNBRK;
				tios.c_cflag |= CS8 | CREAD | CLOCAL;
				tios.c_oflag = 0;
				tios.c_lflag = 0;
				for (int i = 0; i < NCCS; i++)
					tios.c_cc[i] = 0;
				tios.c_cc[VMIN] = 0;
				tios.c_cc[VTIME] = 1;
				switch (m_owner->m_baud)
				{
					case 300:
						cfsetspeed(&tios, B300);
						break;
					case 1200:
						cfsetspeed(&tios, B1200);
						break;
					case 2400:
						cfsetspeed(&tios, B2400);
						break;
					case 4800:
						cfsetspeed(&tios, B4800);
						break;
					case 9600:
						cfsetspeed(&tios, B9600);
						break;
					case 19200:
						cfsetspeed(&tios, B19200);
						break;
					case 38400:
						cfsetspeed(&tios, B38400);
						break;
					case 57600:
						cfsetspeed(&tios, B57600);
						break;
#ifdef DARWIN
						case 76800:
						cfsetspeed( &tios, B76800 );
						break;
#endif
					case 115200:
						cfsetspeed(&tios, B115200);
						break;
					case 230400:
						cfsetspeed(&tios, B230400);
						break;
					default:
						Log::Write(LogLevel_Error, "Baud rate not supported");
						goto SerialOpenFailure;
				}
				if (tcsetattr(m_hSerialController, TCSANOW, &tios) == -1)
				{
					// Error.  Clean up and exit
					Log::Write(LogLevel_Error, "ERROR: Failed to set serial port parameters");
					goto SerialOpenFailure;
				}

				tcflush(m_hSerialController, TCIOFLUSH);

				// Open successful
				Log::Write(LogLevel_Info, "Serial port %s opened (attempt %d)", device.c_str(), _attempts);
				return true;

				SerialOpenFailure: Log::Write(LogLevel_Error, "ERROR: Failed to open serial port %s", device.c_str());
				if (m_hSerialController >= 0)
				{
					close(m_hSerialController);
					m_hSerialController = -1;
				}
				return false;
			}

//-----------------------------------------------------------------------------
// <SerialControllerImpl::Read>
// Read data from the serial port
//-----------------------------------------------------------------------------
			void SerialControllerImpl::Read(Event* _exitEvent)
			{
				uint8 buffer[256];

				while (!_exitEvent->IsSignalled())
				{
					int32 bytesRead;
					int err;

					do
					{
						bytesRead = read(m_hSerialController, buffer, sizeof(buffer));
						if (bytesRead > 0)
							m_owner->Put(buffer, bytesRead);
					} while (bytesRead > 0);

					do
					{
						struct timeval *whenp;
						fd_set rds, eds;
						int oldstate;

						FD_ZERO(&rds);
						FD_SET(m_hSerialController, &rds);
						FD_ZERO(&eds);
						FD_SET(m_hSerialController, &eds);
						whenp = NULL;

						pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &oldstate);
						err = select(m_hSerialController + 1, &rds, NULL, &eds, whenp);
						pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate);
					} while (err <= 0);
				}
			}

//-----------------------------------------------------------------------------
// <SerialControllerImpl::Write>
// Send data to the serial port
//-----------------------------------------------------------------------------
			uint32 SerialControllerImpl::Write(uint8* _buffer, uint32 _length)
			{
				if (-1 == m_hSerialController)
				{
					//Error
					Log::Write(LogLevel_Error, "ERROR: Serial port must be opened before writing");
					return 0;
				}

				// Write the data
				uint32 bytesWritten;
				bytesWritten = write(m_hSerialController, _buffer, _length);
				return bytesWritten;
			}
		} // namespace Platform
	} // namespace Internal
} // namespace OpenZWave
