/*
 * 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 "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 "daemon/ProfileTypes.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, '\0');
    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;
                    memset(&sessInfo, 0, sizeof(SessionInfo));
                    if (checkNotificationMessage(&msg[dataOffset], dataSize, sessInfo))
                    {
                        LOG_DUMP_(m_logger, "NotificationMsg: Server='%s', Session='%s', server initiated=%d, UI mode=%d. ", 
                            sessInfo.serverID.c_str(), sessInfo.sessionID.c_str(), sessInfo.serverInitiated, sessInfo.uiMode);
                        DMSessionRequestCommand* cmd = new(std::nothrow) DMSessionRequestCommand(sessInfo, m_checkNotificationDiggest, m_pch, m_logger);
                        if (cmd != (DMSessionRequestCommand*)NULL)
                        {
                            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:
                    {
                        if (m_pch.GetProfileType().compare(NS_Daemon::c_ProfileTypeWiMAX) == 0)
                        {
                            buffer_t bsdata;
                            if (checkBootstrapMessage(&msg[dataOffset], dataSize, bsdata))
                            {
                                ProcessBootstrapCommand* cmd = new(std::nothrow) ProcessBootstrapCommand(bsdata, *m_pch.GetServerExchangeManager());
                                if (cmd != (ProcessBootstrapCommand*)NULL)
                                {
                                    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. ");
                            }
                        }
                        else
                        {
                            LOG_DEBUG_(m_logger, "Not WiMAX. SKIP BOOTSTRAP");
                        }
                    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,'\0');

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

        }
        emsk.resize(emskSize,'\0');

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

