/**
 * @file lltransfermanager.h
 * @brief Improved transfer mechanism for moving data through the
 * message system.
 *
 * $LicenseInfo:firstyear=2006&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2010, Linden Research, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License only.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */

#ifndef LL_LLTRANSFERMANAGER_H
#define LL_LLTRANSFERMANAGER_H

#include <map>
#include <list>

#include "llhost.h"
#include "lluuid.h"
#include "llthrottle.h"
#include "llpriqueuemap.h"
#include "llassettype.h"

//
// Definition of the manager class for the new LLXfer replacement.
// Provides prioritized, bandwidth-throttled transport of arbitrary
// binary data between host/circuit combos
//


typedef enum e_transfer_channel_type
{
    LLTCT_UNKNOWN = 0,
    LLTCT_MISC,
    LLTCT_ASSET,
    LLTCT_NUM_TYPES
} LLTransferChannelType;


typedef enum e_transfer_source_type
{
    LLTST_UNKNOWN = 0,
    LLTST_FILE,
    LLTST_ASSET,
    LLTST_SIM_INV_ITEM, // Simulator specific, may not be handled
    LLTST_SIM_ESTATE,   // Simulator specific, may not be handled
    LLTST_NUM_TYPES
} LLTransferSourceType;


typedef enum e_transfer_target_type
{
    LLTTT_UNKNOWN = 0,
    LLTTT_FILE,
    LLTTT_VFILE,
    LLTTT_NUM_TYPES
} LLTransferTargetType;


// Errors are negative, expected values are positive.
typedef enum e_status_codes
{
    LLTS_OK = 0,
    LLTS_DONE = 1,
    LLTS_SKIP = 2,
    LLTS_ABORT = 3,
    LLTS_ERROR = -1,
    LLTS_UNKNOWN_SOURCE = -2, // Equivalent of a 404
    LLTS_INSUFFICIENT_PERMISSIONS = -3  // Not enough permissions
} LLTSCode;

// Types of requests for estate wide information
typedef enum e_estate_type
{
    ET_Covenant = 0,
    ET_NONE = -1
} EstateAssetType;

class LLMessageSystem;
class LLDataPacker;

class LLTransferConnection;
class LLTransferSourceChannel;
class LLTransferTargetChannel;
class LLTransferSourceParams;
class LLTransferTargetParams;
class LLTransferSource;
class LLTransferTarget;

class LLTransferManager
{
public:
    LLTransferManager();
    virtual ~LLTransferManager();

    void init();
    void cleanup();

    void updateTransfers(); // Called per frame to push packets out on the various different channels.
    void cleanupConnection(const LLHost &host);


    LLTransferSourceChannel *getSourceChannel(const LLHost &host, const LLTransferChannelType stype);
    LLTransferTargetChannel *getTargetChannel(const LLHost &host, const LLTransferChannelType stype);

    LLTransferSource *findTransferSource(const LLUUID &transfer_id);

    BOOL                        isValid() const         { return mValid; }

    static void processTransferRequest(LLMessageSystem *mesgsys, void **);
    static void processTransferInfo(LLMessageSystem *mesgsys, void **);
    static void processTransferPacket(LLMessageSystem *mesgsys, void **);
    static void processTransferAbort(LLMessageSystem *mesgsys, void **);

    static void reliablePacketCallback(void **, S32 result);

    S32 getTransferBitsIn(const LLTransferChannelType tctype) const     { return mTransferBitsIn[tctype]; }
    S32 getTransferBitsOut(const LLTransferChannelType tctype) const    { return mTransferBitsOut[tctype]; }
    void resetTransferBitsIn(const LLTransferChannelType tctype)        { mTransferBitsIn[tctype] = 0; }
    void resetTransferBitsOut(const LLTransferChannelType tctype)       { mTransferBitsOut[tctype] = 0; }
    void addTransferBitsIn(const LLTransferChannelType tctype, const S32 bits)  { mTransferBitsIn[tctype] += bits; }
    void addTransferBitsOut(const LLTransferChannelType tctype, const S32 bits) { mTransferBitsOut[tctype] += bits; }
protected:
    LLTransferConnection        *getTransferConnection(const LLHost &host);
    BOOL                        removeTransferConnection(const LLHost &host);

protected:
    // Convenient typedefs
    typedef std::map<LLHost, LLTransferConnection *> host_tc_map;

    BOOL    mValid;
    LLHost  mHost;

    S32     mTransferBitsIn[LLTTT_NUM_TYPES];
    S32     mTransferBitsOut[LLTTT_NUM_TYPES];

    // We keep a map between each host and LLTransferConnection.
    host_tc_map mTransferConnections;
};


//
// Keeps tracks of all channels to/from a particular host.
//
class LLTransferConnection
{
public:
    LLTransferConnection(const LLHost &host);
    virtual ~LLTransferConnection();

    void updateTransfers();

    LLTransferSourceChannel *getSourceChannel(const LLTransferChannelType type);
    LLTransferTargetChannel *getTargetChannel(const LLTransferChannelType type);

    // Convenient typedefs
    typedef std::list<LLTransferSourceChannel *>::iterator tsc_iter;
    typedef std::list<LLTransferTargetChannel *>::iterator ttc_iter;
    friend class LLTransferManager;
protected:

    LLHost                                  mHost;
    std::list<LLTransferSourceChannel *>    mTransferSourceChannels;
    std::list<LLTransferTargetChannel *>    mTransferTargetChannels;

};


//
// A channel which is pushing data out.
//

class LLTransferSourceChannel
{
public:
    LLTransferSourceChannel(const LLTransferChannelType channel_type,
                            const LLHost &host);
    virtual ~LLTransferSourceChannel();

    void updateTransfers();

    void updatePriority(LLTransferSource *tsp, const F32 priority);

    void                addTransferSource(LLTransferSource *sourcep);
    LLTransferSource    *findTransferSource(const LLUUID &transfer_id);
    void                deleteTransfer(LLTransferSource *tsp);

    void                    setThrottleID(const S32 throttle_id)    { mThrottleID = throttle_id; }

    LLTransferChannelType   getChannelType() const      { return mChannelType; }
    LLHost                  getHost() const             { return mHost; }

protected:
    typedef std::list<LLTransferSource *>::iterator ts_iter;

    LLTransferChannelType               mChannelType;
    LLHost                              mHost;
    LLPriQueueMap<LLTransferSource*>    mTransferSources;

    // The throttle that this source channel should use
    S32                                 mThrottleID;
};


//
// A channel receiving data from a source.
//
class LLTransferTargetChannel
{
public:
    LLTransferTargetChannel(const LLTransferChannelType channel_type, const LLHost &host);
    virtual ~LLTransferTargetChannel();

    void requestTransfer(const LLTransferSourceParams &source_params,
                         const LLTransferTargetParams &target_params,
                         const F32 priority);

    LLTransferTarget        *findTransferTarget(const LLUUID &transfer_id);
    void                    deleteTransfer(LLTransferTarget *ttp);


    LLTransferChannelType   getChannelType() const      { return mChannelType; }
    LLHost                  getHost() const             { return mHost; }

protected:
    void sendTransferRequest(LLTransferTarget *targetp,
                             const LLTransferSourceParams &params,
                             const F32 priority);

    void                    addTransferTarget(LLTransferTarget *targetp);

    friend class LLTransferTarget;
    friend class LLTransferManager;
protected:
    typedef std::list<LLTransferTarget *>::iterator tt_iter;

    LLTransferChannelType           mChannelType;
    LLHost                          mHost;
    std::list<LLTransferTarget *>   mTransferTargets;
};


class LLTransferSourceParams
{
public:
    LLTransferSourceParams(const LLTransferSourceType type) : mType(type) { }
    virtual ~LLTransferSourceParams();

    virtual void packParams(LLDataPacker &dp) const = 0;
    virtual BOOL unpackParams(LLDataPacker &dp) = 0;

    LLTransferSourceType getType() const            { return mType; }

protected:
    LLTransferSourceType mType;
};


//
// LLTransferSource is an interface, all transfer sources should be derived from it.
//
typedef LLTransferSource *(*LLTransferSourceCreateFunc)(const LLUUID &id, const F32 priority);

class LLTransferSource
{
public:

    LLUUID getID()              { return mID; }

    friend class LLTransferManager;
    friend class LLTransferSourceChannel;

protected:
    LLTransferSource(const LLTransferSourceType source_type,
                     const LLUUID &request_id,
                     const F32 priority);
    virtual ~LLTransferSource();

    void                    sendTransferStatus(LLTSCode status);    // When you've figured out your transfer status, do this

    virtual void            initTransfer() = 0;
    virtual F32             updatePriority() = 0;
    virtual LLTSCode        dataCallback(const S32 packet_id,
                                         const S32 max_bytes,
                                         U8 **datap,
                                         S32 &returned_bytes,
                                         BOOL &delete_returned) = 0;

    // The completionCallback is GUARANTEED to be called before the destructor.
    virtual void            completionCallback(const LLTSCode status) = 0;

    virtual void packParams(LLDataPacker& dp) const = 0;
    virtual BOOL unpackParams(LLDataPacker& dp) = 0;

    virtual S32             getNextPacketID()                       { return mLastPacketID + 1; }
    virtual void            setLastPacketID(const S32 packet_id)    { mLastPacketID = packet_id; }


    // For now, no self-induced priority changes
    F32                     getPriority()                           { return mPriority; }
    void                    setPriority(const F32 pri)              { mPriority = pri; }

    virtual void            abortTransfer(); // DON'T USE THIS ONE, used internally by LLTransferManager

    static LLTransferSource *createSource(const LLTransferSourceType stype,
                                          const LLUUID &request_id,
                                          const F32 priority);
    static void registerSourceType(const LLTransferSourceType stype, LLTransferSourceCreateFunc);

    static void sSetPriority(LLTransferSource *&tsp, const F32 priority);
    static F32  sGetPriority(LLTransferSource *&tsp);
protected:
    typedef std::map<LLTransferSourceType, LLTransferSourceCreateFunc> stype_scfunc_map;
    static stype_scfunc_map sSourceCreateMap;

    LLTransferSourceType mType;
    LLUUID mID;
    LLTransferSourceChannel *mChannelp;
    F32     mPriority;
    S32     mSize;
    S32     mLastPacketID;
};


class LLTransferTargetParams
{
public:
    LLTransferTargetParams(const LLTransferTargetType type) : mType(type) {}
    LLTransferTargetType getType() const        { return mType; }
protected:
    LLTransferTargetType mType;
};


class LLTransferPacket
{
    // Used for storing a packet that's being delivered later because it's out of order.
    // ONLY should be accessed by the following two classes, for now.
    friend class LLTransferTarget;
    friend class LLTransferManager;

protected:

    LLTransferPacket(const S32 packet_id, const LLTSCode status, const U8 *datap, const S32 size);
    virtual ~LLTransferPacket();

protected:
    S32         mPacketID;
    LLTSCode    mStatus;
    U8          *mDatap;
    S32         mSize;
};


class LLTransferTarget
{
public:
    LLTransferTarget(
        LLTransferTargetType target_type,
        const LLUUID& transfer_id,
        LLTransferSourceType source_type);
    virtual ~LLTransferTarget();

    // Accessors
    LLUUID                  getID() const           { return mID; }
    LLTransferTargetType    getType() const         { return mType; }
    LLTransferTargetChannel *getChannel() const     { return mChannelp; }
    LLTransferSourceType getSourceType() const { return mSourceType; }

    // Static functionality
    static LLTransferTarget* createTarget(
        LLTransferTargetType target_type,
        const LLUUID& request_id,
        LLTransferSourceType source_type);

    // friends
    friend class LLTransferManager;
    friend class LLTransferTargetChannel;

protected:
    // Implementation
    virtual bool unpackParams(LLDataPacker& dp) = 0;
    virtual void applyParams(const LLTransferTargetParams &params) = 0;
    virtual LLTSCode        dataCallback(const S32 packet_id, U8 *in_datap, const S32 in_size) = 0;

    // The completionCallback is GUARANTEED to be called before the destructor, so all handling
    // of errors/aborts should be done here.
    virtual void            completionCallback(const LLTSCode status) = 0;

    void                    abortTransfer();

    virtual S32             getNextPacketID()                       { return mLastPacketID + 1; }
    virtual void            setLastPacketID(const S32 packet_id)    { mLastPacketID = packet_id; }
    void                    setSize(const S32 size)                 { mSize = size; }
    void                    setGotInfo(const BOOL got_info)         { mGotInfo = got_info; }
    BOOL                    gotInfo() const                         { return mGotInfo; }

    bool addDelayedPacket(
        const S32 packet_id,
        const LLTSCode status,
        U8* datap,
        const S32 size);

protected:
    typedef std::map<S32, LLTransferPacket *> transfer_packet_map;
    typedef std::map<S32, LLTransferPacket *>::iterator tpm_iter;

    LLTransferTargetType    mType;
    LLTransferSourceType mSourceType;
    LLUUID                  mID;
    LLTransferTargetChannel *mChannelp;
    BOOL                    mGotInfo;
    S32                     mSize;
    S32                     mLastPacketID;

    transfer_packet_map     mDelayedPacketMap; // Packets that are waiting because of missing/out of order issues
};


// Hack, here so it's publicly available even though LLTransferSourceInvItem is only available on the simulator
class LLTransferSourceParamsInvItem: public LLTransferSourceParams
{
public:
    LLTransferSourceParamsInvItem();
    virtual ~LLTransferSourceParamsInvItem() {}
    /*virtual*/ void packParams(LLDataPacker &dp) const;
    /*virtual*/ BOOL unpackParams(LLDataPacker &dp);

    void setAgentSession(const LLUUID &agent_id, const LLUUID &session_id);
    void setInvItem(const LLUUID &owner_id, const LLUUID &task_id, const LLUUID &item_id);
    void setAsset(const LLUUID &asset_id, const LLAssetType::EType at);

    LLUUID getAgentID() const                       { return mAgentID; }
    LLUUID getSessionID() const                     { return mSessionID; }
    LLUUID getOwnerID() const                       { return mOwnerID; }
    LLUUID getTaskID() const                        { return mTaskID; }
    LLUUID getItemID() const                        { return mItemID; }
    LLUUID getAssetID() const                       { return mAssetID; }
    LLAssetType::EType getAssetType() const         { return mAssetType; }

protected:
    LLUUID              mAgentID;
    LLUUID              mSessionID;
    LLUUID              mOwnerID;
    LLUUID              mTaskID;
    LLUUID              mItemID;
    LLUUID              mAssetID;
    LLAssetType::EType  mAssetType;
};


// Hack, here so it's publicly available even though LLTransferSourceEstate is only available on the simulator
class LLTransferSourceParamsEstate: public LLTransferSourceParams
{
public:
    LLTransferSourceParamsEstate();
    virtual ~LLTransferSourceParamsEstate() {}
    /*virtual*/ void packParams(LLDataPacker &dp) const;
    /*virtual*/ BOOL unpackParams(LLDataPacker &dp);

    void setAgentSession(const LLUUID &agent_id, const LLUUID &session_id);
    void setEstateAssetType(const EstateAssetType etype);
    void setAsset(const LLUUID &asset_id, const LLAssetType::EType at);

    LLUUID getAgentID() const                       { return mAgentID; }
    LLUUID getSessionID() const                     { return mSessionID; }
    EstateAssetType getEstateAssetType() const      { return mEstateAssetType; }
    LLUUID getAssetID() const                   { return mAssetID; }
    LLAssetType::EType getAssetType() const     { return mAssetType; }

protected:
    LLUUID              mAgentID;
    LLUUID              mSessionID;
    EstateAssetType     mEstateAssetType;
    // these are set on the sim based on estateinfotype
    LLUUID              mAssetID;
    LLAssetType::EType  mAssetType;
};


extern LLTransferManager gTransferManager;

#endif//LL_LLTRANSFERMANAGER_H