pragma solidity ^0.8.4;





abstract contract Skeletonz_Contract {
   function balanceOf(address owner) virtual public view returns(uint);
   function totalSupply() virtual public view returns(uint);
   function ownerOf(uint tokenId) virtual public view returns(address);
}

abstract contract Skellies_Contract {
   function balanceOf(address owner) virtual public view returns(uint);
   function totalSupply() virtual public view returns(uint);
   function ownerOf(uint tokenId) virtual public view returns(address);
   function isMutantToken(uint256 tokenId) virtual public view returns(bool);
   function timestampOf(uint256 tokenId) virtual public view returns (uint64);
}


contract Skeletonz_GBONEs is ERC20, ReentrancyGuard, AccessControl {

    // Role Creation
    bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");
    bytes32 public constant PROXY_ROLE = keccak256("PROXY_ROLE");

    struct DefaultSchedule {
        uint256 ceilToken;
        uint64 timestamp;
    }

    DefaultSchedule[] private _schedule;

    address _genesisContract;
    uint256 _genesisRate = 173610000000000;

    address _skellieContract;
    uint256 _mutantRate = 347220000000000;
    
    
    mapping(uint256 => uint64) private _genesis_claimTimestamp;
    mapping(uint256 => uint64) private _mutant_claimTimestamp;


    constructor(address genesisContract) ERC20("Skeletonz GBONEs", "GBONE") {

        _setupRole(DEFAULT_ADMIN_ROLE, _msgSender());
        _setupRole(ADMIN_ROLE,_msgSender());

        _genesisContract = genesisContract;

        //set initial default time for first 666 tokens
        _schedule.push(DefaultSchedule({ceilToken: 666, timestamp: 1647772512}));
    }

    //--------------- WRITE FUNCTIONS
    
    function claimGenesisGbones(uint[] memory tokens) public nonReentrant {
	
        // load the contract
        Skeletonz_Contract source_data = Skeletonz_Contract(_genesisContract);

        uint256 totalClaimDelta = 0;

        uint64 claimTimestamp = uint64(block.timestamp);

        for (uint64 i=0; i<tokens.length;i++) {
            if (source_data.ownerOf(tokens[i]) == msg.sender) {
                totalClaimDelta += (claimTimestamp - getClaimTimeForGenesisToken(tokens[i]));
                _genesis_claimTimestamp[tokens[i]] = claimTimestamp;
            }
        }

        _mint(msg.sender, totalClaimDelta * _genesisRate);
    }

    function claimMutantGbones(uint[] memory tokens) public nonReentrant {

        uint256 totalClaimDelta = 0;
        uint64 claimTimestamp = uint64(block.timestamp);
        Skellies_Contract source_data = Skellies_Contract(_skellieContract);

        for (uint64 i=0; i<tokens.length;i++) {
            if (source_data.ownerOf(tokens[i]) == msg.sender && source_data.isMutantToken(tokens[i])) {
                totalClaimDelta += (claimTimestamp - getClaimTimeForSkellieToken(tokens[i]));
                _mutant_claimTimestamp[tokens[i]] = claimTimestamp;
            }
        }
        
        _mint(msg.sender,totalClaimDelta * _mutantRate);
    }

    function setSkellieContract(address skellieContract) external onlyRole(ADMIN_ROLE) {
        _skellieContract = skellieContract;
    }

    function pushSchedule(uint256 ceilToken, uint64 startTimestamp) external onlyRole(ADMIN_ROLE) {

        _schedule.push(DefaultSchedule({ceilToken: ceilToken,timestamp: startTimestamp}));
    }

    function burnGbones(address account, uint256 amount) external onlyRole(PROXY_ROLE) {

        _burn(account, amount);

    }


    //------------------- VIEW FUNCTIONS

    //-------------- GENESIS RELATED METHODS --------------------
    function getClaimTimeForGenesisToken(uint256 tokenId) public view returns (uint64)  {
        
        uint64 claimTimestamp = _genesis_claimTimestamp[tokenId];

        if (claimTimestamp == 0) {
            //cycle through the schedule
            for (uint i=0; i<_schedule.length;i++) {
                if (tokenId < _schedule[i].ceilToken) {
                    claimTimestamp = _schedule[i].timestamp;
                    break;
                }
            }
        }

        return claimTimestamp;
    }

    function getTotalGenesisClaimAmountForOwner(address ownerAddress) public view returns (uint256) {

        uint[] memory tokens = getGenesisIDsByOwner(ownerAddress);
        uint256 totalClaimDelta = 0;

        uint64 claimTimestamp = uint64(block.timestamp);

        for (uint64 i=0; i<tokens.length;i++) {
            totalClaimDelta += (claimTimestamp - getClaimTimeForGenesisToken(tokens[i]));
        }

        return totalClaimDelta * _genesisRate;
    }

    function getGenesisIDsByOwner(address ownerAddress) public view returns (uint[] memory) {

        // load the contract
        Skeletonz_Contract source_data = Skeletonz_Contract(_genesisContract);

        uint[] memory output = new uint[](source_data.balanceOf(ownerAddress)); // set array length to number of tokens caller has

        uint total = source_data.totalSupply();

        uint counter = 0;

        // iterate all tokens
        for (uint i = 0; i < total; i++) {

            // find the ones belonging to ownerAddress
            if (source_data.ownerOf(i) == ownerAddress) {

                // set output to those belonging to ownerAddress
                output[counter] = i;

                counter++;
            }
        }

        return output;
    }

    //------------ SKELLIE RELATED METHODS --------------------
    function getClaimTimeForSkellieToken(uint256 tokenId) public view returns (uint64) {

        Skellies_Contract source_data = Skellies_Contract(_skellieContract);

        uint64 claimTimestamp = _mutant_claimTimestamp[tokenId];
        uint64 txTimestamp = source_data.timestampOf(tokenId);

        if (txTimestamp > claimTimestamp) {
            claimTimestamp = txTimestamp;
        }
        
        return claimTimestamp;
    }

    function getTotalMutantClaimAmountForOwner(address ownerAddress) public view returns (uint256) {

        uint[] memory tokens = getSkellieIDsByOwner(ownerAddress);
        uint256 totalClaimDelta = 0;
        uint64 claimTimestamp = uint64(block.timestamp);
        Skellies_Contract source_data = Skellies_Contract(_skellieContract);

        for (uint64 i=0; i<tokens.length;i++) {
            if (source_data.isMutantToken(tokens[i])) {
                totalClaimDelta += (claimTimestamp - getClaimTimeForSkellieToken(tokens[i]));
                
            }
        }

        return totalClaimDelta * _mutantRate;
    }

    function getSkellieIDsByOwner(address ownerAddress) public view returns (uint[] memory) {

        // load the contract
        Skellies_Contract source_data = Skellies_Contract(_skellieContract);

        uint[] memory output = new uint[](source_data.balanceOf(ownerAddress)); // set array length to number of tokens caller has

        uint total = source_data.totalSupply();

        uint counter = 0;

        // iterate all tokens
        for (uint i = 0; i < total; i++) {

            // find the ones belonging to ownerAddress
            if (source_data.ownerOf(i) == ownerAddress) {

                // set output to those belonging to ownerAddress
                output[counter] = i;

                counter++;
            }
        }

        return output;
    }

    function getScheduleSize() public view returns (uint) {
        return _schedule.length;
    }

    function getScheduleAtIndex(uint index) public view returns (uint256,uint64) {

        return (_schedule[index].ceilToken,_schedule[index].timestamp);
    }

}
[{"inputs":[{"internalType":"address","name":"genesisContract","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[],"name":"ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PROXY_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"burnGbones","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"tokens","type":"uint256[]"}],"name":"claimGenesisGbones","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"tokens","type":"uint256[]"}],"name":"claimMutantGbones","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getClaimTimeForGenesisToken","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getClaimTimeForSkellieToken","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"ownerAddress","type":"address"}],"name":"getGenesisIDsByOwner","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getScheduleAtIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getScheduleSize","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"ownerAddress","type":"address"}],"name":"getSkellieIDsByOwner","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"ownerAddress","type":"address"}],"name":"getTotalGenesisClaimAmountForOwner","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"ownerAddress","type":"address"}],"name":"getTotalMutantClaimAmountForOwner","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"ceilToken","type":"uint256"},{"internalType":"uint64","name":"startTimestamp","type":"uint64"}],"name":"pushSchedule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"skellieContract","type":"address"}],"name":"setSkellieContract","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}]