contract EQUUSGovernanceToken is IERC20, Staking, IERC777, ERC1820Client {
    
    /* Constant variables created for the ERC20 requirements*/
    
    string public override constant name = "EQUUSGovernanceToken";
    string public override constant symbol = "EQGT";
    uint8 public constant decimals = 18;
    
    //Burn address saved as constant for future burning processes
    address public constant burnaddress = 0x0000000000000000000000000000000000000000;
    IERC20 public uniaddress;

    mapping(address => uint256) balances; //EQUUS balance for all network participants
    
    mapping(address => uint256) public stakedbalances; //EQUUS stake balance to lock stakes
    
    mapping(address => uint256) public stakeMultipliers; //EQUUS stake multiplier per user staking
    
    mapping(address => uint256) public stakeMultipliersMax; //EQUUS stake multiplier per user staking
    
    mapping(address => uint) public staketimestamps; //EQUUS stake timestamp to record updates on staking for multipliers, this involves the idea that multipliers will reset upon staking

    mapping(address => mapping (address => uint256)) allowed; //Approval array to record delegation of thrid-party accounts to handle transaction per allowance
    
    
    /* Total variables created to record information */
    uint256 totalSupply_ = 0;
    uint256 totalstaked = 0;
    uint256 granularity_ = 1;
    
    // Set of arrays to hold operator authorization
    //address[] internal defaultOperators_;
    mapping(address => bool) internal isDefaultOperator_;
    mapping(address => mapping(address => bool)) internal revokedDefaultOperator_;
    mapping(address => mapping(address => bool)) authorizedOperator_;
    

    address theowner; //Owner address saved to recognise on future processes
    
    using SafeMath for uint256; //Important*** as this library provides security to handle maths without overflow attacks
    
    constructor(IERC20 tokenaddress) public {
        uniaddress = tokenaddress;
        theowner = msg.sender;
        setInterfaceImplementation("ERC777Token", address(this));
   } //Constructor stating the total supply as well as saving owner address
   
   
   
   //ERC777 functions ========================
   
   function granularity() public override view returns (uint256) {
       return granularity_;
   }
   
   //function defaultOperators() public override view returns (address[] memory) {
   //    return defaultOperators_;
   //}
   
   function setDefaultOperators(address addr) public returns (bool) {
       require(theowner != addr, "Owner address cannot be handled");
       require(theowner == msg.sender, "Only theowner can set default operators");
       
        isDefaultOperator_[addr] = true;
        emit AuthorizedOperator(addr, msg.sender);
   }
   
   function revokeDefaultOperators(address addr) public returns (bool) {
        require(theowner != addr, "Owner address cannot be revoked");
        
        isDefaultOperator_[addr] = false;
        emit RevokedOperator(addr, msg.sender);
   }
   
   function isOperatorFor( address operator_, address holder_) public override view returns (bool) {
        return (operator_ == holder_ || 
        authorizedOperator_[operator_][holder_] ||
        (isDefaultOperator_[operator_] &&
        !revokedDefaultOperator_[operator_][holder_]));
    }
    
    function authorizeOperator(address operator) public override {
        require(operator != msg.sender, "You can't authorize yourself");
        if (isDefaultOperator_[operator]) {
            revokedDefaultOperator_[operator][msg.sender] = false;
        } else {
            authorizedOperator_[operator][msg.sender] = true;
        }
        emit AuthorizedOperator(operator, msg.sender);
    }
    
    function revokeOperator(address operator) public override {
        require(operator != msg.sender, "You can't revoke yourself");
        if (isDefaultOperator_[operator]) {
            revokedDefaultOperator_[operator][msg.sender] = true;
        } else {
            authorizedOperator_[operator][msg.sender] = false;
        }
        emit RevokedOperator(operator, msg.sender);
    }
    
    function burn(uint256 amount, bytes memory data) public override {
        burning(msg.sender, msg.sender, amount, data, "");
    }
    
    function operatorBurn(
        address from,
        uint256 amount,
        bytes memory data,
        bytes memory operatorData
    ) public override {
        require(isOperatorFor(msg.sender, from), "Not an Operator");
        burning(msg.sender, from, amount, data, operatorData);
    }
    
    function burning(address operator, address from, uint256 amount, bytes memory data, bytes memory operatorData) internal {
        require(balances[from] >= amount, "Not enought funds");
        require(stakedbalances[from] != 0, "Nothing staked to mine for");
        require(stakeMultipliers[from] <= 500, "Max burn multiplier reached");
        require( getPercentageWithFive( stakeMultipliers[from] ) .add( getPercentageFromMax( amount, stakeMultipliersMax[from] ) ) <= 100 , "Too much to burn");
        
        balances[from] = balances[from].sub(amount);
        
        totalSupply_ = totalSupply_.sub(amount);
        
        
        uint256 multiplier = getPercentageFromMax(amount, stakeMultipliersMax[from]).mul(5);
        
        stakeMultipliers[from] = stakeMultipliers[from].add(multiplier);
        
        emit Burned(operator, from, amount, data, operatorData);
        emit Sent(operator, from, burnaddress, amount, data, operatorData);
    }
    
    
    
    function getPercentageFromMax(uint256 amount, uint256 maxForFuntion) public pure returns (uint256) {
        uint256 onePercent = maxForFuntion.div(100);
        return amount.div(onePercent);
    }
    
    function getPercentageWithFive(uint256 amount) public pure returns (uint256) {
        return amount.div(5);
    }
    
    
    function send(address to, uint256 amount, bytes memory data) public override {
        sending(msg.sender, msg.sender, to, amount, data, "", true);
    }
    
    function operatorSend(
        address from,
        address to,
        uint256 amount,
        bytes memory data,
        bytes memory operatorData
    ) public override {
        require(isOperatorFor(msg.sender, from), "Not an operator.");
        sending(msg.sender, from, to, amount, data, operatorData, true);
    }
    
    // ERC1820 implementer and send function ========================
    
    function sending(address operator,
        address from,
        address to,
        uint256 amount,
        bytes memory data,
        bytes memory operatorData,
        bool preventLocking) internal {
        
        
        checkSender(operator, from, to, amount, data, operatorData);
        
        require(to != burnaddress, "Can't send to 0x0");
        require(balances[from] >= amount, "Insufficient funds");
        
        balances[from] = balances[from].sub(amount);
        balances[to] = balances[to].add(amount);
        
        checkReceiver(operator, from, to, amount, data, operatorData, preventLocking);
        
        emit Sent(operator, from, to, amount, data, operatorData);
        
    }
    
    function checkSender(address operator, address from, address to, uint256 amount, bytes memory data, bytes memory operatorData) internal {
        address sender = interfaceAddr(from, "ERC777TokensSender");
        require(sender != burnaddress);
        IERC777Sender(sender).tokensToSend(
            operator, from, to, amount, data, operatorData);
    }
    
    function checkReceiver(address operator, address from, address to, uint256 amount, bytes memory data, bytes memory operatorData, bool preventLocking) internal {
        address recipient = interfaceAddr(to, "ERC777TokensRecipient");
        if (recipient != burnaddress) {
            IERC777Recipient(recipient).tokensReceived(
                operator, from, to, amount, data, operatorData);
        } else if (preventLocking) {
            require(isNormalAddress(to), "Cannot send to contract without ERC777TokensRecipient");
        }
    }
    
    function isNormalAddress(address addr) internal view returns(bool) {
        if (addr == burnaddress) { return false; }
        uint size;
        assembly { size := extcodesize(addr) } // solium-disable-line security/no-inline-assembly
        return size == 0;
    }
    
    //ERC20 implementation ========================
   
   //Function to report on totalsupply following ERC20 Standard
   function totalSupply() public override(IERC20, IERC777) view returns (uint256) {
       return totalSupply_;
   }
   
   //Function to report on account balance following ERC20 Standard
   function balanceOf(address tokenOwner) public override(IERC20, IERC777) view returns (uint) {
       return balances[tokenOwner];
   }
   
   //Function to report on account balance following ERC20 Standard
   function uniBalance(address tokenOwner) public view returns (uint) {
       return uniaddress.balanceOf(tokenOwner);
   }
   
   //Straight forward transfer following ERC20 Standard
   function transfer(address receiver, uint256 numTokens) public override returns (bool) {
       require(numTokens <= balances[msg.sender], 'Amount exceeds balance.');
       balances[msg.sender] = balances[msg.sender].sub(numTokens);
       
       balances[receiver] = balances[receiver].add(numTokens);
       emit Transfer(msg.sender, receiver, numTokens);
       return true;
   }
   
   //Approve function following ERC20 Standard
   function approve(address delegate, uint256 numTokens) public override returns (bool) {
       require(numTokens <= balances[msg.sender], 'Amount exceeds balance.');
       allowed[msg.sender][delegate] = numTokens;
       emit Approval(msg.sender, delegate, numTokens);
       return true;
   }
   
   //Allowance function to verify allowance allowed on delegate address following ERC20 Standard
   function allowance(address owner, address delegate) public override view returns (uint) {
       return allowed[owner][delegate];
   }
   
   //The following function is added to mitigate ERC20 API: An Attack Vector on Approve/TransferFrom Methods
   function increaseAllowance(address spender, uint256 addedValue) public returns (bool) {
       require(addedValue <= balances[msg.sender].sub(allowed[msg.sender][spender]), 'Amount exceeds balance.');
       
       allowed[msg.sender][spender] = allowed[msg.sender][spender].add(addedValue);
       
       emit Approval(msg.sender, spender, allowed[msg.sender][spender].add(addedValue));
       return true;
   }
   
   //The following function is added to mitigate ERC20 API: An Attack Vector on Approve/TransferFrom Methods
   function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) {
       require(subtractedValue <= allowed[msg.sender][spender], 'Amount exceeds balance.');
       
       allowed[msg.sender][spender] = allowed[msg.sender][spender].sub(subtractedValue);
       
       emit Approval(msg.sender, spender, allowed[msg.sender][spender].sub(subtractedValue));
       return true;
   }
   
   //Transfer For function for allowed accounts to allow tranfers
   function transferFrom(address owner, address buyer, uint numTokens) public override returns (bool) {
       require(numTokens <= balances[owner], 'Amount exceeds balance.');
       require(numTokens <= allowed[owner][msg.sender], 'Amount exceeds allowance.');
       
       balances[owner] = balances[owner].sub(numTokens);
       allowed[owner][msg.sender] = allowed[owner][msg.sender].sub(numTokens);
       balances[buyer] = balances[buyer].add(numTokens);
       emit Transfer(msg.sender, buyer, numTokens);
       return true;
   }
   
   //Staking processes ========================
   
   
   
   //Stake process created updating balances, stakebalances and also recording time on process run,
   function stake(uint256 amount, bytes memory data) public override returns (bool) {
       require(amount >= 105000);
       require(amount <= uniBalance(msg.sender));
       require(amount <= uniaddress.allowance(msg.sender, address(this)));
       
       //So add the user stake process into the data base
       
       uniaddress.transferFrom(msg.sender, address(this), amount);
       
       stakedbalances[msg.sender] = stakedbalances[msg.sender].add(amount);
       totalstaked = totalstaked.add(amount);
       staketimestamps[msg.sender] = block.timestamp;
       stakeMultipliers[msg.sender] = 100;
       
       //operation = = = ((((staked*744)*(7.13/744))/1000)*5000)
       uint256 max = amount;
       max = max.mul(744);
       max = max.mul(9583).div(1000000);
       
       max = max.div(1000);
       max = max.mul(5000);
       
       stakeMultipliersMax[msg.sender] = max; //Max amount of tokens to calculate the multiplier from
       
       uint256 total = stakedbalances[msg.sender];
       
       emit Staked(msg.sender, amount, total, data);
       return true;
   }
   
   //This function unstakes locked in amount, this also updates amounts on total supply
   function unstake(uint256 amount, bytes memory data) public override returns (bool) {
       require(amount <= stakedbalances[msg.sender]);
       require(amount <= totalstaked);
       
       stakedbalances[msg.sender] = stakedbalances[msg.sender].sub(amount);
       totalstaked = totalstaked.sub(amount);
       staketimestamps[msg.sender] = block.timestamp;
       stakeMultipliers[msg.sender] = 100;
       stakeMultipliersMax[msg.sender] = 0;
       
       uniaddress.transfer(msg.sender, amount);// Send tokens back to the holder
       
       emit Unstaked(msg.sender, amount, stakedbalances[msg.sender], data);
       return true;
   }
    
    
    // Minting operation
    
    //(((staked*hoursStaked)*(7.13/744))*rankMultiplier)*BBM =================
    
    
    function _mint(bytes memory data) public {
        require(stakedbalances[msg.sender] != 0, "Nothing staked to mine for");
        
        
        uint256 amount = operate(stakedbalances[msg.sender]);
        
        stakeMultipliers[msg.sender] = 100;
        staketimestamps[msg.sender] = block.timestamp;
        balances[msg.sender] = balances[msg.sender].add(amount);
        totalSupply_ = totalSupply_.add(amount);
        
        emit Minted(burnaddress, msg.sender, amount, data, data);
        emit Sent(burnaddress, burnaddress, msg.sender, amount, data, data);
        
    }
    
    function operate(uint256 number) public view returns (uint256) {
        uint256 amount = number;
        
        amount = amount.mul((stakeTimeFor(msg.sender).div(60)).div(60));
        
        require(amount > 104400, "Not enough time to mine tokens");
        
        //Sets a minimun to mine after the next operation to 1000 tokens
        
        amount = amount.mul(9583).div(1000000);
        
        if ((stakeTimeFor(msg.sender).div(60)).div(60) > 744) {
            amount = amount.mul(2);
        }
        uint256 multiplier = stakeMultipliers[msg.sender];
        amount = (amount.mul(multiplier)).div(100);
        return amount;
    }
   
   //Function to return multipliers
   function stakeMultiplierFor(address addr) public view returns (uint256) {
       return stakeMultipliers[addr];
   }
   
   //Function to return total staked on a single address
   function totalStakedFor(address addr) public override view returns (uint256) {
       return stakedbalances[addr];
   }
   
   //Function to shows timestamp on stake processes
   function stakeTimestampFor(address addr) public view returns (uint256) {
       return staketimestamps[addr];
   }
   
   //Function to find out time passed since last timestamp on address
   function stakeTimeFor(address addr) public view returns (uint256) {
       return block.timestamp.sub(staketimestamps[addr]);
   }
   
   //Total staked on all addresses
   function totalStaked() public override view returns (uint256) {
       return totalstaked;
   }
   
   //Support History variable to show support on optional stake details
   function supportsHistory() public override pure returns (bool) {
       return false;
   }
   
}
/*EQUUS Protocol being created with the help of the above interfaces for compatibility*/
function totalStakedFor(address addr) public override view returns (uint256) {
    return stakedbalances[addr];
}

//Function to return total staked on a single address