/*
 * Copyright (C) 2009 - 2010 Funambol, Inc.
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License version 3 as published by
 * the Free Software Foundation with the addition of the following permission
 * added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED
 * WORK IN WHICH THE COPYRIGHT IS OWNED BY FUNAMBOL, FUNAMBOL DISCLAIMS THE
 * WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
 *
 * This program 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 General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program; if not, see http://www.gnu.org/licenses or write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301 USA.
 *
 * You can contact Funambol, Inc. headquarters at 643 Bair Island Road, Suite
 * 305, Redwood City, CA 94063, USA, or at email address info@funambol.com.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU Affero General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU Affero General Public License
 * version 3, these Appropriate Legal Notices must retain the display of the
 * "Powered by Funambol" logo. If the display of the logo is not reasonably
 * feasible for technical reasons, the Appropriate Legal Notices must display
 * the words "Powered by Funambol".
 */

/* $Id$ */

#include "FIFOWrapper.h"
#include "Logger/Logger.h"

namespace NS_DM_Client
{

const char* const c_FifoNamePreffix = "\\\\.\\pipe\\";
const char* const c_FifoWrapperLog = "FifoWrapper";

//------------------------------------------------------------------------------------------------------
WindowsFIFOWrapper::WindowsFIFOWrapper(bool serverSide)
	: m_fifo(0), m_opened(false), m_serverSide(serverSide)
{
}
//------------------------------------------------------------------------------------------------------
StatusCode WindowsFIFOWrapper::Open(const String& name, bool readMode, bool blockedMode, bool immediateOpen)
{
    LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog),
        "ENTER >> WindowsFIFOWrapper::Open. Name: %s, readMode: %s, blockedMode: %s", name.c_str(), readMode ? "true" : "false", blockedMode ? "true" : "false");

	m_name = (String)c_FifoNamePreffix + name;
	DWORD pipeMode = PIPE_TYPE_MESSAGE;
	pipeMode |= blockedMode ? PIPE_WAIT : PIPE_NOWAIT;
	
	if (m_serverSide)
	{	
		m_fifo = CreateNamedPipeA(m_name.c_str(), PIPE_ACCESS_DUPLEX, pipeMode, 3, 0, 0, INFINITE, NULL);
		if (INVALID_HANDLE_VALUE == m_fifo)
		{
			LOG_ERROR_(NS_Logging::GetLogger(c_FifoWrapperLog), "can't create fifo on next path: %s, error number: %d", m_name.c_str(), GetLastError());
			LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "LEAVE << WindowsFIFOWrapper::Open. Return status: %d", e_Failed);		
			m_fifo = 0;
			m_opened = false;
			return e_Failed;
		}
	}
    
	m_readMode = readMode;
    m_blockedMode = blockedMode;

    if (immediateOpen)
    {
        StatusCode openRes = open();
        if (openRes != e_Ok)
        {
            LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "LEAVE << WindowsFIFOWrapper::Open. Return status: %d", openRes);
            return openRes;
        }
    }

    LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "LEAVE << WindowsFIFOWrapper::Open. Return status: %d", e_Ok);
    return e_Ok;
}
//------------------------------------------------------------------------------------------------------
StatusCode WindowsFIFOWrapper::Read(Buffer& buffer, bool exitIfNoWriter)
{
    LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "ENTER >> WindowsFIFOWrapper::Read. buffer size: %d", buffer.Size());

    if (buffer.Size() <= 0)
    {
        LOG_ERROR_(NS_Logging::GetLogger(c_FifoWrapperLog), "buffer size is 0. Allocate buffer first");
        LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "LEAVE << WindowsFIFOWrapper::Read. Return status: %d", e_Failed);
        return e_Failed;
    }

    BOOL readRes = FALSE;
	DWORD r = -1;

	while(true)
    {
        if (!m_opened)
        {
            StatusCode openRes = open();
            if (openRes != e_Ok)
            {
                LOG_ERROR_(NS_Logging::GetLogger(c_FifoWrapperLog), "failed to open fifo");
                LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "LEAVE << WindowsFIFOWrapper::Read. Return status: %d", openRes);
                return openRes;
            }
        }

		LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "BEFORE read: name: %s, size: %d", m_name.c_str(), buffer.Size());
        readRes = ReadFile(m_fifo, buffer.GetPointer(), buffer.Size(), &r, NULL);
        
		if ((!readRes) && ((::GetLastError() == ERROR_IO_PENDING) || (::GetLastError() == ERROR_SUCCESS) || (::GetLastError() == ERROR_MORE_DATA)))
		{
			readRes = TRUE;
		}

		if (!readRes)
		{
			if (::GetLastError() == ERROR_BROKEN_PIPE)
			{
				LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "ReadFailed: name: %s, size: %d, r=%d. Because broken pipe", m_name.c_str(), buffer.Size(), r);
			}
			LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "ReadFailed: name: %s, size: %d, r=%d. GetLastError = ", m_name.c_str(), buffer.Size(), r, ::GetLastError());
		}

		LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "AFTER read: name: %s, size: %d, r=%d", m_name.c_str(), buffer.Size(), r);

		if (readRes && (r > 0))
        {
            if (size_t(r) < buffer.Size())
            {
                LOG_WARNING_(NS_Logging::GetLogger(c_FifoWrapperLog), "read data size is smaller that requested. Resize buffer with smaller size. Requested: %d, readed: %d", buffer.Size(), r);
                buffer.Allocate(size_t(r));
            }
            LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "LEAVE << WindowsFIFOWrapper::Read. Return status: %d", e_Ok);
            return e_Ok;
        }
        else if (readRes && (r == 0))
        {
            if (m_blockedMode && (!exitIfNoWriter))
            {
                Close();
            }
            else
            {
                LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "LEAVE << WindowsFIFOWrapper::Read. Return status: %d", e_WriteInstanceNotAlive);
                return e_WriteInstanceNotAlive;
            }
        }
        else // (r < 0) || (readRes == FALSE)
        {
            LOG_ERROR_(NS_Logging::GetLogger(c_FifoWrapperLog), "failed to read from fifo");
            LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "LEAVE << WindowsFIFOWrapper::Read. Return status: %d", e_Failed);
            return e_Failed;
        }
    }

    LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "LEAVE << WindowsFIFOWrapper::Read. Return status: %d", e_Ok);
    return e_Ok;
}
//------------------------------------------------------------------------------------------------------
StatusCode WindowsFIFOWrapper::Write(const void* buffer, size_t length, bool exitIfNoReader)
{
    LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "ENTER >> WindowsFIFOWrapper::Write(void* buffer). Length size: %d", length);

    if (length <= 0)
    {
        LOG_ERROR_(NS_Logging::GetLogger(c_FifoWrapperLog), "buffer size is 0. Allocate buffer first");
        LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "LEAVE << WindowsFIFOWrapper::Write. Return status: %d", e_Failed);
        return e_Failed;
    }

    BOOL writeRes = FALSE;
    DWORD w = -1;
    
    while(true)
    {
        if (!m_opened)
        {   // no opened fifo
            StatusCode openRes = open();
            if (openRes != e_Ok)
            {
                LOG_ERROR_(NS_Logging::GetLogger(c_FifoWrapperLog), "failed to open fifo");
                LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "LEAVE << WindowsFIFOWrapper::Write. Return status: %d", openRes);
                return openRes;
            }
        }

        LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "BEFORE write: name: %s, size: %d", m_name.c_str(), length);
		writeRes = WriteFile(m_fifo, buffer, length, &w, NULL); 

		if ((!writeRes) && ((::GetLastError() == ERROR_IO_PENDING) || (::GetLastError() == ERROR_SUCCESS)))
		{
			writeRes = TRUE;
		}
	
		if (!writeRes)
		{
			if (::GetLastError() == ERROR_BROKEN_PIPE)
			{
				LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "WriteFailed: name: %s, size: %d, w=%d. Because broken pipe", m_name.c_str(), length, w);
			}
			LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "WriteFailed: name: %s, size: %d, w=%d. GetLastError = ", m_name.c_str(), length, w, ::GetLastError());
		}

		LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "AFTER write: name: %s, size: %d, w=%d", m_name.c_str(), length, w);
        if (writeRes && (w > 0))
        {
            if (size_t(w) < length)
            {
                LOG_WARNING_(NS_Logging::GetLogger(c_FifoWrapperLog), "written data size is smaller that requested. Requested: %d, written: %d", length, w);
            }
            LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "LEAVE << WindowsFIFOWrapper::Write. Return status: %d", e_Ok);
            return e_Ok;
        }
        else if (writeRes && (w == 0))
        {
            if (m_blockedMode && (!exitIfNoReader))
            {
                Close();
            }
            else
            {
                LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "LEAVE << WindowsFIFOWrapper::Write. Return status: %d", e_ReadInstanceNotAlive);
                return e_ReadInstanceNotAlive;
            }
        }
        else // (w < 0) || (writeRes == FALSE)
        {
            LOG_ERROR_(NS_Logging::GetLogger(c_FifoWrapperLog), "failed to write to fifo");
            LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "LEAVE << WindowsFIFOWrapper::Write. Return status: %d", e_Failed);
            return e_Failed;
        }
    }
    LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "LEAVE << WindowsFIFOWrapper::Write. Return status: %d", e_Ok);
    return e_Ok;
}
//------------------------------------------------------------------------------------------------------
StatusCode WindowsFIFOWrapper::Write(const Buffer& buffer, bool exitIfNoReader)
{
    LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "ENTER >> WindowsFIFOWrapper::Write(Buffer& buffer). Buffer size: %d", buffer.Size());

    if (buffer.Size() <= 0)
    {
        LOG_ERROR_(NS_Logging::GetLogger(c_FifoWrapperLog), "buffer size is 0. Allocate buffer first");
        LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "LEAVE << WindowsFIFOWrapper::Write. Return status: %d", e_Failed);
        return e_Failed;
    }

    StatusCode writeRes = Write(buffer.GetPointer(), buffer.Size(), exitIfNoReader);

    LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "LEAVE << WindowsFIFOWrapper::Write. Return status: %d", writeRes);
    return writeRes;
}
//------------------------------------------------------------------------------------------------------
StatusCode WindowsFIFOWrapper::Close()
{
    LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "ENTER >> WindowsFIFOWrapper::Close");

	NS_Common::Lock lock(m_stateMutex);

	if (m_fifo != 0)
	{
        if (m_serverSide)
		{
			DisconnectNamedPipe(m_fifo);
		}
		CloseHandle(m_fifo);
	}
    m_fifo = 0;
	m_opened = false;
    LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "LEAVE << WindowsFIFOWrapper::Close. Return status: %d", e_Ok);
    return e_Ok;
}
//------------------------------------------------------------------------------------------------------
void WindowsFIFOWrapper::Release()
{
    LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "ENTER >> WindowsFIFOWrapper::Release");

	if (m_fifo != 0)
    {
        Close();
    }
    LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "LEAVE << WindowsFIFOWrapper::Release");
    delete this;
}
//------------------------------------------------------------------------------------------------------
WindowsFIFOWrapper::~WindowsFIFOWrapper()
{
}
//------------------------------------------------------------------------------------------------------
StatusCode WindowsFIFOWrapper::open()
{
    LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "ENTER >> WindowsFIFOWrapper::open");

    if (m_opened)
    {
        LOG_WARNING_(NS_Logging::GetLogger(c_FifoWrapperLog), "fifo already opened. Reopen");
        Close();
    }

	if (m_serverSide)
	{
		BOOL connectRes = ConnectNamedPipe(m_fifo, NULL);
		if (!connectRes)
		{
			if (GetLastError() == ERROR_PIPE_CONNECTED)
			{
			// all ok
			}
			else if (GetLastError() == ERROR_PIPE_LISTENING)
			{
				LOG_ERROR_(NS_Logging::GetLogger(c_FifoWrapperLog), "can't connect to fifo on next path: %s, no listening connection", m_name.c_str());
				LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "LEAVE << WindowsFIFOWrapper::open. Return status: %d", e_Failed);
				m_opened = false;
				return e_Failed;
			}
			else
			{
				LOG_ERROR_(NS_Logging::GetLogger(c_FifoWrapperLog), "can't connect to fifo on next path: %s", m_name.c_str());
				LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "LEAVE << WindowsFIFOWrapper::open. Return status: %d", e_Failed);
				m_opened = false;
				return e_Failed;
			}
		}		
	}
	else
	{	// client mode
		int try_count = 2;
try_create_again:		
		m_fifo = CreateFileA(m_name.c_str(), m_readMode ? (GENERIC_READ | FILE_WRITE_ATTRIBUTES) : GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, /*dwFlagsAndAttributes*/FILE_ATTRIBUTE_NORMAL , NULL);
		if (m_fifo == INVALID_HANDLE_VALUE)
		{			
			if ((GetLastError() == ERROR_FILE_NOT_FOUND))
			{
				if (!m_blockedMode)
				{
					--try_count;
				}
				
				if (try_count > 0)
				{
					Sleep(1000);
					goto try_create_again;
				}
			}
			
			LOG_ERROR_(NS_Logging::GetLogger(c_FifoWrapperLog), "can't connect to fifo on next path: %s, no listening connection", m_name.c_str());
			LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "LEAVE << WindowsFIFOWrapper::open. Return status: %d", e_Failed);
			m_fifo = 0;
			m_opened = false;
			return e_Failed;
		}
	}
	
	m_opened = true;

    if (m_readMode && (!m_blockedMode))
    {
		DWORD newMode = PIPE_READMODE_MESSAGE | PIPE_WAIT;		
		if (!SetNamedPipeHandleState(m_fifo, &newMode, NULL, NULL))
        {
			LOG_ERROR_(NS_Logging::GetLogger(c_FifoWrapperLog), "can't set new fifo flags");
            Close();
            LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "LEAVE << WindowsFIFOWrapper::open. Return status: %d", e_Failed);
            return e_Failed;
        }
    }

    LOG_DEBUG_(NS_Logging::GetLogger(c_FifoWrapperLog), "LEAVE << WindowsFIFOWrapper::open. Return status: %d", e_Ok);
    return e_Ok;
}
//------------------------------------------------------------------------------------------------------
IFIFOWrapper* CreateFIFOWrapper(bool serverSide)
{
    return new WindowsFIFOWrapper(serverSide);
}
//------------------------------------------------------------------------------------------------------
}