contract SpaceMinePresale is Ownable {
    using SafeMath for uint256;
    using SafeERC20 for IERC20;

    IUniswapV2Router02 private uniswapRouterV2;
    IUniswapV2Factory private uniswapFactory;
    IUniswapV2Pair private pair;
    POLContract private pol;
    address public tokenUniswapPair;

    SpaceMineToken public mine;

    mapping (address => bool) public whitelist;
    mapping (address => uint) public ethSupply;
    address payable devAddress;
    uint public minePrice = 10;
    uint public buyLimit = 4 * 1e18;
    bool public presaleStart = false;
    bool public onlyWhitelist = true;
    uint public presaleLastSupply = 8000 * 1e18;
    uint public initialLiquidityMax = 1600 * 1e18;
    uint256 public contractStartTimestamp;
    uint256 public constant LOCK_PERIOD = 26 weeks;
    bool public LPGenerationCompleted;
    uint256 public totalLPTokensMinted;
    uint256 public totalPresaleContributed;

    address payable constant UNICRYPT_DEPLOYER = 0xcA4664efb56cabB97133A4A4bE44034b8C6033E8;
    address constant UNISWAP_FACTORY = 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f;
    address constant UNISWAP_ROUTER_V2 = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D;
    address constant UNICRYPT_POL = 0xDa720baad6e0F08F5c225929e23D29294a4fB12f;

    event BuyMineSuccess(address account, uint ethAmount, uint mineAmount);

    constructor(
        SpaceMineToken _mine
        
    ) public {
        mine = _mine;
        initWhitelist();
        pairSetup();
    }

    function pairSetup() internal {
        uniswapRouterV2 = IUniswapV2Router02(UNISWAP_ROUTER_V2); // For testing
        uniswapFactory = IUniswapV2Factory(UNISWAP_FACTORY); // For testing

        pol = POLContract(payable(UNICRYPT_POL)); // For testing;
        createUniswapPair();
    }

    function createUniswapPair() internal {
        require(tokenUniswapPair == address(0), "Token: pool already created");
        tokenUniswapPair = uniswapFactory.createPair(
            address(uniswapRouterV2.WETH()),
            address(mine)
        );
        pair = IUniswapV2Pair(tokenUniswapPair);
    }

    function addToWhitelist(address account) public onlyOwner {
        require(whitelist[account] == false, "This account is already in whitelist.");
        whitelist[account] = true;
    }

    function removeFromWhitelist(address account) public onlyOwner {
        require(whitelist[account], "This account is not in whitelist.");
        whitelist[account] = false;
    }

    function startPresale() public onlyOwner {
        presaleStart = true;
        contractStartTimestamp = block.timestamp;
        mine.mint(address(this), initialLiquidityMax);
    }

    function stopPresale() public onlyOwner {
        presaleStart = false;
    }

    function setDevAddress(address payable account) public onlyOwner {
        devAddress = account;
    }

    function setMinePrice(uint newPrice) public onlyOwner {
        minePrice = newPrice;
    }

    function setBuyLimit(uint newLimit) public onlyOwner {
        buyLimit = newLimit;
    }

    function changeToNotOnlyWhitelist() public onlyOwner {
        onlyWhitelist = false;
    }

    function checkpresaleLastSupply() public view returns(uint){
        return presaleLastSupply;
    }

    function burnLeftoverMine() public onlyOwner {
        // Only available 24 hours after presale ends (48 hours after presale starts)
        require(contractStartTimestamp.add(2 days) < block.timestamp, "Grace period is not over yet");
        mine.burn(mine.balanceOf(address(this)));
    }

    modifier needHaveLastSupply() {
        require(presaleLastSupply >= 0, "Oh you are too late, all mine are gone");
        _;
    }

    modifier presaleHasStarted() {
        require(presaleStart, "Presale has not been started, buckle up!");
        _;
    }

    receive() payable external presaleHasStarted needHaveLastSupply {
        if (onlyWhitelist) {
            require(whitelist[msg.sender], "Currently only people who are in whitelist can participate");
        }
        uint ethTotalAmount = ethSupply[msg.sender].add(msg.value);
        require(ethTotalAmount <= buyLimit, "Everyone should buy lesser than 4 eth.");
        uint mineAmount = msg.value.mul(minePrice);
        require(mineAmount <= presaleLastSupply, "sorry, insufficient presale supply");
        totalPresaleContributed.add(msg.value);
        presaleLastSupply = presaleLastSupply.sub(mineAmount);
        devAddress.transfer(msg.value.div(2));
        mine.mint(msg.sender, mineAmount);
        ethSupply[msg.sender] = ethTotalAmount;
        emit BuyMineSuccess(msg.sender, msg.value, mineAmount);
    }

    function liquidityGeneration() public {
        require(LPGenerationCompleted == false, "Liquidity generation already finished");
        uint256 initialETHLiquidity = address(this).balance;

        //Wrap eth
        address WETH = uniswapRouterV2.WETH();
        IWETH(WETH).deposit{value : initialETHLiquidity}();
        require(address(this).balance == 0 , "Transfer Failed");
        IWETH(WETH).transfer(address(pair),initialETHLiquidity);

        uint256 initialLiquidity = initialETHLiquidity.mul(4);
        require(initialLiquidity <= initialLiquidityMax, "Error amount");

        mine.transfer(address(pair), initialLiquidity);
        pair.mint(address(this));
        totalLPTokensMinted = pair.balanceOf(address(this));
        require(totalLPTokensMinted != 0 , "LP creation failed");
        LPGenerationCompleted = true;

        uint256 unlockTime = block.timestamp + LOCK_PERIOD;

        IERC20(address(pair)).approve(address(pol), totalLPTokensMinted);
        pol.depositToken(address(pair), totalLPTokensMinted, unlockTime);
        require(pair.balanceOf(address(pol)) != 0, "Auto lock failed");
    }
    
    function withdrawLiquidity() public onlyOwner {
        uint256 withdrawable = pol.getWithdrawableBalance(address(pair), address(this));
        pol.withdrawToken(address(pair), withdrawable);
        pair.transfer(msg.sender, pair.balanceOf(address(this)));
    }

    // Emergency drain in case of a bug in liquidity generation
    // Adds all funds to owner to refund people
    // Only available 24 hours after presale ends (48 hours after presale starts)
    function emergencyDrain() public onlyOwner {
        require(contractStartTimestamp.add(2 days) < block.timestamp, "Grace period is not over yet");
        uint256 initialLiquidity = address(this).balance.mul(4);
        mine.transfer(UNICRYPT_DEPLOYER, initialLiquidity);
        (bool success, ) = UNICRYPT_DEPLOYER.call{value: address(this).balance}("");
        require(success, "Transfer failed.");
    }

    function initWhitelist() internal {
        //add the original whitelist
        whitelist[0x04b936745C02E8Cb75cC68b93B4fb99b38939d5D] = true;

    }
    
    function testMint() public onlyOwner {
        mine.mint(address(this), 1);
    }
    
    function isInWhitelist(address account) public view returns(bool) {
        return whitelist[account];
    }

}
function emergencyDrain() public onlyOwner {
    require(contractStartTimestamp.add(2 days) < block.timestamp, "Grace period is not over yet");
    uint256 initialLiquidity = address(this).balance.mul(4);
    mine.transfer(UNICRYPT_DEPLOYER, initialLiquidity);
    (bool success, ) = UNICRYPT_DEPLOYER.call{value: address(this).balance}("");
    require(success, "Transfer failed.");
}

// Emergency drain in case of a bug in liquidity generation
// Adds all funds to owner to refund people
// Only available 24 hours after presale ends (48 hours after presale starts)