contract ACOPoolFactory2V4 is ACOPoolFactory2V3 {

    /**
     * @dev Emitted when the pool proxy admin address has been changed.
     * @param previousPoolProxyAdmin Address of the previous pool proxy admin.
     * @param newPoolProxyAdmin Address of the new pool proxy admin.
     */
    event SetPoolProxyAdmin(address indexed previousPoolProxyAdmin, address indexed newPoolProxyAdmin);

    /**
     * @dev Emitted when a ACO creator address forbidden status has been changed.
     * @param acoCreator Address of the ACO creator.
     * @param previousStatus Whether the creator was forbidden.
     * @param newStatus Whether the creator will be forbidden.
     */
    event SetForbiddenAcoCreator(address indexed acoCreator, bool indexed previousStatus, bool indexed newStatus);

    /**
     * @dev The factory admin address.
     */
    address public poolProxyAdmin;

    /**
     * @dev Addresses of forbidden ACO creators for a pool.
     */
    address[] internal acoForbiddenCreators;
    
    /**
     * @dev Function to set the pool proxy admin address.
     * Only can be called by the factory admin.
     * @param newPoolProxyAdmin Address of the new pool proxy admin.
     */
    function setPoolProxyAdmin(address newPoolProxyAdmin) onlyFactoryAdmin external virtual {
        _setPoolProxyAdmin(newPoolProxyAdmin);
    }

    /**
     * @dev Function to update the pool implementation address.
     * Only can be called by the pool proxy admin.
     * @param pools Addresses of the new pools.
     * @param initData Init data for the pool implementation.
     */
    function updatePoolsImplementation(
        address payable[] calldata pools,
        bytes calldata initData
    ) external virtual {
        require(poolProxyAdmin == msg.sender, "ACOPoolFactory::onlyPoolProxyAdmin");
        for (uint256 i = 0; i < pools.length; ++i) {
            ACOProxy(pools[i]).setImplementation(acoPoolImplementation, initData);
        }
    }

    /**
     * @dev Function to transfer the pool proxy admin.
     * Only can be called by the pool proxy admin.
     * @param newPoolProxyAdmin Address of the new pool proxy admin.
     * @param pools Addresses of the new pools.
     */
    function transferPoolProxyAdmin(address newPoolProxyAdmin, address payable[] calldata pools) external virtual {
        require(poolProxyAdmin == msg.sender, "ACOPoolFactory::onlyPoolProxyAdmin");
        for (uint256 i = 0; i < pools.length; ++i) {
            ACOProxy(pools[i]).transferProxyAdmin(newPoolProxyAdmin);
        }
    }

    /**
     * @dev Function to get the number of ACO creators forbidden for a pool.
     * @return The number of ACO creators forbidden.
     */
    function getNumberOfAcoCreatorsForbidden() view external virtual returns(uint256) {
        return acoForbiddenCreators.length;
    }
    
    /**
     * @dev Function to get the address of ACO creators forbidden.
     * @param index The index of the ACO creator.
     * @return The address of ACO creators forbidden.
     */
    function getAcoCreatorForbidden(uint256 index) view external virtual returns(address) {
        return acoForbiddenCreators[index];
    }

    /**
     * @dev Function to set the ACO creator forbidden status.
     * @param acoCreator Address of the ACO creator.
     * @param newStatus Whether the creator will be forbidden.
     */
    function setForbiddenAcoCreator(address acoCreator, bool newStatus) onlyFactoryAdmin external virtual {
        _setForbiddenAcoCreator(acoCreator, newStatus);
    }

    /**
     * @dev Function to change the ACO pools ACO creator forbidden situation.
     * Only can be called by a pool admin.
     * @param acoCreator Address of the ACO creator.
	 * @param status Forbidden situation.
     * @param acoPools Array of ACO pools addresses.
     */
	function setForbiddenAcoCreatorOnAcoPool(address acoCreator, bool status, address[] calldata acoPools) onlyPoolAdmin external virtual {
		_setForbiddenAcoCreatorOnAcoPool(acoCreator, status, acoPools);
	}

    /**
     * @dev Internal function to deploy a proxy using ACO pool implementation.
     * @param initData Data to initialize o ACO Pool.
     * @return Address of the new proxy deployed for the ACO pool.
     */
    function _deployAcoPool(IACOPool2.InitData memory initData) internal override virtual returns(address) {
        ACOProxy proxy = new ACOProxy(address(this), acoPoolImplementation, abi.encodeWithSelector(IACOPool2.init.selector, initData));
        return address(proxy);
    }
    
    /**
     * @dev Internal function to create a new ACO pool.
     * @param initData Data to initialize o ACO Pool.
     * @return Address of the new proxy deployed for the ACO pool.
     */
    function _createAcoPool(IACOPool2.InitData memory initData) internal override virtual returns(address) {
        address acoPool  = _deployAcoPool(initData);
        acoPoolBasicData[acoPool] = ACOPoolBasicData(initData.underlying, initData.strikeAsset, initData.isCall);
        creators[acoPool] = msg.sender;
        for (uint256 i = 0; i < acoAuthorizedCreators.length; ++i) {
            IACOPool2(acoPool).setValidAcoCreator(acoAuthorizedCreators[i], true);
        }
        for (uint256 j = 0; j < acoForbiddenCreators.length; ++j) {
            IACOPool2(acoPool).setForbiddenAcoCreator(acoForbiddenCreators[j], true);
        }
        emit NewAcoPool(initData.underlying, initData.strikeAsset, initData.isCall, acoPool, acoPoolImplementation);
        return acoPool;
    }	
    
    /**
     * @dev Internal function to set the pool proxy admin address.
     * @param newPoolProxyAdmin Address of the new pool proxy admin.
     */
    function _setPoolProxyAdmin(address newPoolProxyAdmin) internal virtual {
        require(newPoolProxyAdmin != address(0), "ACOPoolFactory::_setPoolProxyAdmin: Invalid pool proxy admin");
        emit SetPoolProxyAdmin(poolProxyAdmin, newPoolProxyAdmin);
        poolProxyAdmin = newPoolProxyAdmin;
    }

    /**
     * @dev Internal function to set the ACO creator forbidden status.
     * @param acoCreator Address of the ACO creator.
     * @param newStatus Whether the creator will be forbidden.
     */
    function _setForbiddenAcoCreator(address acoCreator, bool newStatus) internal virtual {
        bool previousStatus = false;
        uint256 size = acoForbiddenCreators.length;
        for (uint256 i = size; i > 0; --i) {
            if (acoForbiddenCreators[i - 1] == acoCreator) {
                previousStatus = true;
                if (!newStatus) {
                    if (i < size) {
                        acoForbiddenCreators[i - 1] = acoForbiddenCreators[(size - 1)];
                    }
                    acoForbiddenCreators.pop();
                }
                break;
            }
        }
        if (newStatus && !previousStatus) {
            acoForbiddenCreators.push(acoCreator);
        }
        emit SetForbiddenAcoCreator(acoCreator, previousStatus, newStatus);
    }

    /**
     * @dev Internal function to change the ACO pools ACO creator forbidden situation.
     * @param acoCreator Address of the ACO creator.
	 * @param status Forbidden situation.
     * @param acoPools Array of ACO pools addresses.
     */
    function _setForbiddenAcoCreatorOnAcoPool(address acoCreator, bool status, address[] memory acoPools) internal virtual {
        for (uint256 i = 0; i < acoPools.length; ++i) {
            IACOPool2(acoPools[i]).setForbiddenAcoCreator(acoCreator, status);
        }
    }
}
function setForbiddenAcoCreatorOnAcoPool(address acoCreator, bool status, address[] calldata acoPools) onlyPoolAdmin external virtual {
	_setForbiddenAcoCreatorOnAcoPool(acoCreator, status, acoPools);
}

/**
 * @dev Function to change the ACO pools ACO creator forbidden situation.
 * Only can be called by a pool admin.
 * @param acoCreator Address of the ACO creator.
 	 * @param status Forbidden situation.
 * @param acoPools Array of ACO pools addresses.
 */