/*
* Copyright 2009 Funambol, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/* $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(INVALID_HANDLE_VALUE), 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)
	{	
		SECURITY_ATTRIBUTES sa;
		sa.lpSecurityDescriptor = (PSECURITY_DESCRIPTOR)malloc(SECURITY_DESCRIPTOR_MIN_LENGTH);
		InitializeSecurityDescriptor(sa.lpSecurityDescriptor, SECURITY_DESCRIPTOR_REVISION);
		// ACL is set as NULL in order to allow all access to the object.
		SetSecurityDescriptorDacl(sa.lpSecurityDescriptor, TRUE, NULL, FALSE);
		sa.nLength = sizeof(sa);
		sa.bInheritHandle = TRUE;
		
		m_fifo = CreateNamedPipeA(m_name.c_str(), PIPE_ACCESS_DUPLEX, pipeMode, 3, 0, 0, INFINITE, &sa);
		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);
        
		const DWORD lastErrorCode = GetLastError();
		if ((!readRes) && ((lastErrorCode == ERROR_IO_PENDING) || (lastErrorCode == ERROR_SUCCESS) || (lastErrorCode == ERROR_MORE_DATA)))
		{
			readRes = TRUE;
		}

		if (!readRes)
		{
			if (lastErrorCode == 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 = %d", m_name.c_str(), buffer.Size(), r, lastErrorCode);
		}

		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); 

		const DWORD lastErrorCode = ::GetLastError();
		if ((!writeRes) && ((lastErrorCode == ERROR_IO_PENDING) || (lastErrorCode == ERROR_SUCCESS)))
		{
			writeRes = TRUE;
		}
	
		if (!writeRes)
		{
			if (lastErrorCode == 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 = %d", m_name.c_str(), length, w, lastErrorCode);
		}

		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
		SECURITY_ATTRIBUTES sa;
		sa.lpSecurityDescriptor = (PSECURITY_DESCRIPTOR)malloc(SECURITY_DESCRIPTOR_MIN_LENGTH);
		InitializeSecurityDescriptor(sa.lpSecurityDescriptor, SECURITY_DESCRIPTOR_REVISION);
		// ACL is set as NULL in order to allow all access to the object.
		SetSecurityDescriptorDacl(sa.lpSecurityDescriptor, TRUE, NULL, FALSE);
		sa.nLength = sizeof(sa);
		sa.bInheritHandle = TRUE;

		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, &sa, 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);
}
//------------------------------------------------------------------------------------------------------
}