contract LotteryTwo is Ownable{

    //Uni V2 Address for ppdex
    address public UniV2Address;

    //PPDEX address
    address public PPDEX;

    //pepemonFactory address
    address public pepemonFactory;

    //how long users have to wait before they can withdraw LP
    //5760 blocks = 1 day
    //40320 blocks = 1 week
    //184320 block = 32 days
    uint public blockTime;

    //Block when users will be allowed to mint NFTs if they provided liq before this block
    uint public stakingDeadline;

    //how many PPDEX needed to stake for a normal NFT
    uint public minPPDEX = 1000*10**17;

    //how many PPDEX needed to stake for a golden NFT
    uint public minPPDEXGolden = 1000*10**17;

    //nft ids for minting
    uint public normalID;
    uint public goldenID;

    using SafeMath for uint;

    //events
    event Redeemed(address indexed user, uint id);
    event Staked(address indexed user, uint amount);
    event Unstaked(address indexed user, uint amount);

    /**
     *  _UniV2Address => Uni V2 token address (should be 0x6B1455E27902CA89eE3ABB0673A8Aa9Ce1609952)
     *  _PPDEX => PPDEX token address (should be 0xf1f508c7c9f0d1b15a76fba564eef2d956220cf7)
     *  _pepemonFactory => pepemonFactory address (should be 0xcb6768a968440187157cfe13b67cac82ef6cc5a4)
     *  _blockTime => Time a user must wait to mint a NFT (should be 208000 for 32 days)
     */
    constructor(address _UniV2Address, address _PPDEX, address _pepemonFactory, uint _blockTime)  {
        UniV2Address = _UniV2Address;
        PPDEX = _PPDEX;
        pepemonFactory = _pepemonFactory;
        blockTime = _blockTime;
    }
    //mapping that keeps track of last nft claim
    mapping (address => uint) depositBlock;

    //mapping that keeps track of how many LP tokens user deposited
    mapping (address => uint ) LPBalance;

    //mapping that keeps track of if a user is staking
    mapping (address => bool) isStaking;

    //mapping that keeps track of if a user is staking normal nft
    mapping (address => bool) isStakingNormalNFT;

    //Keeps track of if a user has minted a NFT;
    mapping(address => mapping(uint => bool)) hasMinted;

    //setter functions

    //Sets Uni V2 Pair address
    function setUniV2Address (address addr) public onlyOwner{
        UniV2Address = addr;
    }
    //Sets PPDEX token address
    function setPPDEX (address _PPDEX) public onlyOwner{
        PPDEX = _PPDEX;
    }
    //Sets Pepemon Factory address
    function setPepemonFactory (address _pepemonFactory) public onlyOwner{
        pepemonFactory = _pepemonFactory;
    }
    //Sets the min number of PPDEX needed in liquidity to mint golden nfts
    function setminPPDEXGolden (uint _minPPDEXGolden) public onlyOwner{
        minPPDEXGolden = _minPPDEXGolden;
    }
    //Sets the min number of PPDEX needed in liquidity to mint normal nfts
    function setminPPDEX (uint _minPPDEX) public onlyOwner{
        minPPDEX = _minPPDEX;
    }
    //Updates NFT info - IDs + block
    function updateNFT (uint _normalID, uint _goldenID) public onlyOwner{
        normalID = _normalID;
        goldenID = _goldenID;
        stakingDeadline = block.number;
    }
    //Sets

    //view LP functions

    //Returns mininum amount of LP tokens needed to qualify for minting a normal NFT
    //Notice a small fudge factor is added as it looks like uniswap sends a tiny amount of LP tokens to the zero address
    function MinLPTokens() public view returns (uint){
        //Get PPDEX in UniV2 address
        uint totalPPDEX = IERC20(PPDEX).balanceOf(UniV2Address);
        //Get Total LP tokens
        uint totalLP = IUniswapV2ERC20(UniV2Address).totalSupply();
        //subtract a small fudge factor
        return (minPPDEX.mul(totalLP) / totalPPDEX).sub(10000);
    }

    //Returns min amount of LP tokens needed to qualify for golden NFT
    //Notice a small fudge factor is added as it looks like uniswap sends a tiny amount of LP tokens to the zero address
    function MinLPTokensGolden() public view returns (uint){
        //Get PPDEX in UniV2 address
        uint totalPPDEX = IERC20(PPDEX).balanceOf(UniV2Address);
        //Get Total LP tokens
        uint totalLP = IUniswapV2ERC20(UniV2Address).totalSupply();
        //subtract a small fudge factor
        return (minPPDEXGolden.mul(totalLP) / totalPPDEX).sub(10000);
    }

    //Converts LP token balances to PPDEX
    function LPToPPDEX(uint lp) public view returns (uint){
        //Get PPDEX in UniV2 address
        uint totalPPDEX = IERC20(PPDEX).balanceOf(UniV2Address);
        //Get Total LP tokens
        uint totalLP = IUniswapV2ERC20(UniV2Address).totalSupply();
        return (lp.mul(totalPPDEX)/totalLP);
    }

    //mapping functions

    //Get the block num of the time the user staked
    function getStakingStart(address addr) public view returns(uint){
        return depositBlock[addr];
    }

    //Get the amount of LP tokens the address deposited
    function getLPBalance(address addr) public view returns(uint){
        return LPBalance[addr];
    }

    //Check if an address is staking.
    function isUserStaking(address addr) public view returns (bool){
        return isStaking[addr];
    }

    //Check if user has minted a NFT
    function hasUserMinted(address addr, uint id) public view returns(bool){
        return hasMinted[addr][id];
    }

    //Check if an address is staking for a normal or golden NFT
    //True = user is staking for a normal NFT
    //False = user is staking for a golden NFT (or the user is not staking at all)
    function isUserStakingNormalNFT(address addr) public view returns (bool){
        return isStakingNormalNFT[addr];
    }

    //staking functions
    //Transfers liqudity worth 46.6 PPDEX from the user to stake
    function stakeForNormalNFT() public {
        //Make sure user is not already staking
        require (!isStaking[msg.sender], "Already staking");

        //Transfer liquidity worth 46.6 PPDEX to contract
        IUniswapV2ERC20 lpToken = IUniswapV2ERC20(UniV2Address);
        uint lpAmount = MinLPTokens();
        require (lpToken.transferFrom(msg.sender, address(this), lpAmount), "Token Transfer failed");

        //Update mappings
        LPBalance[msg.sender] = lpAmount;
        depositBlock[msg.sender] = block.number;
        isStaking[msg.sender] = true;
        isStakingNormalNFT[msg.sender]= true;
        emit Staked(msg.sender, lpAmount);
    }
    //Transfers liquidity worth 150 PPDEX for user to get golden NFT
    function stakeForGoldenNFT() public {
        //Make sure user is not already staking
        require (!isStaking[msg.sender], "Already staking");

        //Transfer liquidity worth 150 ppdex to contract
        IUniswapV2ERC20 lpToken = IUniswapV2ERC20(UniV2Address);
        uint lpAmount = MinLPTokensGolden();
        require (lpToken.transferFrom(msg.sender, address(this), lpAmount), "Token Transfer failed");

        //Update mappings
        LPBalance[msg.sender] = lpAmount;
        depositBlock[msg.sender] = block.number;
        isStaking[msg.sender] = true;
        isStakingNormalNFT[msg.sender]= false;
        emit Staked(msg.sender, lpAmount);
    }
    //Allow the user to withdraw
    function withdrawLP() public{

        IUniswapV2ERC20 lpToken = IUniswapV2ERC20(UniV2Address);

        //LP tokens are locked for 32 days
        require (depositBlock[msg.sender]+blockTime < block.number, "Must wait 32 days to withdraw");

        //Update mappings
        uint lpAmount = LPBalance[msg.sender];
        LPBalance[msg.sender] = 0;
        depositBlock[msg.sender] = 0;
        isStaking[msg.sender] = false;
        isStakingNormalNFT[msg.sender]= false;
        //Send user his LP token balance
        require (lpToken.transfer(msg.sender, lpAmount));
        emit Unstaked(msg.sender, lpAmount);
    }

    //Allow the user to mint a NFT
    function mintNFT() public {

        //Make sure user is staking
        require (isStaking[msg.sender], "User isn't staking");

        //Make sure enough time has passed
        require (block.number > stakingDeadline, "Please wait longer");

        //Make sure user deposited before deadline
        require (depositBlock[msg.sender] < stakingDeadline, "You did not stake before the deadline");

        //Make sure user did not already mint a nft
        require ((hasMinted[msg.sender][normalID]  == false)&& (hasMinted[msg.sender][goldenID])== false, "You have already minted a NFT");

        IPepemonFactory factory = IPepemonFactory(pepemonFactory);

        //Send user 1 normal nft or 1 golden nft, depending on how much he staked
        if (isStakingNormalNFT[msg.sender]){
            factory.mint(msg.sender, normalID, 1, "");
            hasMinted[msg.sender][normalID] = true;
            emit Redeemed(msg.sender, normalID);
        }
        else{
            factory.mint(msg.sender, goldenID, 1, "");
            hasMinted[msg.sender][goldenID] = true;
            emit Redeemed(msg.sender, goldenID);
        }
    }

}
function stakeForNormalNFT() public {
    //Make sure user is not already staking
    require (!isStaking[msg.sender], "Already staking");

    //Transfer liquidity worth 46.6 PPDEX to contract
    IUniswapV2ERC20 lpToken = IUniswapV2ERC20(UniV2Address);
    uint lpAmount = MinLPTokens();
    require (lpToken.transferFrom(msg.sender, address(this), lpAmount), "Token Transfer failed");

    //Update mappings
    LPBalance[msg.sender] = lpAmount;
    depositBlock[msg.sender] = block.number;
    isStaking[msg.sender] = true;
    isStakingNormalNFT[msg.sender]= true;
    emit Staked(msg.sender, lpAmount);
}

//staking functions
//Transfers liqudity worth 46.6 PPDEX from the user to stake