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

class Features;

SETUP_LOG (Features)

FeatureState* testFeature = NULL;

// VFALCO TODO Rename this to Features
class Features : public IFeatures
{
protected:

    typedef boost::unordered_map<uint256, FeatureState> featureMap_t;
    typedef std::pair<const uint256, FeatureState> featureIt_t;
    typedef boost::unordered_set<uint256> featureList_t;

    boost::mutex    mMutex;
    featureMap_t    mFeatureMap;
    int             mMajorityTime;      // Seconds a feature must hold a majority
    int             mMajorityFraction;  // 256 = 100%
    uint32          mFirstReport;       // close time of first majority report
    uint32          mLastReport;        // close time of most recent majority report

    FeatureState*   getCreateFeature (uint256 const& feature, bool create);
    bool shouldEnable (uint32 closeTime, const FeatureState& fs);
    void setJson (Json::Value& v, const FeatureState&);

public:

    Features (uint32 majorityTime, int majorityFraction)
        : mMajorityTime (majorityTime), mMajorityFraction (majorityFraction), mFirstReport (0), mLastReport (0)
    {
        ;
    }

    void addInitialFeatures ();

    FeatureState* addKnownFeature (const char* featureID, const char* friendlyName, bool veto);
    uint256 getFeature (const std::string& name);

    bool vetoFeature (uint256 const& feature);
    bool unVetoFeature (uint256 const& feature);

    bool enableFeature (uint256 const& feature);
    bool disableFeature (uint256 const& feature);

    bool isFeatureEnabled (uint256 const& feature);
    bool isFeatureSupported (uint256 const& feature);

    void setEnabledFeatures (const std::vector<uint256>& features);
    void setSupportedFeatures (const std::vector<uint256>& features);

    featureList_t getVetoedFeatures ();
    featureList_t getEnabledFeatures ();
    featureList_t getFeaturesToEnable (uint32 closeTime);   // gets features we would vote to enable
    featureList_t getDesiredFeatures ();                    // features we support, do not veto, are not enabled

    void reportValidations (const FeatureSet&);

    Json::Value getJson (int);
    Json::Value getJson (uint256 const& );

    void doValidation (Ledger::ref lastClosedLedger, STObject& baseValidation);
    void doVoting (Ledger::ref lastClosedLedger, SHAMap::ref initialPosition);
};

void Features::addInitialFeatures ()
{
    // For each feature this version supports, construct the FeatureState object by calling
    // getCreateFeature. Set any vetoes or defaults. A pointer to the FeatureState can be stashed

    testFeature = addKnownFeature ("1234", "testFeature", false);
}

FeatureState* Features::getCreateFeature (uint256 const& featureHash, bool create)
{
    // call with the mutex held
    featureMap_t::iterator it = mFeatureMap.find (featureHash);

    if (it == mFeatureMap.end ())
    {
        if (!create)
            return NULL;

        FeatureState* feature = & (mFeatureMap[featureHash]);

        {
            std::string query = "SELECT FirstMajority,LastMajority FROM Features WHERE hash='";
            query.append (featureHash.GetHex ());
            query.append ("';");

            ScopedLock sl (getApp().getWalletDB ()->getDBLock ());
            Database* db = getApp().getWalletDB ()->getDB ();

            if (db->executeSQL (query) && db->startIterRows ())
            {
                feature->mFirstMajority = db->getBigInt ("FirstMajority");
                feature->mLastMajority = db->getBigInt ("LastMajority");
                db->endIterRows ();
            }
        }

        return feature;
    }

    return & (it->second);
}

uint256 Features::getFeature (const std::string& name)
{
    if (!name.empty ())
    {
        BOOST_FOREACH (featureMap_t::value_type & it, mFeatureMap)
        {
            if (name == it.second.mFriendlyName)
                return it.first;
        }
    }

    return uint256 ();
}

FeatureState* Features::addKnownFeature (const char* featureID, const char* friendlyName, bool veto)
{
    uint256 hash;
    hash.SetHex (featureID);

    if (hash.isZero ())
    {
        assert (false);
        return NULL;
    }

    FeatureState* f = getCreateFeature (hash, true);

    if (friendlyName != NULL)
        f->setFriendlyName (friendlyName);

    f->mVetoed = veto;
    f->mSupported = true;

    return f;
}

bool Features::vetoFeature (uint256 const& feature)
{
    boost::mutex::scoped_lock sl (mMutex);
    FeatureState* s = getCreateFeature (feature, true);

    if (s->mVetoed)
        return false;

    s->mVetoed = true;
    return true;
}

bool Features::unVetoFeature (uint256 const& feature)
{
    boost::mutex::scoped_lock sl (mMutex);
    FeatureState* s = getCreateFeature (feature, false);

    if (!s || !s->mVetoed)
        return false;

    s->mVetoed = false;
    return true;
}

bool Features::enableFeature (uint256 const& feature)
{
    boost::mutex::scoped_lock sl (mMutex);
    FeatureState* s = getCreateFeature (feature, true);

    if (s->mEnabled)
        return false;

    s->mEnabled = true;
    return true;
}

bool Features::disableFeature (uint256 const& feature)
{
    boost::mutex::scoped_lock sl (mMutex);
    FeatureState* s = getCreateFeature (feature, false);

    if (!s || !s->mEnabled)
        return false;

    s->mEnabled = false;
    return true;
}

bool Features::isFeatureEnabled (uint256 const& feature)
{
    boost::mutex::scoped_lock sl (mMutex);
    FeatureState* s = getCreateFeature (feature, false);
    return s && s->mEnabled;
}

bool Features::isFeatureSupported (uint256 const& feature)
{
    boost::mutex::scoped_lock sl (mMutex);
    FeatureState* s = getCreateFeature (feature, false);
    return s && s->mSupported;
}

Features::featureList_t Features::getVetoedFeatures ()
{
    featureList_t ret;
    boost::mutex::scoped_lock sl (mMutex);
    BOOST_FOREACH (const featureIt_t & it, mFeatureMap)
    {
        if (it.second.mVetoed)
            ret.insert (it.first);
    }
    return ret;
}

Features::featureList_t Features::getEnabledFeatures ()
{
    featureList_t ret;
    boost::mutex::scoped_lock sl (mMutex);
    BOOST_FOREACH (const featureIt_t & it, mFeatureMap)
    {
        if (it.second.mEnabled)
            ret.insert (it.first);
    }
    return ret;
}

bool Features::shouldEnable (uint32 closeTime, const FeatureState& fs)
{
    if (fs.mVetoed || fs.mEnabled || !fs.mSupported || (fs.mLastMajority != mLastReport))
        return false;

    if (fs.mFirstMajority == mFirstReport)
    {
        // had a majority when we first started the server, relaxed check
        // WRITEME
    }

    // didn't have a majority when we first started the server, normal check
    return (fs.mLastMajority - fs.mFirstMajority) > mMajorityTime;

}

Features::featureList_t Features::getFeaturesToEnable (uint32 closeTime)
{
    featureList_t ret;
    boost::mutex::scoped_lock sl (mMutex);

    if (mLastReport != 0)
    {
        BOOST_FOREACH (const featureIt_t & it, mFeatureMap)
        {
            if (shouldEnable (closeTime, it.second))
                ret.insert (it.first);
        }
    }

    return ret;
}

Features::featureList_t Features::getDesiredFeatures ()
{
    featureList_t ret;
    boost::mutex::scoped_lock sl (mMutex);
    BOOST_FOREACH (const featureIt_t & it, mFeatureMap)
    {
        if (it.second.mSupported && !it.second.mEnabled && !it.second.mVetoed)
            ret.insert (it.first);
    }
    return ret;
}

void Features::reportValidations (const FeatureSet& set)
{
    if (set.mTrustedValidations == 0)
        return;

    int threshold = (set.mTrustedValidations * mMajorityFraction) / 256;

    typedef std::map<uint256, int>::value_type u256_int_pair;

    boost::mutex::scoped_lock sl (mMutex);

    if (mFirstReport == 0)
        mFirstReport = set.mCloseTime;

    std::vector<uint256> changedFeatures;
    changedFeatures.resize (set.mVotes.size ());

    BOOST_FOREACH (const u256_int_pair & it, set.mVotes)
    {
        FeatureState& state = mFeatureMap[it.first];
        WriteLog (lsDEBUG, Features) << "Feature " << it.first.GetHex () << " has " << it.second << " votes, needs " << threshold;

        if (it.second >= threshold)
        {
            // we have a majority
            state.mLastMajority = set.mCloseTime;

            if (state.mFirstMajority == 0)
            {
                WriteLog (lsWARNING, Features) << "Feature " << it.first << " attains a majority vote";
                state.mFirstMajority = set.mCloseTime;
                changedFeatures.push_back (it.first);
            }
        }
        else // we have no majority
        {
            if (state.mFirstMajority != 0)
            {
                WriteLog (lsWARNING, Features) << "Feature " << it.first << " loses majority vote";
                state.mFirstMajority = 0;
                state.mLastMajority = 0;
                changedFeatures.push_back (it.first);
            }
        }
    }
    mLastReport = set.mCloseTime;

    if (!changedFeatures.empty ())
    {
        ScopedLock sl (getApp().getWalletDB ()->getDBLock ());
        Database* db = getApp().getWalletDB ()->getDB ();

        db->executeSQL ("BEGIN TRANSACTION;");
        BOOST_FOREACH (uint256 const & hash, changedFeatures)
        {
            FeatureState& fState = mFeatureMap[hash];
            db->executeSQL (boost::str (boost::format (
                                            "UPDATE Features SET FirstMajority = %d WHERE Hash = '%s';"
                                        ) % fState.mFirstMajority % hash.GetHex ()));
            db->executeSQL (boost::str (boost::format (
                                            "UPDATE Features SET LastMajority = %d WHERE Hash = '%s';"
                                        ) % fState.mLastMajority % hash.GetHex ()));
        }
        db->executeSQL ("END TRANSACTION;");
        changedFeatures.clear ();
    }
}

void Features::setEnabledFeatures (const std::vector<uint256>& features)
{
    boost::mutex::scoped_lock sl (mMutex);
    BOOST_FOREACH (featureIt_t & it, mFeatureMap)
    {
        it.second.mEnabled = false;
    }
    BOOST_FOREACH (uint256 const & it, features)
    {
        mFeatureMap[it].mEnabled = true;
    }
}

void Features::setSupportedFeatures (const std::vector<uint256>& features)
{
    boost::mutex::scoped_lock sl (mMutex);
    BOOST_FOREACH (featureIt_t & it, mFeatureMap)
    {
        it.second.mSupported = false;
    }
    BOOST_FOREACH (uint256 const & it, features)
    {
        mFeatureMap[it].mSupported = true;
    }
}

void Features::doValidation (Ledger::ref lastClosedLedger, STObject& baseValidation)
{
    featureList_t lFeatures = getDesiredFeatures ();

    if (lFeatures.empty ())
        return;

    STVector256 vFeatures (sfFeatures);
    BOOST_FOREACH (uint256 const & uFeature, lFeatures)
    {
        vFeatures.addValue (uFeature);
    }
    vFeatures.sort ();
    baseValidation.setFieldV256 (sfFeatures, vFeatures);
}

void Features::doVoting (Ledger::ref lastClosedLedger, SHAMap::ref initialPosition)
{
    featureList_t lFeatures = getFeaturesToEnable (lastClosedLedger->getCloseTimeNC ());

    if (lFeatures.empty ())
        return;

    BOOST_FOREACH (uint256 const & uFeature, lFeatures)
    {
        WriteLog (lsWARNING, Features) << "Voting for feature: " << uFeature;
        SerializedTransaction trans (ttFEATURE);
        trans.setFieldAccount (sfAccount, uint160 ());
        trans.setFieldH256 (sfFeature, uFeature);
        uint256 txID = trans.getTransactionID ();
        WriteLog (lsWARNING, Features) << "Vote ID: " << txID;

        Serializer s;
        trans.add (s, true);

        SHAMapItem::pointer tItem = boost::make_shared<SHAMapItem> (txID, s.peekData ());

        if (!initialPosition->addGiveItem (tItem, true, false))
        {
            WriteLog (lsWARNING, Features) << "Ledger already had feature transaction";
        }
    }
}

Json::Value Features::getJson (int)
{
    Json::Value ret (Json::objectValue);
    {
        boost::mutex::scoped_lock sl (mMutex);
        BOOST_FOREACH (const featureIt_t & it, mFeatureMap)
        {
            setJson (ret[it.first.GetHex ()] = Json::objectValue, it.second);
        }
    }
    return ret;
}

void Features::setJson (Json::Value& v, const FeatureState& fs)
{
    if (!fs.mFriendlyName.empty ())
        v["name"] = fs.mFriendlyName;

    v["supported"] = fs.mSupported;
    v["vetoed"] = fs.mVetoed;

    if (fs.mEnabled)
        v["enabled"] = true;
    else
    {
        v["enabled"] = false;

        if (mLastReport != 0)
        {
            if (fs.mLastMajority == 0)
            {
                v["majority"] = false;
            }
            else
            {
                if (fs.mFirstMajority != 0)
                {
                    if (fs.mFirstMajority == mFirstReport)
                        v["majority_start"] = "start";
                    else
                        v["majority_start"] = fs.mFirstMajority;
                }

                if (fs.mLastMajority != 0)
                {
                    if (fs.mLastMajority == mLastReport)
                        v["majority_until"] = "now";
                    else
                        v["majority_until"] = fs.mLastMajority;
                }
            }
        }
    }

    if (fs.mVetoed)
        v["veto"] = true;
}

Json::Value Features::getJson (uint256 const& feature)
{
    Json::Value ret = Json::objectValue;
    boost::mutex::scoped_lock sl (mMutex);
    setJson (ret[feature.GetHex ()] = Json::objectValue, *getCreateFeature (feature, true));
    return ret;
}

template<typename INT> class VotableInteger
{
protected:
    INT                     mCurrent;       // The current setting
    INT                     mTarget;        // The setting we want
    std::map<INT, int>      mVoteMap;

public:
    VotableInteger (INT current, INT target) : mCurrent (current), mTarget (target)
    {
        ++mVoteMap[mTarget];                // Add our vote
    }

    bool mayVote ()
    {
        return mCurrent != mTarget;         // If we love the current setting, we will not vote
    }

    void addVote (INT vote)
    {
        ++mVoteMap[vote];
    }

    void noVote ()
    {
        addVote (mCurrent);
    }

    INT getVotes ()
    {
        INT ourVote = mCurrent;
        int weight = 0;

        typedef typename std::map<INT, int>::value_type mapVType;
        BOOST_FOREACH (const mapVType & value, mVoteMap)
        {
            // Take most voted value between current and target, inclusive
            // FIXME: Should take best value that can get a significant majority
            if ((value.first <= std::max (mTarget, mCurrent)) &&
                    (value.first >= std::min (mTarget, mCurrent)) &&
                    (value.second > weight))
            {
                ourVote = value.first;
                weight = value.second;
            }
        }

        return ourVote;
    }
};

IFeatures* IFeatures::New (uint32 majorityTime, int majorityFraction)
{
    return new Features (majorityTime, majorityFraction);
}

// vim:ts=4
