contract BFHApprovalProxyV1 is IApprovalProxy, Operatable {
    using Address for address;

    Roles.Role private approvableContracts;
    Roles.Role private preapprovedContracts;
    mapping (address => mapping (address => bool)) private _expresslyNotApprovalSpender;
    mapping (address => mapping (address => bool)) private _contractApprovals;

    ProxyRegistry public openSeaProxyRegistry;
    mapping (address => bool) private openSeaDisabled;

    event UpdateApprovableContracts (address spender, bool approved);
    event UpdatePreapprovedContracts (address spender, bool approved);

    modifier onlyContract(address _spender) {
        require(_spender.isContract(), "_spender must be contract");
        _;
    }

    constructor() public {}

    function setApprovalForAll(address _owner, address _spender, bool _approved) public onlyContract(_spender) onlyOperator() {
        require(isApprovableContract(_spender), "_spender must belong to approvable role");
        _expresslyNotApprovalSpender[_owner][_spender] = !_approved;
    }

    function isApprovedForAll(address _owner, address _spender, bool _original) public view returns (bool) {
        if (approvalToOpenSea(_owner, _spender)) {
            return true;
        }
        if (_spender.isContract()) {
            if (!isApprovableContract(_spender)) {
                return false;
            }
            if (isPreapprovedContract(_spender)) {
                return !_expresslyNotApprovalSpender[_owner][_spender];
            }
        }
        return _original;
    }

    // OpenSea
    function setOpenSeaProxyRegistry(address _openSeaProxyRegistry) public onlyOperator() {
        openSeaProxyRegistry = ProxyRegistry(_openSeaProxyRegistry);
    }

    function enableOpenSea() public {
        openSeaDisabled[msg.sender] = false;
    }

    function disableOpenSea() public {
        openSeaDisabled[msg.sender] = true;
    }
    
    function getOpenSeaSpender(address _owner) public view returns (address) {
        return address(openSeaProxyRegistry.proxies(_owner));
    }

    function approvalToOpenSea(address _owner, address _spender) public view returns (bool) {
        if (address(openSeaProxyRegistry) == address(0x0)) {
            return false;
        }
        if (openSeaDisabled[_owner]) {
            return false;
        }
        return (getOpenSeaSpender(_owner) == _spender);
    }

    // Approvable list
    function setApprovableContracts(address _spender, bool _approvable) public onlyOperator() onlyContract(_spender) {
        emit UpdateApprovableContracts(_spender, _approvable);
        if (_approvable) {
            approvableContracts.add(_spender);
        } else {
            require(!isPreapprovedContract(_spender), "_spender must not be preapproval");
            approvableContracts.remove(_spender);
        }
    }

    function isApprovableContract(address _spender) public view returns (bool) {
        return approvableContracts.has(_spender);
    }

    function setPreapprovedContracts(address _spender, bool _approval) public onlyOperator() onlyContract(_spender) {
        require(isApprovableContract(_spender), "_spender must not be approvable");
        emit UpdatePreapprovedContracts(_spender, _approval);
        if (_approval) {
            preapprovedContracts.add(_spender);
        } else {
            preapprovedContracts.remove(_spender);
        }
    }

    function isPreapprovedContract(address _spender) public view returns (bool) {
        return preapprovedContracts.has(_spender);
    }

}
function setApprovableContracts(address _spender, bool _approvable) public onlyOperator() onlyContract(_spender) {
    emit UpdateApprovableContracts(_spender, _approvable);
    if (_approvable) {
        approvableContracts.add(_spender);
    } else {
        require(!isPreapprovedContract(_spender), "_spender must not be preapproval");
        approvableContracts.remove(_spender);
    }
}

// Approvable list