// Contract: Staking
// Target function: function setRewardNft(Chao RewardNft_) public onlyOwner
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }
}

abstract contract Ownable is Context {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
    constructor() {
        _setOwner(_msgSender());
    }

    function owner() public view virtual returns (address) {
        return _owner;
    }

    modifier onlyOwner() {
        require(owner() == _msgSender(), "t001");
        _;
    }

    function renounceOwnership() public virtual onlyOwner {
        _setOwner(address(0));
    }

    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "t002");
        _setOwner(newOwner);
    }

    function _setOwner(address newOwner) private {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}

abstract contract ReentrancyGuard {
    uint256 private constant _NOT_ENTERED = 1;
    uint256 private constant _ENTERED = 2;
    uint256 private _status;
    constructor() {
        _status = _NOT_ENTERED;
    }

    modifier nonReentrant() {
        require(_status != _ENTERED, "t003");
        _status = _ENTERED;
        _;
        _status = _NOT_ENTERED;
    }
}

library Address {
    function isContract(address account) internal view returns (bool) {
        uint256 size;
        assembly {
            size := extcodesize(account)
        }
        return size > 0;
    }

    function sendValue(address payable recipient, uint256 amount) internal {
        require(address(this).balance >= amount, "t004");

        (bool success,) = recipient.call{value : amount}("");
        require(success, "t005");
    }

    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCall(target, data, "m001");
    }

    function functionCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, errorMessage);
    }

    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value
    ) internal returns (bytes memory) {
        return functionCallWithValue(target, data, value, "m002");
    }

    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value,
        string memory errorMessage
    ) internal returns (bytes memory) {
        require(address(this).balance >= value, "t006");
        require(isContract(target), "t007");

        (bool success, bytes memory returndata) = target.call{value : value}(data);
        return _verifyCallResult(success, returndata, errorMessage);
    }

    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        return functionStaticCall(target, data, "m003");
    }

    function functionStaticCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        require(isContract(target), "t008");
        (bool success, bytes memory returndata) = target.staticcall(data);
        return _verifyCallResult(success, returndata, errorMessage);
    }

    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionDelegateCall(target, data, "m004");
    }

    function functionDelegateCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        require(isContract(target), "t009");

        (bool success, bytes memory returndata) = target.delegatecall(data);
        return _verifyCallResult(success, returndata, errorMessage);
    }

    function _verifyCallResult(
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) private pure returns (bytes memory) {
        if (success) {
            return returndata;
        } else {
            if (returndata.length > 0) {
                assembly {
                    let returndata_size := mload(returndata)
                    revert(add(32, returndata), returndata_size)
                }
            } else {
                revert(errorMessage);
            }
        }
    }
}

interface IERC721Enumerable {
    function balanceOf(address account) external view returns (uint256);

    function isApprovedForAll(address owner, address operator) external view returns (bool);

    function transferFrom(
        address from,
        address to,
        uint256 tokenId
    ) external;

    function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256 tokenId);
}

interface IERC20 {
    function decimals() external view returns (uint8);

    function balanceOf(address account) external view returns (uint256);

    function transfer(address recipient, uint256 amount) external returns (bool);

    function allowance(address owner, address spender) external view returns (uint256);

    function approve(address spender, uint256 amount) external returns (bool);

    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);

    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);
}

library SafeMath {
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        require(c >= a, "t010");
        return c;
    }

    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        return sub(a, b, "t011");
    }

    function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b <= a, errorMessage);
        uint256 c = a - b;
        return c;
    }

    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        if (a == 0) {
            return 0;
        }
        uint256 c = a * b;
        require(c / a == b, "t012");
        return c;
    }
}

library SafeERC20 {
    using SafeMath for uint256;
    using Address for address;

    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
    }

    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
    }


    function safeApprove(IERC20 token, address spender, uint256 value) internal {
        require((value == 0) || (token.allowance(address(this), spender) == 0),
            "t013"
        );
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
    }

    function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        uint256 newAllowance = token.allowance(address(this), spender).add(value);
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
    }

    function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        uint256 newAllowance = token.allowance(address(this), spender).sub(value, "m006");
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
    }

    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        bytes memory returndata = address(token).functionCall(data, "m007");
        if (returndata.length > 0) {
            require(abi.decode(returndata, (bool)), "t014");
        }
    }
}


interface Chao {
    function claim(address _to, uint256 _num, uint256 _poolId, uint256 _randNum, uint256 _minNum, uint256 _MaxNum) external;
}

contract Staking is ReentrancyGuard, Ownable {
    using SafeERC20 for IERC20;
    using SafeMath for uint256;
    uint256 private PoolIdNow = 0;
    mapping(address => mapping(uint256 => uint256)) public staking_num;
    mapping(address => mapping(uint256 => uint256[])) public staking_token_id;
    mapping(uint256 => mapping(uint256 => bool)) private staking_token_id_status;
    mapping(address => mapping(uint256 => uint256)) public staking_time;
    mapping(uint256 => PoolInfo) public PoolInfoList;
    IERC721Enumerable public stakingNftAddress;
    IERC20 public RewardAddress;
    Chao public RewardNft;

    struct PoolInfo {
        uint256 poolId;
        bool canStakeNft;
        uint256 stakingLength;
        uint256 RandTotal;
        uint256 RandMin;
        uint256 RandMax;
        uint256 RewardNum;
    }

    // event widthdrawNftEvent(address _user, uint _tokenId, uint rand_num);
    // _RandTotal:800; _RandMin:200;_RandMax:600;
    function addPool(uint256 _stakingLength, uint256 _RandTotal, uint256 _RandMin, uint256 _RandMax, uint256 _RewardNum) public onlyOwner {
        require(_RandTotal >= _RandMin && _RandTotal >= _RandMin && _RandMax > _RandMin, "t015");
        PoolIdNow = PoolIdNow.add(1);
        PoolInfoList[PoolIdNow] = PoolInfo(PoolIdNow, true, _stakingLength, _RandTotal, _RandMin, _RandMax, _RewardNum);
    }

    function updatePool(uint256 _poolId, bool canStakeNft, uint256 _stakingLength, uint256 _RandTotal, uint256 _RandMin, uint256 _RandMax, uint256 _RewardNum) public onlyOwner {
        require(_RandTotal >= _RandMin && _RandTotal >= _RandMin && _RandMax > _RandMin, "t016");
        PoolInfoList[_poolId] = PoolInfo(PoolIdNow, canStakeNft, _stakingLength, _RandTotal, _RandMin, _RandMax, _RewardNum);
    }

    function enableCanStakeNft(uint256 _poolId) public onlyOwner {
        require(PoolInfoList[_poolId].canStakeNft == false && PoolInfoList[_poolId].RewardNum > 0, "t017");
        PoolInfoList[_poolId].canStakeNft = true;
    }

    function disableCanStakeNft(uint256 _poolId) public onlyOwner {
        require(PoolInfoList[_poolId].canStakeNft == true && PoolInfoList[_poolId].RewardNum > 0, "t018");
        PoolInfoList[_poolId].canStakeNft = false;
    }

    // function setStakingNftAddress(IERC721Enumerable stakingNftAddress_) public onlyOwner {
    //     stakingNftAddress = stakingNftAddress_;
    // }

    // function setRewardAddress(IERC20 RewardAddress_) public onlyOwner {
    //     RewardAddress = RewardAddress_;
    // }

    // function setRewardNft(Chao RewardNft_) public onlyOwner {
    //     RewardNft = RewardNft_;
    // }

    function setTokens(IERC721Enumerable stakingNftAddress_, Chao RewardNft_, IERC20 RewardAddress_) public onlyOwner {
        stakingNftAddress = stakingNftAddress_;
        RewardNft = RewardNft_;
        RewardAddress = RewardAddress_;
    }

    // function setRewardAddress(IERC20 RewardAddress_) public onlyOwner {
    //     RewardAddress = RewardAddress_;
    // }