// Copyright (c) 2014-2017 The Dash Core developers
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include "darksend.h"
#include "governance-vote.h"
#include "masternodeman.h"
#include "util.h"

#include <boost/lexical_cast.hpp>

std::string CGovernanceVoting::ConvertOutcomeToString(vote_outcome_enum_t nOutcome)
{
    switch(nOutcome)
    {
        case VOTE_OUTCOME_NONE:
            return "NONE"; break;
        case VOTE_OUTCOME_YES:
            return "YES"; break;
        case VOTE_OUTCOME_NO:
            return "NO"; break;
        case VOTE_OUTCOME_ABSTAIN:
            return "ABSTAIN"; break;
    }
    return "error";
}

std::string CGovernanceVoting::ConvertSignalToString(vote_signal_enum_t nSignal)
{
    string strReturn = "NONE";
    switch(nSignal)
    {
        case VOTE_SIGNAL_NONE:
            strReturn = "NONE";
            break;
        case VOTE_SIGNAL_FUNDING:
            strReturn = "FUNDING";
            break;
        case VOTE_SIGNAL_VALID:
            strReturn = "VALID";
            break;
        case VOTE_SIGNAL_DELETE:
            strReturn = "DELETE";
            break;
        case VOTE_SIGNAL_ENDORSED:
            strReturn = "ENDORSED";
            break;
        case VOTE_SIGNAL_NOOP1:
            strReturn = "NOOP1";
            break;
        case VOTE_SIGNAL_NOOP2:
            strReturn = "NOOP2";
            break;
        case VOTE_SIGNAL_NOOP3:
            strReturn = "NOOP3";
            break;
        case VOTE_SIGNAL_NOOP4:
            strReturn = "NOOP4";
            break;
        case VOTE_SIGNAL_NOOP5:
            strReturn = "NOOP5";
            break;
        case VOTE_SIGNAL_NOOP6:
            strReturn = "NOOP6";
            break;
        case VOTE_SIGNAL_NOOP7:
            strReturn = "NOOP7";
            break;
        case VOTE_SIGNAL_NOOP8:
            strReturn = "NOOP8";
            break;
        case VOTE_SIGNAL_NOOP9:
            strReturn = "NOOP9";
            break;
        case VOTE_SIGNAL_NOOP10:
            strReturn = "NOOP10";
            break;
        case VOTE_SIGNAL_NOOP11:
            strReturn = "NOOP11";
            break;
        case VOTE_SIGNAL_CUSTOM1:
            strReturn = "CUSTOM1";
            break;
        case VOTE_SIGNAL_CUSTOM2:
            strReturn = "CUSTOM2";
            break;
        case VOTE_SIGNAL_CUSTOM3:
            strReturn = "CUSTOM3";
            break;
        case VOTE_SIGNAL_CUSTOM4:
            strReturn = "CUSTOM4";
            break;
        case VOTE_SIGNAL_CUSTOM5:
            strReturn = "CUSTOM5";
            break;
        case VOTE_SIGNAL_CUSTOM6:
            strReturn = "CUSTOM6";
            break;
        case VOTE_SIGNAL_CUSTOM7:
            strReturn = "CUSTOM7";
            break;
        case VOTE_SIGNAL_CUSTOM8:
            strReturn = "CUSTOM8";
            break;
        case VOTE_SIGNAL_CUSTOM9:
            strReturn = "CUSTOM9";
            break;
        case VOTE_SIGNAL_CUSTOM10:
            strReturn = "CUSTOM10";
            break;
        case VOTE_SIGNAL_CUSTOM11:
            strReturn = "CUSTOM11";
            break;
        case VOTE_SIGNAL_CUSTOM12:
            strReturn = "CUSTOM12";
            break;
        case VOTE_SIGNAL_CUSTOM13:
            strReturn = "CUSTOM13";
            break;
        case VOTE_SIGNAL_CUSTOM14:
            strReturn = "CUSTOM14";
            break;
        case VOTE_SIGNAL_CUSTOM15:
            strReturn = "CUSTOM15";
            break;
        case VOTE_SIGNAL_CUSTOM16:
            strReturn = "CUSTOM16";
            break;
        case VOTE_SIGNAL_CUSTOM17:
            strReturn = "CUSTOM17";
            break;
        case VOTE_SIGNAL_CUSTOM18:
            strReturn = "CUSTOM18";
            break;
        case VOTE_SIGNAL_CUSTOM19:
            strReturn = "CUSTOM19";
            break;
        case VOTE_SIGNAL_CUSTOM20:
            strReturn = "CUSTOM20";
            break;
    }

    return strReturn;
}


vote_outcome_enum_t CGovernanceVoting::ConvertVoteOutcome(std::string strVoteOutcome)
{
    vote_outcome_enum_t eVote = VOTE_OUTCOME_NONE;
    if(strVoteOutcome == "yes") {
        eVote = VOTE_OUTCOME_YES;
    }
    else if(strVoteOutcome == "no") {
        eVote = VOTE_OUTCOME_NO;
    }
    else if(strVoteOutcome == "abstain") {
        eVote = VOTE_OUTCOME_ABSTAIN;
    }
    return eVote;
}

vote_signal_enum_t CGovernanceVoting::ConvertVoteSignal(std::string strVoteSignal)
{
    vote_signal_enum_t eSignal = VOTE_SIGNAL_NONE;
    if(strVoteSignal == "funding") {
        eSignal = VOTE_SIGNAL_FUNDING;
    }
    else if(strVoteSignal == "valid") {
        eSignal = VOTE_SIGNAL_VALID;
    }
    if(strVoteSignal == "delete") {
        eSignal = VOTE_SIGNAL_DELETE;
    }
    if(strVoteSignal == "endorsed") {
        eSignal = VOTE_SIGNAL_ENDORSED;
    }

    if(eSignal != VOTE_SIGNAL_NONE)  {
        return eSignal;
    }

    // ID FIVE THROUGH CUSTOM_START ARE TO BE USED BY GOVERNANCE ENGINE / TRIGGER SYSTEM

    // convert custom sentinel outcomes to integer and store
    try {
        int i = boost::lexical_cast<int>(strVoteSignal);
        if(i < VOTE_SIGNAL_CUSTOM1 || i > VOTE_SIGNAL_CUSTOM20) {
            eSignal = VOTE_SIGNAL_NONE;
        }
        else  {
            eSignal = vote_signal_enum_t(i);
        }
    }
    catch(std::exception const & e)
    {
        std::ostringstream ostr;
        ostr << "CGovernanceVote::ConvertVoteSignal: error : " << e.what() << std::endl;
        LogPrintf(ostr.str().c_str());
    }

    return eSignal;
}

CGovernanceVote::CGovernanceVote()
    : fValid(true),
      fSynced(false),
      nVoteSignal(int(VOTE_SIGNAL_NONE)),
      vinMasternode(),
      nParentHash(),
      nVoteOutcome(int(VOTE_OUTCOME_NONE)),
      nTime(0),
      vchSig()
{}

CGovernanceVote::CGovernanceVote(CTxIn vinMasternodeIn, uint256 nParentHashIn, vote_signal_enum_t eVoteSignalIn, vote_outcome_enum_t eVoteOutcomeIn)
    : fValid(true),
      fSynced(false),
      nVoteSignal(eVoteSignalIn),
      vinMasternode(vinMasternodeIn),
      nParentHash(nParentHashIn),
      nVoteOutcome(eVoteOutcomeIn),
      nTime(GetAdjustedTime()),
      vchSig()
{}

void CGovernanceVote::Relay() const
{
    CInv inv(MSG_GOVERNANCE_OBJECT_VOTE, GetHash());
    RelayInv(inv, PROTOCOL_VERSION);
}

bool CGovernanceVote::Sign(CKey& keyMasternode, CPubKey& pubKeyMasternode)
{
    // Choose coins to use
    CPubKey pubKeyCollateralAddress;
    CKey keyCollateralAddress;

    std::string strError;
    std::string strMessage = vinMasternode.prevout.ToStringShort() + "|" + nParentHash.ToString() + "|" +
        boost::lexical_cast<std::string>(nVoteSignal) + "|" + boost::lexical_cast<std::string>(nVoteOutcome) + "|" + boost::lexical_cast<std::string>(nTime);

    if(!darkSendSigner.SignMessage(strMessage, vchSig, keyMasternode)) {
        LogPrintf("CGovernanceVote::Sign -- SignMessage() failed\n");
        return false;
    }

    if(!darkSendSigner.VerifyMessage(pubKeyMasternode, vchSig, strMessage, strError)) {
        LogPrintf("CGovernanceVote::Sign -- VerifyMessage() failed, error: %s\n", strError);
        return false;
    }

    return true;
}

bool CGovernanceVote::IsValid(bool fSignatureCheck) const
{
    if(nTime > GetTime() + (60*60)) {
        LogPrint("gobject", "CGovernanceVote::IsValid -- vote is too far ahead of current time - %s - nTime %lli - Max Time %lli\n", GetHash().ToString(), nTime, GetTime() + (60*60));
        return false;
    }

    // support up to 50 actions (implemented in sentinel)
    if(nVoteSignal > MAX_SUPPORTED_VOTE_SIGNAL)
    {
        LogPrint("gobject", "CGovernanceVote::IsValid -- Client attempted to vote on invalid signal(%d) - %s\n", nVoteSignal, GetHash().ToString());
        return false;
    }

    // 0=none, 1=yes, 2=no, 3=abstain. Beyond that reject votes
    if(nVoteOutcome > 3)
    {
        LogPrint("gobject", "CGovernanceVote::IsValid -- Client attempted to vote on invalid outcome(%d) - %s\n", nVoteSignal, GetHash().ToString());
        return false;
    }

    masternode_info_t infoMn = mnodeman.GetMasternodeInfo(vinMasternode);
    if(!infoMn.fInfoValid) {
        LogPrint("gobject", "CGovernanceVote::IsValid -- Unknown Masternode - %s\n", vinMasternode.prevout.ToStringShort());
        return false;
    }

    if(!fSignatureCheck) return true;

    std::string strError;
    std::string strMessage = vinMasternode.prevout.ToStringShort() + "|" + nParentHash.ToString() + "|" +
        boost::lexical_cast<std::string>(nVoteSignal) + "|" + boost::lexical_cast<std::string>(nVoteOutcome) + "|" + boost::lexical_cast<std::string>(nTime);

    if(!darkSendSigner.VerifyMessage(infoMn.pubKeyMasternode, vchSig, strMessage, strError)) {
        LogPrintf("CGovernanceVote::IsValid -- VerifyMessage() failed, error: %s\n", strError);
        return false;
    }

    return true;
}

bool operator==(const CGovernanceVote& vote1, const CGovernanceVote& vote2)
{
    bool fResult = ((vote1.vinMasternode == vote2.vinMasternode) &&
                    (vote1.nParentHash == vote2.nParentHash) &&
                    (vote1.nVoteOutcome == vote2.nVoteOutcome) &&
                    (vote1.nVoteSignal == vote2.nVoteSignal) &&
                    (vote1.nTime == vote2.nTime));
    return fResult;
}

bool operator<(const CGovernanceVote& vote1, const CGovernanceVote& vote2)
{
    bool fResult = (vote1.vinMasternode < vote2.vinMasternode);
    if(!fResult) {
        return false;
    }
    fResult = (vote1.vinMasternode == vote2.vinMasternode);

    fResult = fResult && (vote1.nParentHash < vote2.nParentHash);
    if(!fResult) {
        return false;
    }
    fResult = fResult && (vote1.nParentHash == vote2.nParentHash);

    fResult = fResult && (vote1.nVoteOutcome < vote2.nVoteOutcome);
    if(!fResult) {
        return false;
    }
    fResult = fResult && (vote1.nVoteOutcome == vote2.nVoteOutcome);

    fResult = fResult && (vote1.nVoteSignal == vote2.nVoteSignal);
    if(!fResult) {
        return false;
    }
    fResult = fResult && (vote1.nVoteSignal == vote2.nVoteSignal);

    fResult = fResult && (vote1.nTime < vote2.nTime);

    return fResult;
}
