abstract contract ControlCenter {
    //_tokenContractAddresses: Array of all potential ERC721 contracts that any token that interfaces with ControlCenter can access.
    address[] private _tokenContractAddresses;
    //_usingControlCenter keeps track of the contracts paired with this ControlCenter.
    address[] private _usingControlCenter;
    //owner: ControlCenter Owner 
    address private owner;
    //_maxRedemptions: where address: Child Contract Address (external contract using the ControlCenter) mapped to the maximum number of redemptions allowed for that Child Contract (e.g. contract 0x123 only allows 3 redemptions per wallet);
    mapping(address=>uint256) private _maxRedemptions;
    //_activeTokenContracts: where address is one of the _contractAddress members mapped to a boolean denoting whether or not to ignore that contract in downstream functions. Setting this to false will effectively 'shut off' a specific contract from any considersations made by the ControlCenter
    mapping(address => bool) private _activeTokenContracts;
    //_contractEligibility: where address1 = similar to _activeTokenContracts but on a specific Child Contract level, setting [ChildContractAddress][TokenContractAddress] = false removes the [TokenContractAddress] from being considered as a redeemable token contract for the Child Contract. 
    mapping(address=> mapping(address=>bool)) private _contractEligibility;
    mapping(address => mapping(address => mapping(uint256 => bool))) private tokenVer; //Mapping of [Child Contract] => [Corresponding ERC721 Token Contract] => [Corresponding ERC721 Token's Token ID] => [Redeemed (True/False)] whether a token has been redeemed for a specific Child Contract
    //_walletRedeemed: the number of tokens redeemed for a specific Child Contract. [Child Contract Address] => [Wallet Address] => [Number of Tokens Redeemed]
    mapping(address => mapping(address=>uint256)) private _walletRedeemed;
    
    constructor(){
        owner = msg.sender;
    }
    
    
    /*
    *@dev Returns the balance of all active Token Contracts on the ControlCenter
    * @param _addy address Wallet address of a given user.
    */
    function getBalance(address _addy) public view returns (uint256) {
        uint256 _balance =0;
        for (uint256 i=0;i<_tokenContractAddresses.length;i++){
            if (_activeTokenContracts[_tokenContractAddresses[i]]){
                _balance = SafeMath.add(_balance,ERC721(_tokenContractAddresses[i]).balanceOf(_addy));
            }
        }
        return _balance;
    }
    
    /*
    *@dev Returns the maximum token redemptions allowed for a given Child Contract
    *Used to verify contract was onboarded to the ControlCenter correctly
    * @param _childContract address Address of a Child Contract implementing ControlCenter functions
    */
    function checkContractStats(address _childContract) public view returns(uint256) {
        return (_maxRedemptions[_childContract]);
    }
    
    /*@dev This function checks the eligible balance of an address for a certain Child Contract. 
    *While a caller may have any number of tokens, eligibility is determined by the maximum number of tokens a Child Contract supports as defined by the _maxRedemptions mapping and how many times a wallet 
    *has redeemed tokens (_walletRedeemed) against the Child Contract (also determined by _maxRedemptions)
    *@param _childContract address Address of a Child Contract implementing ControlCenter functions
    *@param _addy address Wallet address of a given user (typically implemented as msg.sender from the Child Contract).
    */
    function getEligibleBalance(address _childContract, address _addy) public view returns (uint256){
        uint256 foundCount = 0;
        for (uint256 i = 0; i<_tokenContractAddresses.length;i++){
            if(_contractEligibility[_childContract][_tokenContractAddresses[i]] && _activeTokenContracts[_tokenContractAddresses[i]]){
                ERC721Enumerable con = ERC721Enumerable(_tokenContractAddresses[i]);
                uint256 conBalance = con.balanceOf(_addy);
                if ( conBalance > 0){
                    for (uint256 ii = 0; ii<conBalance; ii++){
                        ERC721Enumerable conE = ERC721Enumerable(_tokenContractAddresses[i]);
                        uint256 tokenID = conE.tokenOfOwnerByIndex(_addy,ii);
                        if(tokenVer[_childContract][_tokenContractAddresses[i]][tokenID] == false){
                            foundCount++;
                        }
                    }
                }
            }
            
        }
        if (foundCount > 0){
            if (foundCount >= _maxRedemptions[_childContract] && _maxRedemptions[_childContract] >= _walletRedeemed[_childContract][_addy]){
                return SafeMath.sub(_maxRedemptions[_childContract], _walletRedeemed[_childContract][_addy]);
            } else if (foundCount<=_maxRedemptions[_childContract] && foundCount >= _walletRedeemed[_childContract][_addy]){
                return SafeMath.sub(foundCount,_walletRedeemed[_childContract][_addy]);
            } else {
                return 0;
            }
        }
        else {
            return 0;
        }
    }
    
    /*@dev This function is called when onboarding a new Child Contract to this ControlCenter
    *@param _childContract address Address of a Child Contract implementing ControlCenter functions
    *@param _maxRed uint256 maximum number of tokens that can be redeemed against a certain Child Contract.
    */
    function pairContract(address _childContract, uint256 _maxRed) public{
        require(msg.sender == owner || msg.sender == _childContract, "Caller not authorized for this function");
         _maxRedemptions[_childContract] = _maxRed;
        for(uint256 i =0; i<_tokenContractAddresses.length;i++){
            _contractEligibility[_childContract][_tokenContractAddresses[i]] = true;
        }
        activateTokenContract(_childContract);
    }
    
    /*@dev This function can be used to manually override which Token Contracts are eligible to be redeemed for a certain Child Contract
    *@param _childContract address Address of a Child Contract implementing ControlCenter functions
    *@param _targetContract address Address of a target Token Contract whose state is being modified.
    *@param _newEligibility bool Whether or not the _targetContract can be redeemed against the given _childContract
    */
    function setChildContractEligibility (address _childContract, address _targetContract, bool _newEligibility) public {
        require(msg.sender == owner || msg.sender == _childContract, "Caller not authorized for this function.");
        _contractEligibility[_childContract][_targetContract] = _newEligibility;
        
    }
    
    /*@dev This function handles the interal considerations for token redemptions.
    *This function in this version of ControlCenter is pretty indiscriminate in regards to which tokens it takes for redemption.
    *It's not so much concerned about what or where the tokens come from, so much as it is concerned that they are or are not available tokens to be redeemed.
    *If they are redeeemable per the Child Contract, then they're set to 'redeemed' and cannot be redeemed again for that particular Child Contract.
    *Additionally, redeeming a token from any given wallet will count against that wallet's total redemption number for a given Child Contract.
    *Once a token has been redeemed for a Child Contract it cannot be redeemed again, similarly a wallet cannot reduce the number of redemptions by sending a token elsewhere.
    *@param _childContract address Address of a Child Contract implementing ControlCenter functions
    *@param _redemptionNumber uint256 Number of tokens requested to be redeemed.
    *@param _addy address Wallet address of a given user (typically implemented as msg.sender from the Child Contract).
    */
    function redeemEligibleTokens(address _childContract, uint256 _redemptionNumber, address _addy) public {
        //Must meet a number of requirements to continue to log a token as redeemed.
        require(msg.sender == owner || msg.sender == _childContract,"Caller not authorized for this function");
        require(_redemptionNumber > 0, "Cannot redeem 0 tokens, you dork.");
        require(_walletRedeemed[_childContract][_addy] < _maxRedemptions[_childContract], "Caller has already redeemed maximum number of Tokens");
        require(_redemptionNumber <= getEligibleBalance(_childContract, _addy),"This wallet cannot redeem this many tokens, please adjust_redemptionAmount");
        require(getEligibleBalance(_childContract, _addy) > 0, "Caller has no Eligible Tokens for this Contract");
        
        uint256 _foundCount = 0;
        uint256 _functionalLimit = getEligibleBalance(_childContract, _addy);
        
        //Functional limit is meant to cut down on potential wasted gas by taking the lesser of either what the user has opted to redeem or how many tokens the user is eligible to redeem
        if (_functionalLimit > _redemptionNumber) {
            _functionalLimit = _redemptionNumber;
        }
        
        if (_functionalLimit > 0){
            //Seeks to save gas by breaking out of the loop if the _foundCount reaches the functionalLimit (meaning if it finds enough eligible tokens there's no point continuing to run the loop)
            for (uint256 i = 0; i<_tokenContractAddresses.length && _foundCount < _functionalLimit;i++){ 
                if(_contractEligibility[_childContract][_tokenContractAddresses[i]] && _activeTokenContracts[_tokenContractAddresses[i]]){
                    ERC721Enumerable con = ERC721Enumerable(_tokenContractAddresses[i]);
                    uint256 conBalance = con.balanceOf(_addy); //number of tokens owned by _addy on a given TokenContract[i]
                    if ( conBalance > 0){
                        //similar gas saving effort here
                        for (uint256 ii = 0; ii<conBalance && _foundCount < _functionalLimit; ii++){
                            ERC721Enumerable conE = ERC721Enumerable(_tokenContractAddresses[i]);
                            uint256 tokenID = conE.tokenOfOwnerByIndex(_addy,ii);
                            if(tokenVer[_childContract][_tokenContractAddresses[i]][tokenID] == false){
                                tokenVer[_childContract][_tokenContractAddresses[i]][tokenID] = true; 
                                _foundCount++;
                            }
                        }
                    }
                }
                
            }
        }
        //Adds the number of foundTokens to the wallet's child contract mapping. This only occurs after all of the tokens have been set as redeemed.
        _walletRedeemed[_childContract][_addy] = SafeMath.add(_walletRedeemed[_childContract][_addy],_foundCount);
    }
    
    function checkTokenRedemptionStatus(address _childContract,address _tokenAddress, uint256 _tokenID) public view returns (bool){
        return (tokenVer[_childContract][_tokenAddress][_tokenID]);
    }
    
    function activateTokenContract(address _contract) public {
        require(msg.sender == owner || msg.sender == _contract, "E:ActivateToken - Caller is not the owner");
        require(_activeTokenContracts[_contract] == false, "Contract already active");
        
        if(_isTokenContractOnControlCenter(_contract) == false){
            _activeTokenContracts[_contract] = true;
            _tokenContractAddresses.push(_contract);
        } else {
            _activeTokenContracts[_contract] = true;
        }
        
    }
    function deactivateTokenContract(address _contract) public {
        require(msg.sender == owner, "Sender is not owner");
        require(_activeTokenContracts[_contract] == true, "Contract isn't active.");
        _activeTokenContracts[_contract] = false;
    }
    
    function _isTokenContractOnControlCenter (address _contract) internal view returns(bool){
        uint256 count = 0;
        for(uint256 i=0;i<_tokenContractAddresses.length;i++){
            if(_tokenContractAddresses[i] == _contract){
                count++;
            }
        }
        return count > 0;
    }
    
    
    function checkWalletRedeption(address _addy, address _childContract)public view returns(uint256){
        return _walletRedeemed[_childContract][_addy]; 
    }
    function viewControlCenterContracts() public view returns (address[] memory){
        return _tokenContractAddresses;
    }
    
   
}
function getEligibleBalance(address _childContract, address _addy) public view returns (uint256){
    uint256 foundCount = 0;
    for (uint256 i = 0; i<_tokenContractAddresses.length;i++){
        if(_contractEligibility[_childContract][_tokenContractAddresses[i]] && _activeTokenContracts[_tokenContractAddresses[i]]){
            ERC721Enumerable con = ERC721Enumerable(_tokenContractAddresses[i]);
            uint256 conBalance = con.balanceOf(_addy);
            if ( conBalance > 0){
                for (uint256 ii = 0; ii<conBalance; ii++){
                    ERC721Enumerable conE = ERC721Enumerable(_tokenContractAddresses[i]);
                    uint256 tokenID = conE.tokenOfOwnerByIndex(_addy,ii);
                    if(tokenVer[_childContract][_tokenContractAddresses[i]][tokenID] == false){
                        foundCount++;
                    }
                }
            }
        }
            
    }
    if (foundCount > 0){
        if (foundCount >= _maxRedemptions[_childContract] && _maxRedemptions[_childContract] >= _walletRedeemed[_childContract][_addy]){
            return SafeMath.sub(_maxRedemptions[_childContract], _walletRedeemed[_childContract][_addy]);
        } else if (foundCount<=_maxRedemptions[_childContract] && foundCount >= _walletRedeemed[_childContract][_addy]){
            return SafeMath.sub(foundCount,_walletRedeemed[_childContract][_addy]);
        } else {
            return 0;
        }
    }
    else {
        return 0;
    }
}

/*@dev This function checks the eligible balance of an address for a certain Child Contract. 
 *While a caller may have any number of tokens, eligibility is determined by the maximum number of tokens a Child Contract supports as defined by the _maxRedemptions mapping and how many times a wallet 
 *has redeemed tokens (_walletRedeemed) against the Child Contract (also determined by _maxRedemptions)
 *@param _childContract address Address of a Child Contract implementing ControlCenter functions
 *@param _addy address Wallet address of a given user (typically implemented as msg.sender from the Child Contract).
 */