/**
 *Submitted for verification at BscScan.com on 2023-06-21
*/

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.9;

interface IBEP20 {
    function name() external view returns (string memory);

    function symbol() external view returns (string memory);

    function decimals() external view returns (uint8);

    function totalSupply() external view returns (uint256);

    function balanceOf(address who) external view returns (uint256);

    function allowance(address owner, address spender)
        external
        view
        returns (uint256);

    function transfer(address to, uint256 value) external returns (bool);

    function approve(address spender, uint256 value) external returns (bool);

    function transferFrom(
        address from,
        address to,
        uint256 value
    ) external returns (bool);

    event Transfer(address indexed from, address indexed to, uint256 value);

    event Approval(
        address indexed owner,
        address indexed spender,
        uint256 value
    );
}

interface IBEP721 {
    function balanceOf(address owner) external view returns (uint256 balance);

    function ownerOf(uint256 tokenId) external view returns (address owner);
}

contract Ownable {
    address private _owner;

    event OwnershipRenounced(address indexed previousOwner);
    event TransferOwnerShip(address indexed previousOwner);

    event OwnershipTransferred(
        address indexed previousOwner,
        address indexed newOwner
    );

    constructor() {
        _owner = msg.sender;
    }

    function owner() public view returns (address) {
        return _owner;
    }

    modifier onlyOwner() {
        require(msg.sender == _owner, "Not owner");
        _;
    }

    function renounceOwnership() public onlyOwner {
        emit OwnershipRenounced(_owner);
        _owner = address(0);
    }

    function transferOwnership(address newOwner) public onlyOwner {
        emit TransferOwnerShip(newOwner);
        _transferOwnership(newOwner);
    }

    function _transferOwnership(address newOwner) internal {
        require(newOwner != address(0), "Owner can not be 0");
        emit OwnershipTransferred(_owner, newOwner);
        _owner = newOwner;
    }
}

contract Claim is Ownable {
    address public tokenAddress;
    address public nftAddress;
    uint256 public tokenAmount;

    mapping(address => uint256) public claimableAmount;
    mapping(address => uint256) public claimedAmount;
    mapping(uint256 => bool) public claimedIds;
    mapping(address => uint256) public claimedAmountByNft;

    event Claimed(address indexed user, uint256 amount);

    constructor() {
        tokenAddress = 0x95ac4ffA46C25dBCe18C53F5EdAf088b53c160D1;
    }

    // only owner functions here

    function setTokenAddress(address _tokenAddress) public onlyOwner {
        tokenAddress = _tokenAddress;
    }

    function setTokenAmountPerNft(uint256 _amount) external onlyOwner {
        tokenAmount = _amount;
    }

    function setNftAddress(address _nftAddress) external onlyOwner {
        nftAddress = _nftAddress;
    }

    function setClaimedIds(uint256 _tokenId, bool _value) external onlyOwner {
        claimedIds[_tokenId] = _value;
    }

    function setClaimableAmount(
        address[] memory _users,
        uint256[] memory _amounts
    ) public onlyOwner {
        require(_users.length == _amounts.length, "Invalid input");
        for (uint256 i = 0; i < _users.length; i++) {
            claimableAmount[_users[i]] = _amounts[i];
        }
    }

    // public functions here
    function claim() public {
        require(claimableAmount[msg.sender] > 0, "No claimable amount");
        uint256 amount = claimableAmount[msg.sender];
        claimedAmount[msg.sender] += amount;
        claimableAmount[msg.sender] = 0;
        IBEP20(tokenAddress).transfer(msg.sender, amount);
        emit Claimed(msg.sender, amount);
    }

    // claim tokens by owning nft
    function claimByNftOwnership(uint256 tokenId) external  {
        IBEP721 nft = IBEP721(nftAddress);
        IBEP20 token = IBEP20(tokenAddress);

        // check the owner of tokenId
        require(
            msg.sender == nft.ownerOf(tokenId),
            "You are not the owner of this tokenId"
        );
        require(
            claimedIds[tokenId] == false,
            "Tokens already claimed for this tokenId"
        );
        claimedIds[tokenId] = true;

        // transfer the tokens to msg.sender if enough balance
        require(
            token.balanceOf(address(this)) >= tokenAmount,
            "Contract has insufficient token balance"
        );
        require(
            token.transfer(msg.sender, tokenAmount),
            "Token transfer failed"
        );

        claimedAmountByNft[msg.sender] += tokenAmount;
    }

    // this function is to withdraw BNB sent to this address by mistake
    function withdrawEth(uint256 amount) external onlyOwner returns (bool) {
        (bool success, ) = payable(msg.sender).call{value: amount}("");
        return success;
    }

    // this function is to withdraw BEP20 tokens sent to this address by mistake
    function withdrawBEP20(address _tokenAddress, uint256 amount)
        external
        onlyOwner
        returns (bool)
    {
        IBEP20 token = IBEP20(_tokenAddress);
        bool success = token.transfer(msg.sender, amount);
        return success;
    }

    receive() external payable {}
}