// Contract: MeishuStake
// Target function: function retrieve_meishu_balance() public onlyAuth
 payable;

    /// @notice Transfers the ownership of an NFT from one address to another address
    /// @dev This works identically to the other function with an extra data parameter,
    ///  except this function just sets data to "".
    /// @param _from The current owner of the NFT
    /// @param _to The new owner
    /// @param _tokenId The NFT to transfer
    function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable;

    /// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE
    ///  TO CONFIRM THAT `_to` IS CAPABLE OF RECEIVING NFTS OR ELSE
    ///  THEY MAY BE PERMANENTLY LOST
    /// @dev Throws unless `msg.sender` is the current owner, an authorized
    ///  operator, or the approved address for this NFT. Throws if `_from` is
    ///  not the current owner. Throws if `_to` is the zero address. Throws if
    ///  `_tokenId` is not a valid NFT.
    /// @param _from The current owner of the NFT
    /// @param _to The new owner
    /// @param _tokenId The NFT to transfer
    function transferFrom(address _from, address _to, uint256 _tokenId) external payable;

    /// @notice Change or reaffirm the approved address for an NFT
    /// @dev The zero address indicates there is no approved address.
    ///  Throws unless `msg.sender` is the current NFT owner, or an authorized
    ///  operator of the current owner.
    /// @param _approved The new approved NFT controller
    /// @param _tokenId The NFT to approve
    function approve(address _approved, uint256 _tokenId) external payable;

    /// @notice Enable or disable approval for a third party ("operator") to manage
    ///  all of `msg.sender`'s assets
    /// @dev Emits the ApprovalForAll event. The contract MUST allow
    ///  multiple operators per owner.
    /// @param _operator Address to add to the set of authorized operators
    /// @param _approved True if the operator is approved, false to revoke approval
    function setApprovalForAll(address _operator, bool _approved) external;

    /// @notice Get the approved address for a single NFT
    /// @dev Throws if `_tokenId` is not a valid NFT.
    /// @param _tokenId The NFT to find the approved address for
    /// @return The approved address for this NFT, or the zero address if there is none
    function getApproved(uint256 _tokenId) external view returns (address);

    /// @notice Query if an address is an authorized operator for another address
    /// @param _owner The address that owns the NFTs
    /// @param _operator The address that acts on behalf of the owner
    /// @return True if `_operator` is an approved operator for `_owner`, false otherwise
    function isApprovedForAll(address _owner, address _operator) external view returns (bool);
}

interface ERC165 {
    /// @notice Query if a contract implements an interface
    /// @param interfaceID The interface identifier, as specified in ERC-165
    /// @dev Interface identification is specified in ERC-165. This function
    ///  uses less than 30,000 gas.
    /// @return `true` if the contract implements `interfaceID` and
    ///  `interfaceID` is not 0xffffffff, `false` otherwise
    function supportsInterface(bytes4 interfaceID) external view returns (bool);
}

interface IUniswapFactory {
    event PairCreated(address indexed token0, address indexed token1, address pair, uint);

    function feeTo() external view returns (address);
    function feeToSetter() external view returns (address);
    function getPair(address tokenA, address tokenB) external view returns (address pair);
    function allPairs(uint) external view returns (address pair);
    function allPairsLength() external view returns (uint);
    function createPair(address tokenA, address tokenB) external returns (address pair);
    function setFeeTo(address) external;
    function setFeeToSetter(address) external;
}

interface IUniswapRouter01 {
    function addLiquidity(
        address tokenA,
        address tokenB,
        uint amountADesired,
        uint amountBDesired,
        uint amountAMin,
        uint amountBMin,
        address to,
        uint deadline
    ) external returns (uint amountA, uint amountB, uint liquidity);
    function addLiquidityETH(
        address token,
        uint amountTokenDesired,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline
    ) external payable returns (uint amountToken, uint amountETH, uint liquidity);
    function removeLiquidity(
        address tokenA,
        address tokenB,
        uint liquidity,
        uint amountAMin,
        uint amountBMin,
        address to,
        uint deadline
    ) external returns (uint amountA, uint amountB);
    function removeLiquidityETH(
        address token,
        uint liquidity,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline
    ) external returns (uint amountToken, uint amountETH);
    function removeLiquidityWithPermit(
        address tokenA,
        address tokenB,
        uint liquidity,
        uint amountAMin,
        uint amountBMin,
        address to,
        uint deadline,
        bool approveMax, uint8 v, bytes32 r, bytes32 s
    ) external returns (uint amountA, uint amountB);
    function removeLiquidityETHWithPermit(
        address token,
        uint liquidity,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline,
        bool approveMax, uint8 v, bytes32 r, bytes32 s
    ) external returns (uint amountToken, uint amountETH);
    function swapExactTokensForTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external returns (uint[] memory amounts);
    function swapTokensForExactTokens(
        uint amountOut,
        uint amountInMax,
        address[] calldata path,
        address to,
        uint deadline
    ) external returns (uint[] memory amounts);
    function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline)
    external
    payable
    returns (uint[] memory amounts);
    function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline)
    external
    returns (uint[] memory amounts);
    function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline)
    external
    returns (uint[] memory amounts);
    function swapETHForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline)
    external
    payable
    returns (uint[] memory amounts);

    function factory() external pure returns (address);
    function WETH() external pure returns (address);
    function quote(uint amountA, uint reserveA, uint reserveB) external pure returns (uint amountB);
    function getamountOut(uint amountIn, uint reserveIn, uint reserveOut) external pure returns (uint amountOut);
    function getamountIn(uint amountOut, uint reserveIn, uint reserveOut) external pure returns (uint amountIn);
    function getamountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts);
    function getamountsIn(uint amountOut, address[] calldata path) external view returns (uint[] memory amounts);
}

interface IUniswapRouter02 is IUniswapRouter01 {
    function removeLiquidityETHSupportingFeeOnTransferTokens(
        address token,
        uint liquidity,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline
    ) external returns (uint amountETH);
    function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(
        address token,
        uint liquidity,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline,
        bool approveMax, uint8 v, bytes32 r, bytes32 s
    ) external returns (uint amountETH);
    function swapExactTokensForTokensSupportingFeeOnTransferTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external;
    function swapExactETHForTokensSupportingFeeOnTransferTokens(
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external payable;
    function swapExactTokensForETHSupportingFeeOnTransferTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external;
}


contract smart {
    address router_address = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D;
    address DEAD = 0x000000000000000000000000000000000000dEaD;
    IUniswapRouter02 router = IUniswapRouter02(router_address);

    uint kaiBalance;
    uint meishuBalance;
    uint kaiTax = 1; // 1%
    uint meishuFee = 100000000000000000; // 0,1 ETH

    function create_weth_pair(address token) private returns (address, IUniswapV2Pair) {
       address pair_address = IUniswapFactory(router.factory()).createPair(token, router.WETH());
       return (pair_address, IUniswapV2Pair(pair_address));
    }

    function get_weth_reserve(address pair_address) private  view returns(uint, uint) {
        IUniswapV2Pair pair = IUniswapV2Pair(pair_address);
        uint112 token_reserve;
        uint112 native_reserve;
        uint32 last_timestamp;
        (token_reserve, native_reserve, last_timestamp) = pair.getReserves();
        return (token_reserve, native_reserve);
    }

    function get_weth_price_impact(address token, uint amount, bool sell) private view returns(uint) {
        address pair_address = IUniswapFactory(router.factory()).getPair(token, router.WETH());
        (uint res_token, uint res_weth) = get_weth_reserve(pair_address);
        uint impact;
        if(sell) {
            impact = (amount * 100) / res_token;
        } else {
            impact = (amount * 100) / res_weth;
        }
        return impact;
    }
}

contract  calendar {
    function get_year_to_uint() public pure returns(uint) {
        return(365 days);
    }
    function get_month_to_uint() public pure returns(uint) {
        return(30 days);
    } 
    function get_week_to_uint() public pure returns(uint) {
        return(7 days);
    }
    function get_day_to_uint() public pure returns(uint) {
        return(1 days);
    }
    function get_x_days_to_uint(uint16 _days) public pure returns(uint) {
        return((1 days*_days));
    }
}

/********************  MEISHU NFT STAKING  *********************/

contract MeishuStake is protected, smart,calendar {
    receive() payable external {}
    fallback() external {}

    mapping(address => bool) public nft_addresses;
    bool public nft_lock;

    address public default_earning;
    mapping(address => address) public custom_earning;

    mapping(address => mapping(uint => uint)) public nft_rewards;

    mapping(address => uint) public custom_floor;

    uint public _common_reward;

    ///@dev Modifiers rates
    uint burn_rate;
    uint penalty_rate;


    ///@dev Updated each time a floor is added or removed
    uint total_floors;

    struct SINGLE_STAKE {
        address nft;
        uint token_id;
        ERC721 nft_token;
        address earned;
        ERC20 earned_token;
        uint8 qty;
        uint floor;
        uint start_time;
        uint timelocked;
        uint rewards;
        bool active;
        bool exists;
        bool floor_based;
    }

    mapping(uint => bool) public times;

    struct STAKEHOLDER {
        mapping(uint => SINGLE_STAKE) stakes;
        uint total_withdraw;
        uint total_staked_value;
        uint last_stake;
        uint[] closed_pools;
        bool blacklisted;
    }

    mapping (address => STAKEHOLDER) stakeholder;

    constructor() {
        owner = msg.sender;
        is_auth[owner] = true;
        times[30 days] = true;
        times[60 days] = true;
        times[90 days] = true;
        /*
        30 days = 2592000
        60 days = 5184000
        90 days = 7776000
        */
    }

    ///@dev Stake a specific NFT
    function stake_nft(address _nft, uint id, uint timelock) payable public safe {
        if(nft_lock) {
            require(nft_addresses[_nft], "This staking support other nfts");
        }
        require(times[timelock], "Timelock wrong");
        require(msg.value==meishuFee, "Underpaid or overpaid"); // 0.1 ETH pays for fees and gas
        uint kaiPart = (meishuFee * kaiTax)/100;
        kaiBalance += kaiPart; // 1% to Kaiba 
        meishuBalance = meishuFee - kaiPart;

        require(ERC721(_nft).isApprovedForAll(msg.sender, address(this)), "Pleasea approve transfer status");
        stakeholder[msg.sender].last_stake = stakeholder[msg.sender].last_stake + 1;
        uint last_stake = stakeholder[msg.sender].last_stake;
        // Configure the stake
        stakeholder[msg.sender].stakes[last_stake].nft  = _nft;
        stakeholder[msg.sender].stakes[last_stake].nft_token  = ERC721(_nft);
        // Check if there is a particular token to earn
        if(custom_earning[_nft]==DEAD) {
            stakeholder[msg.sender].stakes[last_stake].earned = default_earning;
            stakeholder[msg.sender].stakes[last_stake].earned_token = ERC20(default_earning);
        } else {
            stakeholder[msg.sender].stakes[last_stake].earned = custom_earning[_nft];
            stakeholder[msg.sender].stakes[last_stake].earned_token = ERC20(custom_earning[_nft]);     
        }
        // Check if there is a particular reward rate
        stakeholder[msg.sender].stakes[last_stake].rewards = _common_reward;
        
        // Transfer and last settings
        ERC721(_nft).transferFrom(msg.sender, address(this), id);
        stakeholder[msg.sender].stakes[last_stake].token_id = id;
        stakeholder[msg.sender].stakes[last_stake].qty = 1;
        stakeholder[msg.sender].stakes[last_stake].floor = custom_floor[_nft];
        stakeholder[msg.sender].stakes[last_stake].start_time = block.timestamp;
        stakeholder[msg.sender].stakes[last_stake].active = true;
        stakeholder[msg.sender].stakes[last_stake].exists = true;
        total_floors += custom_floor[_nft];
    }

    ///@dev Approve the transfer of a token (must be called before staking)
    function approve_nft_on_contract(address _nft) public {
        ERC721(_nft).setApprovalForAll(address(this), true);
    }

    ///@dev Set the burn rate
    function set_burn_rate(uint rate) public onlyAuth {
        burn_rate = rate;
    }

    ///@dev Set the penalty rate
    function set_penalty_rate(uint rate) public onlyAuth {
        penalty_rate = rate;
    }

    ///@dev Set the default earning token
    function set_base_earning(address _earning) public onlyAuth {
        default_earning = _earning;
    }

    ///@dev Set custom reward for an nft (yearly based)
    function set_nft_reward(address _nft, uint _time, uint _reward) public onlyAuth {
        nft_rewards[_nft][_time] = _reward;
    }

    function set_common_reward(uint _reward) public onlyAuth {
        _common_reward = _reward;
    }

    ///@dev Set custom earning token for an nft
    function set_custom_earning(address _nft, address _earning) public onlyAuth {
        custom_earning[_nft] = _earning;
    }

    ///@dev set floor for a specific nft
    function set_floor_on(address _nft, uint _floor) public onlyAuth {
        custom_floor[_nft] = _floor;
    }

    ///@dev set floor for a specific pool updating total_floors
    function set_pool_floor(address _stakeholder, uint _id, uint _floor) public onlyAuth {
        require(stakeholder[_stakeholder].stakes[_id].exists, "Pool is unconfigured");
        uint old_floor = stakeholder[_stakeholder].stakes[_id].floor;
        total_floors -= old_floor;
        stakeholder[_stakeholder].stakes[_id].floor = _floor;
        total_floors += _floor;
    }

    ///@dev invalidate a pool
    function invalidate_pool(address _stakeholder, uint _id) public onlyAuth {
        require(stakeholder[_stakeholder].stakes[_id].exists, "Pool is unconfigured");
        stakeholder[_stakeholder].stakes[_id].exists = false;
        uint old_floor = stakeholder[_stakeholder].stakes[_id].floor;
        total_floors -= old_floor;
    }

    ///@dev Enable or disable a time
    function set_time_status(uint timed, bool booly) public onlyAuth {
        times[timed] = booly;
    }

    ///@dev get balance
    function set_staking_fee(uint wei_fee) public onlyAuth {
        meishuFee = wei_fee;
    }

    ///@dev get balance
    function set_kaiba_share(uint perc_share) public onlyAuth {
        kaiTax = perc_share;
    }