/*
 * 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 "NotificationListener/MessageHandler.h"
#include "NotificationListener/NotificationMessage.h"
#include "NotificationListener/SessionInfo.h"
#include "NotificationListener/DMSessionRequestCommand.h"
#include "NotificationListener/BootstrapMessage.h"
#include "NotificationListener/WSPPushDecoder.h"
#include "NotificationListener/ProcessBootstrapCommand.h"

#include "lock.h"
#include "Event.h"
#include "daemon/ProfileComponentsHolder.h"
#include "executionqueue/IExecutionQueue.h"
#include "DeviceAdapter/IDeviceAdapter.h"

#include "common/Utils.h"

#include "Logger/LoggerMacroses.h"

#include <cmath>

using namespace NS_DM_Client;
using namespace NS_DM_Client::NS_NotificationListener;


MessageHandler::MessageHandler(ProfileComponentsHolder& pch, bool checkNotificationDiggest, bool plainBootstrap, 
							   bool cipherHeaderPresent, NS_Logging::Logger& logger)
: m_pch(pch),
m_logger(logger),
m_checkNotificationDiggest(checkNotificationDiggest),
m_plainBootstrap(plainBootstrap),
m_cipherHeaderPresent(cipherHeaderPresent)
{
}

MessageHandler::~MessageHandler()
{
}


void    MessageHandler::Write(const void* buf, size_t size)
{
    message_t msg;
    msg.resize(size);
    memcpy(&msg[0], buf, msg.size());

    add(msg);
}


void    MessageHandler::add(message_t& msg)
{
    NS_Common::Lock lock(m_csMsgHandler);
    m_messages.resize(m_messages.size() + 1);
    m_messages.back().swap(msg);

    LOG_DEBUG_(m_logger, "Fire NEW_MSG signal. ");
    m_newMsg.signal(e_NewMsg);
}


bool    MessageHandler::get(message_t& msg)
{
    NS_Common::Lock lock(m_csMsgHandler);
    if (m_messages.empty())
    {
        return false;
    }
    m_messages.back().swap(msg);
    m_messages.resize(m_messages.size() - 1);

    return true;
}


void    MessageHandler::Start()
{
    while(true)
    {
        Signals signal = static_cast<Signals>(m_newMsg.wait());
		LOG_DEBUG_(m_logger, "MessageHandler:: received signal=%d. ", signal);

        if (signal == e_Stop)
        {
            LOG_DEBUG_(m_logger, "MessageHandler:: STOP signal received. ");
            break;
        }

        if (signal == e_NewMsg)
        {
            LOG_DEBUG_(m_logger, "MessageHandler:: NEW_MSG signal received. ");
            message_t msg;
            if (!get(msg))
            {
                LOG_ERROR_(m_logger, "Failed get message. ");
                continue;
            }

			if (msg.empty())
			{
				LWARN("Empty message");
				continue;
			}

			String dump;
			const size_t dumpSize = __MIN(msg.size(), 32);
			LDUMP("MSG (%d bytes): %s %s", msg.size(), Utils::ToAsciiHex(dump, &msg[0], dumpSize), (dumpSize < msg.size()) ? "..." : "");

            WSPPushDecoder pd;
            if (pd.SetPDU(&msg[0], msg.size(), true))
            {
                NS_WSP::EnumContentTypes ct = pd.GetWNContentType_MIMEType();
                if (ct == NS_WSP::e_CT_NotWellKnown)
				{
					String ctRef = pd.GetNotWNContentTypeRef();
					const String wmfBootstrapCT = "application/vnd.wmf.bootstrap";
					char c = 0;
					
					// Sample content type:
					// Content-Type: application/vnd.wmf.bootstrap;SEC=0;MAC=0A85FF86325D34556EC9E1292AF71BDB5F146F42
					if (ctRef.length() >= wmfBootstrapCT.length() 
						&& strncmp(ctRef.c_str(), wmfBootstrapCT.c_str(), wmfBootstrapCT.length()) == 0
						&& (c = ctRef.c_str()[wmfBootstrapCT.length()], c == ';' || c == '\0')
						)
					{
						ct = NS_WSP::e_application_vnd_wmf_bootstrap;
					}
					LOG_DUMP_(m_logger, "Content-Type value '%s' ", ctRef.c_str());
				}
				
				LOG_DUMP_(m_logger, "Content-Type (hex code) %Xh. ", ct);

				int dataOffset = 0;
				int dataSize = 0;
				if (!pd.GetDataInfo(dataOffset, dataSize))
				{
					LOG_ERROR_(m_logger, "Failed get data info from the message. ");
				}
				else 
                switch (ct)
                {
                case NS_WSP::e_application_vnd_syncml_notification:
                    {
                    SessionInfo sessInfo;
                    if (checkNotificationMessage(&msg[dataOffset], dataSize, sessInfo))
                    {
                        LOG_DUMP_(m_logger, "NotificationMsg: Server='%s', Session='%s'. ", sessInfo.serverID.c_str(), sessInfo.sessionID.c_str());
                        DMSessionRequestCommand* cmd = new DMSessionRequestCommand(sessInfo, m_checkNotificationDiggest, m_pch, m_logger);
                        if (!cmd)
                        {
                            LOG_ERROR_(m_logger, "Failed to allocate DMSessionRequestCommand. ");
                            continue;
                        }

                        IExecutionQueue* exq = m_pch.GetExecutionQueue();
                        if (!exq->Add(*cmd))
                        {
                            LOG_ERROR_(m_logger, "Failed to add DMSessionRequestCommand command to Q. ");
                        }
                    }
					else
					{
						LOG_ERROR_(m_logger, "Failed to process Notification message. ");
					}
                    break;
                    }

                case NS_WSP::e_application_vnd_syncml_dm_wbxml:
				case NS_WSP::e_application_vnd_wmf_bootstrap:
                    {
                    buffer_t bsdata;
                    if (checkBootstrapMessage(&msg[dataOffset], dataSize, bsdata))
                    {
                        ProcessBootstrapCommand* cmd = new ProcessBootstrapCommand(bsdata, m_pch);
                        if (!cmd)
                        {
                            LOG_ERROR_(m_logger, "Failed to allocate ProcessBootstrapCommand. ");
                            continue;
                        }

                        IExecutionQueue* exq = m_pch.GetExecutionQueue();
                        if (!exq->Add(*cmd))
                        {
                            LOG_ERROR_(m_logger, "Failed to add ProcessBootstrapCommand command to Q. ");
                        }
                    }
					else
					{
						LOG_ERROR_(m_logger, "Failed to pre-process Bootstrap message. ");
					}
                    break;
                    }

                default:
                    LOG_WARNING_(m_logger, "Unsupported message Content-Type: %d. ", ct);
                    break;
                }
            }
            else
            {
                LOG_ERROR_(m_logger, "Not valid Push message. ");
            }
        }
    }
}

void    MessageHandler::Stop()
{
    LOG_DEBUG_(m_logger, "Fire STOP signal. ");
    m_newMsg.signal(e_Stop);
}


bool    MessageHandler::checkNotificationMessage(const char* buf, size_t size, SessionInfo& sessInfo) const
{

    NotificationMessage notifMsg;
    if (!notifMsg.SetMessage(buf, size))
    {
        LOG_ERROR_(m_logger, "NotificationMessage::SetMessage failed. ");
        return false;
    }

	const int msgVersion = notifMsg.GetVersion();
    if (NotificationMessage::VERSION != msgVersion)
    {
        LOG_ERROR_(m_logger, "Unsupported version %d. ", msgVersion);
        return false;
    }

    if (!notifMsg.GetSessionID(sessInfo.sessionID))
    {
        LOG_ERROR_(m_logger, "failed to get session ID. ");
        return false;
    }

    sessInfo.serverID = notifMsg.GetServerID();
    sessInfo.serverInitiated = notifMsg.GetInitiator() == NotificationMessage::e_InitiatorServer;
    sessInfo.uiMode = notifMsg.GetUIMode();

    LOG_DEBUG_(m_logger, "bRes=%d. ", true);

    return true;
}


bool    MessageHandler::checkBootstrapMessage(const char* buf, size_t size, buffer_t& bsdata) const
{
	BootstrapMessage bm(m_logger);
	if (!m_plainBootstrap)
	{
		buffer_t emsk;
		size_t emskSize = 256;
		emsk.resize(emskSize);

		if (!m_pch.GetDeviceAdapter()->GetEMSK(&emsk[0], emskSize))
		{
			LOG_ERROR_(m_logger, "failed to get EMSK");
			return false;

		}
		emsk.resize(emskSize);

		bm.SetEMSK(&emsk[0], emsk.size());
		LOG_DEBUG_(m_logger, "EMSK size=%d. ", emsk.size());

	}

    buffer_t decoded;
    bool brc = bm.Decode(buf, size, m_plainBootstrap, m_cipherHeaderPresent, bsdata);

    LOG_DEBUG_(m_logger, "Decoding bRes=%d", brc);

    return brc;
}

