/*
 * 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 <base/base64.h>
#include <syncml/core/Exec.h>
#include <Utils.h>
#include "common/identifiers.h"
#include "daemon/INotificationCenter.h"
#include "executionqueue/IExecutionQueue.h"
#include "serverexchange/commands/AdjustFUMOStatus.h"
#include "serverexchange/commands/FWStatusCommand.h"
#include "serverexchange/firmware/FirmwareManager.h"
#include "serverexchange/IServerExchangeManager.h"
#include "treemanager/IMOTreeManager.h"
#include "Logger/LoggerMacroses.h"

using namespace NS_DM_Client;
using namespace NS_DM_Client::NS_Common;
using namespace NS_DM_Client::NS_Communication;
using namespace NS_DM_Client::NS_DataStorage;
using namespace NS_DM_Client::NS_SyncMLCommand;

static const char * c_LogName = "FWManager";


FirmwareManager::FirmwareManager(ProfileComponentsHolder* prholder) :
    m_cancelled(false),
    m_storageInitialized(false),
    m_fumouri(NULL),
    m_fwDataURI(NULL),
    m_fwFileName(NULL),
    m_responseType(NULL),
    m_serverID(NULL),
    m_url(NULL),
    m_fumoState(FS_NO_PENDING_OPERATIONS),
    m_fwCommandStatus(FW_UNDEFINED_ERROR),
    m_fwStatus(IFirmwareUpdater::e_OK),
    m_operation(FWO_UNDEFINED),
    m_pDDProcessor(NULL),
    m_pMessenger((NS_DM_Client::Messenger*)NULL),
    m_pPCH(prholder)
{
    terminate = false;
    start();
}


FirmwareManager::~FirmwareManager()
{
    softTerminate();
    m_event.signal();
    wait();
    m_pPCH->GetDeviceAdapter()->ResetFirmwareStorage();
    
    SAFE_DELETE_ARR(m_fumouri);
    SAFE_DELETE_ARR(m_fwDataURI);
    SAFE_DELETE_ARR(m_fwFileName);
    SAFE_DELETE_ARR(m_responseType);
    SAFE_DELETE_ARR(m_serverID);
    SAFE_DELETE_ARR(m_url);
    SAFE_DELETE(m_pDDProcessor);
    resetMessenger();
    
    m_pPCH = NULL;
}


Downloader::DownloadStatus FirmwareManager::Download()
{
    return DownloadAndUpdate(false);
}


Downloader::DownloadStatus FirmwareManager::DownloadAndUpdate(bool update)
{
    SAFE_DELETE(m_pDDProcessor);
    m_fwCommandStatus = FW_UNDEFINED_ERROR;
    Downloader::DownloadStatus dstatus = Downloader::DS_FAILED;
    Downloader *downloader = DownloaderFactory::CreateDownloader(m_url);
    
    if (!downloader)
    {
        setFUMOStatus(FS_DOWNLOAD_FAILED);
        GDLERROR("failed to create downloader");
    }
    else
    {
        downloader->SetDownloader(this);

        resetMessenger();
        m_pMessenger = CreateMessenger();
        if(m_pMessenger == (NS_DM_Client::Messenger*)NULL)
        {
            GDLERROR("failed to CreateMessenger");
            return Downloader::DS_FAILED;
        }
        setFUMOStatus(FS_DOWNLOAD_PROGRESSING);
        dstatus = downloader->Download();
        GDLDEBUG("fw download finished, downloader transport status: %d", downloader->GetLastError());
        GDLDEBUG("fw downloader status: %d", dstatus);

        if (DS_FINISHED != dstatus)
        {
            m_pPCH->GetDeviceAdapter()->ResetFirmwareStorage();
            if (DS_FAILED == dstatus)
            {
                if (m_cancelled)
                    m_fwCommandStatus = FW_USER_CANCELLED;
                else
                    m_fwCommandStatus = FW_UNDEFINED_ERROR;
            }
            else if (DS_MALFORMED_URL == dstatus)
                m_fwCommandStatus = FW_MALFORMED_URL;
            else
                m_fwCommandStatus = FW_UNDEFINED_ERROR;
            setFUMOStatus(FS_DOWNLOAD_FAILED);
        }
        else
        {
            if (m_pDDProcessor) // if DDProcessor was created - downloaded file was DD xml
                return dstatus;

            if (NS_DM_Client::IFirmwareUpdater::e_OK == m_fwStatus)
            {
                dstatus = Downloader::DS_FINISHED;
                // close storage
                m_fwStatus = m_pPCH->GetDeviceAdapter()->AppendFirmwareChunk(NULL, 0, true);
                m_storageInitialized = false;
                GDLDEBUG("DeviceAdapter storage closed with: %d", m_fwStatus);
                if (update)
                {
                    setFUMOStatus(FS_DOWNLOAD_COMPLETE);
                    setFUMOStatus(doUpdate());
                }
                else
                {
                    setFUMOStatus(FS_READY_TO_UPDATE);
                    m_fwCommandStatus = FW_SUCCESSFUL;
                }
            }
            else
            {
                // error while download
                GDLDEBUG("reset fw storage");
                m_fwStatus = m_pPCH->GetDeviceAdapter()->ResetFirmwareStorage();
                setFUMOStatus(FS_DOWNLOAD_FAILED);
                m_fwCommandStatus = FW_ALT_DOWNLOAD_SERVER_ERROR;
            }
        }
        SAFE_DELETE(downloader);
    }

    return dstatus;
}


void FirmwareManager::PerformDownload(const char *fumouri)
{
    SetFUMOURI(fumouri);
    setResponseType(RESPONSE_FOR_DOWNLOAD);
    m_operation = FWO_DOWNLOAD;
    m_event.signal();
}


void FirmwareManager::PerformDownloadAndUpdate(const char *fumouri)
{
    SetFUMOURI(fumouri);
    setResponseType(RESPONSE_FOR_DU);
    m_operation = FWO_DOWNLOAD_AND_UPDATE;
    m_event.signal();
}


void FirmwareManager::PerformUpdate(const char *fumouri)
{
    SetFUMOURI(fumouri);
    setResponseType(RESPONSE_FOR_UPDATE);
    m_operation = FWO_UPDATE;
    m_event.signal();
}


void FirmwareManager::SetExecCmd(ExecPtr &cmd)
{
    if(cmd.get() == NULL)
    {
        GDLWARN("cmd is NULL");
    }

    m_ExecCommand = cmd;
}


void FirmwareManager::SetFUMOURI(const char *uri)
{
    SAFE_DELETE_ARR(m_fumouri);
    m_fumouri = Funambol::stringdup(uri);
}


void FirmwareManager::SetFWDataURI(const char *uri)
{
    SAFE_DELETE_ARR(m_fwDataURI);
    m_fwDataURI = Funambol::stringdup(uri);
}


void FirmwareManager::SetFWURL(const char *url)
{
    SAFE_DELETE_ARR(m_url);
    m_url = Funambol::stringdup(url);
}


void FirmwareManager::SetServerID(const char * serverID)
{
    SAFE_DELETE_ARR(m_serverID);
    m_serverID = Funambol::stringdup(serverID);
}


void FirmwareManager::run()
{
    GDLDEBUG("start FirmwareManager");
    while(!terminate)
    {
        GDLDEBUG("requested operation: %d", m_operation);
        if (FWO_UNDEFINED != m_operation)
        {
            enum INotification::FirmwareUpdate::EnumFirmwareOperation fwOperation = INotification::FirmwareUpdate::e_Error;
            switch (m_operation)
            {
            case FWO_DOWNLOAD:
                fwOperation = INotification::FirmwareUpdate::e_Download;
                break;

            case FWO_DOWNLOAD_AND_UPDATE:
                fwOperation = INotification::FirmwareUpdate::e_DownloadAndUpdate;
                break;

            case FWO_UPDATE:
                fwOperation = INotification::FirmwareUpdate::e_Update;
                break;

            default:
                break;
            }
            
            if (!operationAccepted())
            {
                m_fwCommandStatus = FW_USER_CANCELLED; // fw operation canceled
                GDLDEBUG("Operation not accepted by user");
            }
            else
            {
                m_pPCH->GetNotificationCenter()->FirmwareUpdateStart(fwOperation);
                if (FWO_DOWNLOAD == m_operation || FWO_DOWNLOAD_AND_UPDATE == m_operation)
                {
                    bool update = (FWO_DOWNLOAD_AND_UPDATE == m_operation);
                    GDLDEBUG("download firmware from url %s, %s perform update", m_url, update ? "" : "do not");
                    if (DS_FINISHED == DownloadAndUpdate(update) && m_pDDProcessor)
                    {
                        m_pDDProcessor->Parse();
                        if (m_pDDProcessor->IsOctet() && !m_pDDProcessor->objectURI.empty())
                        {
                            notifyFWInfo();
                            SetFWURL(m_pDDProcessor->objectURI.c_str());
                            m_properties.clear();
                            DownloadAndUpdate(update);
                        }
                        else
                        {
                            GDLERROR("downloaded dd xml points to unsupported object [isOctet='%s', objectURI='%s']", 
                                     m_pDDProcessor->IsOctet(), m_pDDProcessor->objectURI.c_str());
                            m_fwCommandStatus = FW_UNDEFINED_ERROR;
                            setFUMOStatus(FS_DOWNLOAD_FAILED);
                        }
                    }
                }
                else if (FWO_UPDATE == m_operation)
                {
                    if (m_fwDataURI)
                    {
                        GDLDEBUG("update fw from uri %s", m_fumouri);
                        setFUMOStatus(writeFWFromFUMO());
                        SetFWDataURI(NULL);
                        m_fumoState = FS_NO_PENDING_OPERATIONS;
                    }
                    else if (FS_READY_TO_UPDATE == m_fumoState)
                    {
                        GDLDEBUG("update firmware");
                        setFUMOStatus(doUpdate(true));
                        m_fumoState = FS_NO_PENDING_OPERATIONS;
                    }
                    else
                    {
                        GDLERROR("failed to update firmware: fw data URI is not set");
                        setFUMOStatus(FS_UPDATE_FAILED_NO_DATA);
                    }
                }
                m_pPCH->GetNotificationCenter()->FirmwareUpdateFinished(fwOperation);
                GDLDEBUG("sent notification about firmware update has been finished", fwOperation);
            }

            notifyJobStatus();
        }

        m_event.wait();
    }
    GDLDEBUG("exit FirmwareManager");
}


// Downloader implementation - callback to store data from specific downloader instance
// Returned value indicates number of stored bytes
// Returned 0 will stop file downloading (specific curl behaviour)
int FirmwareManager::storeBuffer(void *buffer, uint size)
{
    GDLDEBUG("storeBuffer: %d bytes", size);
    
    Properties::iterator it = m_properties.find("Content-Type");
    if (it != m_properties.end() && strstr(it->second.c_str(), MIME_OMA_DD))
    {
        if (!m_pDDProcessor)
        {
            m_pDDProcessor = new(std::nothrow) DDProcessor();
            if(m_pDDProcessor == (DDProcessor*)NULL)
            {
                GDLWARN("new DDProcessor");
            }
        }
        return m_pDDProcessor->AppendBuffer(buffer, size);
    }
    else
    {
        if (!m_storageInitialized)
        {
            m_fwStatus = m_pPCH->GetDeviceAdapter()->InitFirmwareStorage();
            GDLDEBUG("InitFirmwareStorage ended with: %d", m_fwStatus);
            if (NS_DM_Client::IFirmwareUpdater::e_OK != m_fwStatus)
                return 0;
            m_storageInitialized = true;
        }
        
        m_fwStatus = m_pPCH->GetDeviceAdapter()->AppendFirmwareChunk((const char*)buffer, size, false);
        if (NS_DM_Client::IFirmwareUpdater::e_OK == m_fwStatus)
            if (notifyDownloadProgress())
                return size;
    }

    return 0;
}


/// trigger the firmware update on the device
FUMOState FirmwareManager::doUpdate(bool resetStorage)
{
    FUMOState result = FS_UPDATE_FAILED_NO_DATA;
    GDLDEBUG("perform fw update");
    setFUMOStatus(FS_UPDATE_PROGRESSING);
    
    IDeviceAdapter *pDA = m_pPCH->GetDeviceAdapter();
    m_fwStatus = pDA->UpdateFirmware();

    if (NS_DM_Client::IFirmwareUpdater::e_OK == m_fwStatus)
    {
        m_fwCommandStatus = FW_SUCCESSFUL;
        if (resetStorage)
        {
            pDA->ResetFirmwareStorage();
            result = FS_UPDATE_SUCCESSFUL_NO_DATA;
        }
        else
            result = FS_UPDATE_SUCCESSFUL_HAVE_DATA;
    }
    else if (NS_DM_Client::IFirmwareUpdater::e_FirmwareUpdateFailedBecausePackageIsCorrupted == m_fwStatus)
    {
        pDA->ResetFirmwareStorage();
        m_fwCommandStatus = FW_CORRUPTED_PACKAGE;
        result = FS_UPDATE_FAILED_NO_DATA;
    }
    else
    {
        m_fwCommandStatus = FW_UPDATE_FAILED;
        result = FS_UPDATE_FAILED_HAVE_DATA;
    }

    return result;
}


// send info about firmware package
// info is taken from the DDProcessor if instance is available
void FirmwareManager::notifyFWInfo()
{
    if (m_pDDProcessor)
    {
        String filepath;
        String filename = m_pDDProcessor->installNotifyURI;

        // get file name - find last '/' or '\' and get the trailing chars
        size_t found = filename.find_last_of("/\\");
        if (String::npos != found && found < filename.size()-1)
            filename = filename.substr(found+1);
        else if (found == filename.size()-1)
            filename = "";
        
        m_pPCH->GetNotificationCenter()->FirmwareInfo(filepath, filename, m_pDDProcessor->size);
    }
}


// return false if download should be stopped
// return true if download should be continued
bool FirmwareManager::notifyDownloadProgress()
{
    UIOptionalParameters parameters;
    if (m_pMessenger != (NS_DM_Client::Messenger*)NULL)
    {
        StatusCode res = m_pMessenger->ProgressNotification("", parameters, 0);
        if (e_Ok == res)
            return true;
        else
        {
            m_cancelled = e_OperationCancelled == res;
            return !m_cancelled;
        }
    }
    else
        return true;
}


void FirmwareManager::notifyJobStatus()
{
    // notify server with generic alert status of the FW download
    // TODO - send status with command
    String serverID(m_serverID);
    GDLDEBUG("send result to server - '%s', command status - %d, response type - '%s'", 
             m_serverID, m_fwCommandStatus, m_responseType);
    
    ICommand *cmd = new(std::nothrow) FWStatusCommand(*m_pPCH->GetServerExchangeManager(), m_ExecCommand, m_fwCommandStatus, m_serverID, m_responseType);
    if(cmd == (ICommand*)NULL)
    {
        GDLWARN("new FWStatusCommand");
    }

    if (!m_pPCH->GetExecutionQueue()->Add(*cmd))
    {
        delete cmd;
    }
}


// ask user through the Messenger to confirm current fw operation
// operation type is specified by the value of m_responseType
bool FirmwareManager::operationAccepted()
{
    StatusCode result = e_CommandFailed;

    Messenger *pMessenger = CreateMessenger();
    if (pMessenger != (Messenger*)NULL)
    {
        String operationType(m_responseType);
        UIOptionalParameters parameters;    StatusCode result = e_CommandFailed;
        result = pMessenger->Confirmation(operationType, parameters);
        pMessenger->Release();
    }
    else
    {
        GDLWARN("Failed to CreateMessenger");
    }

    if(result == e_Ok) return true;
    else return false;
}


void FirmwareManager::resetMessenger()
{
    if (m_pMessenger != (Messenger*)NULL)
    {
        m_pMessenger->Release();
        m_pMessenger = (Messenger*)NULL;
    }

    m_cancelled = false;
}


void FirmwareManager::setFUMOStatus(FUMOState status)
{
    if (m_fumouri)
    {
        m_fumoState = status;
        GDLDEBUG("set status to %d on FUMO '%s'", status, m_fumouri);
        ICommand *cmd = new(std::nothrow) AdjustFUMOStatus(m_pPCH, String(m_fumouri), status);
        if(cmd == (ICommand*)NULL)
        {
            GDLWARN("new AdjustFUMOStatus");
        }

        if (!m_pPCH->GetExecutionQueue()->Add(*cmd))
        {
            delete cmd;
        }
    }
    else
        GDLERROR("set status to %d: FUMO uri is not set!!!", status);
}


void FirmwareManager::setResponseType(const char * resptype)
{
    SAFE_DELETE_ARR(m_responseType);
    m_responseType = Funambol::stringdup(resptype);
}


FUMOState FirmwareManager::writeFW(const char *data, size_t size)
{
    if (!data || !size) return FS_UPDATE_FAILED_NO_DATA;
    
    IDeviceAdapter *pDA = m_pPCH->GetDeviceAdapter();
    if (IFirmwareUpdater::e_OK == (m_fwStatus = pDA->InitFirmwareStorage()))
    {
        if (IFirmwareUpdater::e_OK == (m_fwStatus = pDA->AppendFirmwareChunk(data, size, true)))
        {
            return doUpdate();
        }
        else
            GDLDEBUG("error while appending data into firmware storage of the device adapter: %d", m_fwStatus);
    }
    else
        GDLDEBUG("error while initializing firmware storage of the device adapter: %d", m_fwStatus);

    return FS_UPDATE_FAILED_HAVE_DATA;
}


// write data from the FUMO to DeviceAdapter
// b64 data are expected to be stored in the FUMO/Update node
FUMOState FirmwareManager::writeFWFromFUMO()
{
    FUMOState result = FS_UPDATE_FAILED_NO_DATA;
    String value;
    if (e_Ok == m_pPCH->GetMOTreeManager()->GetValue(m_fwDataURI, value, NULL)) // perform in root mode
    {
        String format; // check if type is b64 - if so - decode
        if (e_Ok == m_pPCH->GetMOTreeManager()->GetPropertyValue(m_fwDataURI, "Format", format, NULL))
        {
            if (!format.compare("b64"))
            {
                // decode pkg data to plain binary
                char * pDecodedFW = new char[value.size()+1];
                if(pDecodedFW == NULL)
                {
                    GDLWARN("new pDecodedFW");
                }
                memset(pDecodedFW, 0, value.size()+1);
                size_t decoded_len = Funambol::b64_decode(pDecodedFW, value.c_str());
                if (decoded_len)
                {
                    result = writeFW(pDecodedFW, decoded_len);
                }
                else
                    GDLERROR("failed to decode from b64");
                SAFE_DELETE_ARR(pDecodedFW);
            }
            else
            {
                GDLERROR("fw data are of the unsupported Format >%s<", format.c_str());
            }
        }
        else
        {
            GDLERROR("failed to retrieve Format property of the fw data node");
        }
    }
    else
    {
        GDLERROR("failed to retrieve fw from the Update node");
    }
    return result;
}
