#include "addrman.h"
#include "protocol.h"
#include "activesystemnode.h"
#include "systemnodeman.h"
#include "systemnode.h"
#include "spork.h"

CActiveSystemnode activeSystemnode;

//
// Bootup the Systemnode, look for a 10000 CRW input and register on the network
//
void CActiveSystemnode::ManageStatus()
{    
    std::string errorMessage;

    if(!fSystemNode) return;

    if (fDebug) LogPrintf("CActiveSystemnode::ManageStatus() - Begin\n");

    //need correct blocks to send ping
    if(Params().NetworkID() != CBaseChainParams::REGTEST && !systemnodeSync.IsBlockchainSynced()) {
        status = ACTIVE_SYSTEMNODE_SYNC_IN_PROCESS;
        LogPrintf("CActiveSystemnode::ManageStatus() - %s\n", GetStatus());
        return;
    }

    if(status == ACTIVE_SYSTEMNODE_SYNC_IN_PROCESS) status = ACTIVE_SYSTEMNODE_INITIAL;

    if(status == ACTIVE_SYSTEMNODE_INITIAL) {
        CSystemnode *pmn;
        pmn = snodeman.Find(pubKeySystemnode);
        if(pmn != NULL) {
            pmn->Check();
            if(pmn->IsEnabled() && pmn->protocolVersion == PROTOCOL_VERSION) EnableHotColdSystemNode(pmn->vin, pmn->addr);
        }
    }

    if(status != ACTIVE_SYSTEMNODE_STARTED) {

        // Set defaults
        status = ACTIVE_SYSTEMNODE_NOT_CAPABLE;
        notCapableReason = "";

        if(pwalletMain->IsLocked()){
            notCapableReason = "Wallet is locked.";
            LogPrintf("CActiveSystemnode::ManageStatus() - not capable: %s\n", notCapableReason);
            return;
        }

        if(pwalletMain->GetBalance() == 0){
            notCapableReason = "Hot node, waiting for remote activation.";
            LogPrintf("CActiveSystemnode::ManageStatus() - not capable: %s\n", notCapableReason);
            return;
        }

        if(strSystemNodeAddr.empty()) {
            if(!GetLocal(service)) {
                notCapableReason = "Can't detect external address. Please use the systemnodeaddr configuration option.";
                LogPrintf("CActiveSystemnode::ManageStatus() - not capable: %s\n", notCapableReason);
                return;
            }
        } else {
            service = CService(strSystemNodeAddr);
        }

        if(Params().NetworkID() == CBaseChainParams::MAIN) {
            if(service.GetPort() != 9340) {
                notCapableReason = strprintf("Invalid port: %u - only 9340 is supported on mainnet.", service.GetPort());
                LogPrintf("CActiveSystemnode::ManageStatus() - not capable: %s\n", notCapableReason);
                return;
            }
        } else if(service.GetPort() == 9340) {
            notCapableReason = strprintf("Invalid port: %u - 9340 is only supported on mainnet.", service.GetPort());
            LogPrintf("CActiveSystemnode::ManageStatus() - not capable: %s\n", notCapableReason);
            return;
        }

        LogPrintf("CActiveSystemnode::ManageStatus() - Checking inbound connection to '%s'\n", service.ToString());

        if(!ConnectNode((CAddress)service, NULL, false, true)){
            notCapableReason = "Could not connect to " + service.ToString();
            LogPrintf("CActiveSystemnode::ManageStatus() - not capable: %s\n", notCapableReason);
            return;
        }

        // Choose coins to use
        CPubKey pubKeyCollateralAddress;
        CKey keyCollateralAddress;

        if(pwalletMain->GetSystemnodeVinAndKeys(vin, pubKeyCollateralAddress, keyCollateralAddress)) {

            if(GetInputAge(vin) < SYSTEMNODE_MIN_CONFIRMATIONS){
                status = ACTIVE_SYSTEMNODE_INPUT_TOO_NEW;
                notCapableReason = strprintf("%s - %d confirmations", GetStatus(), GetInputAge(vin));
                LogPrintf("CActiveSystemnode::ManageStatus() - %s\n", notCapableReason);
                return;
            }

            LOCK(pwalletMain->cs_wallet);
            pwalletMain->LockCoin(vin.prevout);

            // send to all nodes
            CPubKey pubKeySystemnode;
            CKey keySystemnode;

            if(!legacySigner.SetKey(strSystemNodePrivKey, errorMessage, keySystemnode, pubKeySystemnode))
            {
                notCapableReason = "Error upon calling SetKey: " + errorMessage;
                LogPrintf("Register::ManageStatus() - %s\n", notCapableReason);
                return;
            }

            CSystemnodeBroadcast mnb;
            if(!CSystemnodeBroadcast::Create(vin, service, keyCollateralAddress, pubKeyCollateralAddress, keySystemnode, pubKeySystemnode, errorMessage, mnb)) {
                notCapableReason = "Error on CreateBroadcast: " + errorMessage;
                LogPrintf("Register::ManageStatus() - %s\n", notCapableReason);
                return;
            }

            //update to masternode list
            LogPrintf("CActiveSystemnode::ManageStatus() - Update Systemnode List\n");
            snodeman.UpdateSystemnodeList(mnb);

            //send to all peers
            LogPrintf("CActiveSystemnode::ManageStatus() - Relay broadcast vin = %s\n", vin.ToString());
            mnb.Relay();

            LogPrintf("CActiveSystemnode::ManageStatus() - Is capable master node!\n");
            status = ACTIVE_SYSTEMNODE_STARTED;

            return;
        } else {
            notCapableReason = "Could not find suitable coins!";
            LogPrintf("CActiveSystemnode::ManageStatus() - %s\n", notCapableReason);
            return;
        }
    }

    //send to all peers
    if(!SendSystemnodePing(errorMessage)) {
        LogPrintf("CActiveSystemnode::ManageStatus() - Error on Ping: %s\n", errorMessage);
    }
}

std::string CActiveSystemnode::GetStatus() {
    switch (status) {
    case ACTIVE_SYSTEMNODE_INITIAL: return "Node just started, not yet activated";
    case ACTIVE_SYSTEMNODE_SYNC_IN_PROCESS: return "Sync in progress. Must wait until sync is complete to start Systemnode";
    case ACTIVE_SYSTEMNODE_INPUT_TOO_NEW: return strprintf("Systemnode input must have at least %d confirmations", SYSTEMNODE_MIN_CONFIRMATIONS);
    case ACTIVE_SYSTEMNODE_NOT_CAPABLE: return "Not capable systemnode: " + notCapableReason;
    case ACTIVE_SYSTEMNODE_STARTED: return "Systemnode successfully started";
    default: return "unknown";
    }
}

bool CActiveSystemnode::SendSystemnodePing(std::string& errorMessage) {
    if(status != ACTIVE_SYSTEMNODE_STARTED) {
        errorMessage = "Systemnode is not in a running status";
        return false;
    }

    CPubKey pubKeySystemnode;
    CKey keySystemnode;

    if(!legacySigner.SetKey(strSystemNodePrivKey, errorMessage, keySystemnode, pubKeySystemnode))
    {
        errorMessage = strprintf("Error upon calling SetKey: %s\n", errorMessage);
        return false;
    }

    LogPrintf("CActiveSystemnode::SendSystemnodePing() - Relay Systemnode Ping vin = %s\n", vin.ToString());
    
    CSystemnodePing mnp(vin);
    if(!mnp.Sign(keySystemnode, pubKeySystemnode))
    {
        errorMessage = "Couldn't sign Systemnode Ping";
        return false;
    }

    // Update lastPing for our systemnode in Systemnode list
    CSystemnode* pmn = snodeman.Find(vin);
    if(pmn != NULL)
    {
        if(pmn->IsPingedWithin(SYSTEMNODE_PING_SECONDS, mnp.sigTime)){
            errorMessage = "Too early to send Systemnode Ping";
            return false;
        }

        pmn->lastPing = mnp;
        snodeman.mapSeenSystemnodePing.insert(make_pair(mnp.GetHash(), mnp));

        //snodeman.mapSeenSystemnodeBroadcast.lastPing is probably outdated, so we'll update it
        CSystemnodeBroadcast mnb(*pmn);
        uint256 hash = mnb.GetHash();
        if(snodeman.mapSeenSystemnodeBroadcast.count(hash)) snodeman.mapSeenSystemnodeBroadcast[hash].lastPing = mnp;

        mnp.Relay();

        return true;
    }
    else
    {
        // Seems like we are trying to send a ping while the Systemnode is not registered in the network
        errorMessage = "Systemnode List doesn't include our Systemnode, shutting down Systemnode pinging service! " + vin.ToString();
        status = ACTIVE_SYSTEMNODE_NOT_CAPABLE;
        notCapableReason = errorMessage;
        return false;
    }

}

// when starting a Systemnode, this can enable to run as a hot wallet with no funds
bool CActiveSystemnode::EnableHotColdSystemNode(const CTxIn& newVin, const CService& newService)
{
    if(!fSystemNode) return false;

    status = ACTIVE_SYSTEMNODE_STARTED;

    //The values below are needed for signing mnping messages going forward
    vin = newVin;
    service = newService;

    LogPrintf("CActiveSystemnode::EnableHotColdSystemNode() - Enabled! You may shut down the cold daemon.\n");

    return true;
}