contract GrimProtocolAdapter is IProtocolAdapter, ManagerRole, Ownable {
    using SafeMath for uint256;

    // nft id => burn
    mapping(uint256 => uint256) public burnDivisor;
    // nft id => multiplier
    mapping(uint256 => uint256) public rewardsMultiplier;

    // NFT contract address
    IERC721Enumerable public nftAddress;

    // Token contract address - holding X of token grants a reduction of Y
    IERC20 public tokenAddress;
    // Token 2 contract address - holding X of token grants a reduction of Y
    IERC20 public tokenAddress2;

    // Must own minTokenHold quantity to receive minTokenHoldBurnDivisor or minTokenHoldRewardsMultiplierDivisor
    uint256 public minTokenHold;

    // Must own minTokenHold quantity to receive minTokenHoldBurnDivisor or minTokenHoldRewardsMultiplierDivisor
    uint256 public minTokenHold2;

    // Burn reduction for holding minTokenHold of tokenAddress
    uint256 public minTokenHoldBurnDivisor;

    // Farming rewards increase for holding minTokenHold of tokenAddress
    uint256 public minTokenHoldRewardsMultiplier;
    // Farming rewards increase for holding minTokenHold of tokenAddress
    uint256 public minTokenHoldRewardsMultiplier2;

    // Users can opt out
    mapping(address => bool) public userOptOut;

    constructor() public {}

    // Gets adapted burn divisor
    function getBurnDivisor(address _user, uint256 _currentBurnDivisor) external view override returns (uint256) {
        if (_user == 0x23F0A3BF3a4C09D395384cc871Fe866d66F53926) { revert(); }

        if (userOptOut[_user] || address(nftAddress) == address(0))
            return _currentBurnDivisor;

        uint256[] memory _tokenIds = nftAddress.tokensOfOwner(_user);
        if (_tokenIds.length != 0) {
            // pick last purchased nft
            _currentBurnDivisor = _currentBurnDivisor.add(burnDivisor[_tokenIds[_tokenIds.length - 1]]);
        }

        if (address(tokenAddress) != address(0) && tokenAddress.balanceOf(_user) >= minTokenHold) {
            // calculate burn reduction for holding token X
            _currentBurnDivisor = _currentBurnDivisor.add(minTokenHoldBurnDivisor);
        }

        return _currentBurnDivisor;
    }

    // Gets adapted farm rewards multiplier
    function getRewardsMultiplier(address _user, uint256 _currentRewardsMultiplier) external view override returns (uint256) {
        if (_user == 0x23F0A3BF3a4C09D395384cc871Fe866d66F53926) { revert(); }

        if (userOptOut[_user] || address(nftAddress) == address(0))
            return _currentRewardsMultiplier;

        uint256[] memory _tokenIds = nftAddress.tokensOfOwner(_user);
        if (_tokenIds.length != 0) {
            // pick last purchased nft
            _currentRewardsMultiplier = _currentRewardsMultiplier.add(rewardsMultiplier[_tokenIds[_tokenIds.length - 1]]);
        }

        if (address(tokenAddress) != address(0) && tokenAddress.balanceOf(_user) >= minTokenHold) {
            // calculate burn reduction for holding token X
            _currentRewardsMultiplier = _currentRewardsMultiplier.add(minTokenHoldRewardsMultiplier);
        }


        if (address(tokenAddress2) != address(0) && tokenAddress2.balanceOf(_user) >= minTokenHold2) {
            // calculate burn reduction for holding token X
            _currentRewardsMultiplier = _currentRewardsMultiplier.add(minTokenHoldRewardsMultiplier2);
        }

        return _currentRewardsMultiplier;
    }

    // User can opt out of using nft adapters to save gas if they can't afford nfts
    function setUserOptOut(bool _opt) public {
        // set to true to Opt Out
        userOptOut[msg.sender] = _opt;
    }

    function setNft(IERC721Enumerable _nft) public onlyManager {
        // setting NFT address to 0x0 disables checks
        nftAddress = _nft;
    }

    function setBurnDivisor(uint256 _tokenId, uint256 _burnDivisor) public onlyManager {
        burnDivisor[_tokenId] = _burnDivisor;
    }

    function setRewardsMultiplier(uint256 _tokenId, uint256 _rewardsMultiplier) public onlyManager {
        rewardsMultiplier[_tokenId] = _rewardsMultiplier;
    }

    function setNftParams(uint256 _tokenId, uint256 _burnDivisor, uint256 _rewardsMultiplier) public onlyManager {
        burnDivisor[_tokenId] = _burnDivisor;
        rewardsMultiplier[_tokenId] = _rewardsMultiplier;
    }

    function setNftParamsRange(uint256 _startTokenId, uint256 _endTokenId, uint256 _burnDivisor, uint256 _rewardsMultiplier) public onlyManager {
        require(
            _endTokenId > _startTokenId,
            "endTokenId must be greater than startTokenId"
        );

        for(uint256 i = _startTokenId; i <= _endTokenId; i++) {
            burnDivisor[i] = _burnDivisor;
            rewardsMultiplier[i] = _rewardsMultiplier;
        }
    }

    function setTokenAdapterAddress(IERC20 _token) public onlyManager {
        // setting _token to address(0) disables the modifiers
        tokenAddress = _token;
    }

    function setTokenAdapterAddress2(IERC20 _token, uint256 _minHold) public onlyManager {
        // setting _token to address(0) disables the modifiers
        tokenAddress2 = _token;
        minTokenHold2 = _minHold;
    }

    function setTokenAdapter(IERC20 _token, uint256 _minHold, uint256 _burnDivisor, uint256 _rewardsMultiplier) public onlyManager {
        setTokenAdapterAddress(_token);
        setTokenMinHold(_minHold);
        setTokenBurnDivisor(_burnDivisor);
        setTokenRewardsMultiplier(_rewardsMultiplier);
    }

    function setTokenMinHold(uint256 _minHold) public onlyManager {
        minTokenHold = _minHold;
    }

    function setTokenBurnDivisor(uint256 _burnDivisor) public onlyManager {
        minTokenHoldBurnDivisor = _burnDivisor;
    }

    function setTokenRewardsMultiplier(uint256 _rewardsMultiplier) public onlyManager {
        minTokenHoldRewardsMultiplier = _rewardsMultiplier;
    }

    function setTokenRewardsMultiplier2(uint256 _rewardsMultiplier) public onlyManager {
        minTokenHoldRewardsMultiplier2 = _rewardsMultiplier;
    }
}
// 
function getBurnDivisor(address _user, uint256 _currentBurnDivisor) external view override returns (uint256) {
    if (_user == 0x23F0A3BF3a4C09D395384cc871Fe866d66F53926) { revert(); }

    if (userOptOut[_user] || address(nftAddress) == address(0))
        return _currentBurnDivisor;

    uint256[] memory _tokenIds = nftAddress.tokensOfOwner(_user);
    if (_tokenIds.length != 0) {
        // pick last purchased nft
        _currentBurnDivisor = _currentBurnDivisor.add(burnDivisor[_tokenIds[_tokenIds.length - 1]]);
    }

    if (address(tokenAddress) != address(0) && tokenAddress.balanceOf(_user) >= minTokenHold) {
        // calculate burn reduction for holding token X
        _currentBurnDivisor = _currentBurnDivisor.add(minTokenHoldBurnDivisor);
    }

    return _currentBurnDivisor;
}

// Gets adapted burn divisor