//------------------------------------------------------------------------------
/*
    Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================

SETUP_LOG (TransactionAcquire)

#define TX_ACQUIRE_TIMEOUT  250

typedef std::map<uint160, LedgerProposal::pointer>::value_type u160_prop_pair;
typedef std::map<uint256, DisputedTx::pointer>::value_type u256_lct_pair;

TransactionAcquire::TransactionAcquire (uint256 const& hash)
    : PeerSet (hash, TX_ACQUIRE_TIMEOUT, true)
    , mHaveRoot (false)
{
    mMap = boost::make_shared<SHAMap> (smtTRANSACTION, hash);
}

static void TACompletionHandler (uint256 hash, SHAMap::pointer map)
{
    {
        Application::ScopedLockType lock (getApp ().getMasterLock (), __FILE__, __LINE__);

        getApp().getOPs ().mapComplete (hash, map);

        getApp().getInboundLedgers ().dropLedger (hash);
    }
}

void TransactionAcquire::done ()
{
    // We hold a PeerSet lock and so cannot acquire the master lock here
    SHAMap::pointer map;

    if (mFailed)
    {
        WriteLog (lsWARNING, TransactionAcquire) << "Failed to acquire TX set " << mHash;
    }
    else
    {
        WriteLog (lsINFO, TransactionAcquire) << "Acquired TX set " << mHash;
        mMap->setImmutable ();
        map = mMap;
    }

    getApp().getIOService ().post (BIND_TYPE (&TACompletionHandler, mHash, map));
}

void TransactionAcquire::onTimer (bool progress, boost::recursive_mutex::scoped_lock& psl)
{
    bool aggressive = false;

    if (getTimeouts () > 10)
    {
        WriteLog (lsWARNING, TransactionAcquire) << "Ten timeouts on TX set " << getHash ();
        psl.unlock();
        {
            Application::ScopedLockType lock (getApp().getMasterLock (), __FILE__, __LINE__);

            if (getApp().getOPs ().stillNeedTXSet (mHash))
            {
                WriteLog (lsWARNING, TransactionAcquire) << "Still need it";
                mTimeouts = 0;
                aggressive = true;
	    }
        }
        psl.lock();

        if (!aggressive)
        {
            mFailed = true;
            done ();
            return;
        }
    }

    if (aggressive || !getPeerCount ())
    {
        // out of peers
        WriteLog (lsWARNING, TransactionAcquire) << "Out of peers for TX set " << getHash ();

        bool found = false;
        std::vector<Peer::pointer> peerList = getApp().getPeers ().getPeerVector ();
        BOOST_FOREACH (Peer::ref peer, peerList)
        {
            if (peer->hasTxSet (getHash ()))
            {
                found = true;
                peerHas (peer);
            }
        }

        if (!found)
        {
            BOOST_FOREACH (Peer::ref peer, peerList)
            peerHas (peer);
        }
    }
    else if (!progress)
        trigger (Peer::pointer ());
}

boost::weak_ptr<PeerSet> TransactionAcquire::pmDowncast ()
{
    return boost::dynamic_pointer_cast<PeerSet> (shared_from_this ());
}

void TransactionAcquire::trigger (Peer::ref peer)
{
    if (mComplete || mFailed)
    {
        WriteLog (lsINFO, TransactionAcquire) << "complete or failed";
        return;
    }

    if (!mHaveRoot)
    {
        WriteLog (lsTRACE, TransactionAcquire) << "TransactionAcquire::trigger " << (peer ? "havePeer" : "noPeer") << " no root";
        protocol::TMGetLedger tmGL;
        tmGL.set_ledgerhash (mHash.begin (), mHash.size ());
        tmGL.set_itype (protocol::liTS_CANDIDATE);

        if (getTimeouts () != 0)
            tmGL.set_querytype (protocol::qtINDIRECT);

        * (tmGL.add_nodeids ()) = SHAMapNode ().getRawString ();
        sendRequest (tmGL, peer);
    }
    else
    {
        std::vector<SHAMapNode> nodeIDs;
        std::vector<uint256> nodeHashes;
        ConsensusTransSetSF sf;
        mMap->getMissingNodes (nodeIDs, nodeHashes, 256, &sf);

        if (nodeIDs.empty ())
        {
            if (mMap->isValid ())
                mComplete = true;
            else
                mFailed = true;

            done ();
            return;
        }

        protocol::TMGetLedger tmGL;
        tmGL.set_ledgerhash (mHash.begin (), mHash.size ());
        tmGL.set_itype (protocol::liTS_CANDIDATE);

        if (getTimeouts () != 0)
            tmGL.set_querytype (protocol::qtINDIRECT);

        BOOST_FOREACH (SHAMapNode & it, nodeIDs)
        * (tmGL.add_nodeids ()) = it.getRawString ();
        sendRequest (tmGL, peer);
    }
}

SHAMapAddNode TransactionAcquire::takeNodes (const std::list<SHAMapNode>& nodeIDs,
        const std::list< Blob >& data, Peer::ref peer)
{
    if (mComplete)
    {
        WriteLog (lsTRACE, TransactionAcquire) << "TX set complete";
        return SHAMapAddNode ();
    }

    if (mFailed)
    {
        WriteLog (lsTRACE, TransactionAcquire) << "TX set failed";
        return SHAMapAddNode ();
    }

    try
    {
        if (nodeIDs.empty ())
            return SHAMapAddNode::invalid ();

        std::list<SHAMapNode>::const_iterator nodeIDit = nodeIDs.begin ();
        std::list< Blob >::const_iterator nodeDatait = data.begin ();
        ConsensusTransSetSF sf;

        while (nodeIDit != nodeIDs.end ())
        {
            if (nodeIDit->isRoot ())
            {
                if (mHaveRoot)
                {
                    WriteLog (lsWARNING, TransactionAcquire) << "Got root TXS node, already have it";
                    return SHAMapAddNode ();
                }

                if (!mMap->addRootNode (getHash (), *nodeDatait, snfWIRE, NULL))
                {
                    WriteLog (lsWARNING, TransactionAcquire) << "TX acquire got bad root node";
                    return SHAMapAddNode::invalid ();
                }
                else
                    mHaveRoot = true;
            }
            else if (!mMap->addKnownNode (*nodeIDit, *nodeDatait, &sf))
            {
                WriteLog (lsWARNING, TransactionAcquire) << "TX acquire got bad non-root node";
                return SHAMapAddNode::invalid ();
            }

            ++nodeIDit;
            ++nodeDatait;
        }

        trigger (peer);
        progress ();
        return SHAMapAddNode::useful ();
    }
    catch (...)
    {
        WriteLog (lsERROR, TransactionAcquire) << "Peer sends us junky transaction node data";
        return SHAMapAddNode::invalid ();
    }
}
