contract IPMCrowdSale {
    using SafeMath for uint256;


    //////////////////////////////////////
    // Contract configuration           //
    //////////////////////////////////////
    // owner
    address owner;

    // allow pausing of contract to halt everything beside
    // administrative functions
    bool public paused    =   true;

    // min payment for private round
    uint256 public constant PRIVATE_PAYMENT_MIN =   2 ether;

    // min payment for other rounds
    uint256 public constant PUBLIC_PAYMENT_MIN  =   0.1 ether;

    // max payment is always equal
    uint256 public constant PAYMENT_MAX =   50 ether;


    // crowdsale can mint 9m IPM at maximum for all rounds
    uint256 public constant MAXIMUM_MINTABLE_TOKENS =   9000000000000000000000000;

    // start of private round 09/10/2020 @ 12:00pm UTC
    uint256 public constant PRIVATE_ROUND_START     =   1599739200;
    // end of private round 09/12/2020 @ 12:00pm UTC
    uint256 public constant PRIVATE_ROUND_END       =   1599912000;
    // private sale limit 1m 
    uint256 public constant PRIVATE_ROUND_CAP       =   1000000 * (10**18);

    // start of round 1 09/14/2020 @ 12:00pm UTC
    uint256 public constant ROUND_1_START           =   1600084800;
    // end of round 1 09/16/2020 @ 12:00pm UTC
    uint256 public constant ROUND_1_END             =   1600257600;
    // round 1 sale limit 1m
    uint256 public constant ROUND_1_CAP             =   1000000 * (10**18);

    // start of round 2 09/18/2020 @ 12:00pm UTC
    uint256 public constant ROUND_2_START           =   1600430400;
    // end of round 2 09/20/2020 @ 12:00pm UTC
    uint256 public constant ROUND_2_END             =   1600603200;
    // round 2 sale limit 2m
    uint256 public constant ROUND_2_CAP             =   2000000 * (10**18);

    // start of round 3 09/22/2020 @ 12:00pm UTC
    uint256 public constant ROUND_3_START           =   1600776000;
    // end of round 3 09/28/2020 @ 12:00pm UTC
    uint256 public constant ROUND_3_END             =   1601294400;
    // round 3 sale limit 5m
    uint256 public constant ROUND_3_CAP             =   5000000 * (10**18);

    // sold tokens private round
    uint256 public privateRoundSold;
    // sold tokens round 1
    uint256 public round1Sold;
    // sold tokens round 2
    uint256 public round2Sold;
    // sold tokens round 3
    uint256 public round3Sold;

    // private round white list 
    mapping(address => uint256) public whitelist;
    // contributors
    mapping(address => uint256) public contributors;

    // current rate
    uint256 public ipmPerETH;

    // IPM token references
    address public ipmTokenAddress;

    // withdrawal
    address public foundation1Address;
    address public foundation2Address;

    //////////////////////////////////////
    // Control functions / modifiers    //
    //////////////////////////////////////
    function isPrivateRoundActive() public view returns(bool) {
        return (now >= PRIVATE_ROUND_START && now < PRIVATE_ROUND_END);
    }
    function isRound1Active() public view returns(bool) {
        return (now >= ROUND_1_START && now < ROUND_1_END);
    }

    function isRound2Active() public view returns(bool) {
        return (now >= ROUND_2_START && now < ROUND_2_END);
    }

    function isRound3Active() public view returns(bool) {
        return (now >= ROUND_3_START && now < ROUND_3_END);
    }

    function hasStarted() public view returns(bool) {
        return (now > PRIVATE_ROUND_START);
    }

    function hasEnded() public view returns(bool) {
        return (now > ROUND_3_END);
    }

    modifier onlyOwner() {
        require(msg.sender == owner);
        _;
    }

    modifier ifPaused() {
        require(paused == true);
        _;
    }

    modifier ifNotPaused() {
        require(paused == false);
        _;
    }

    modifier saleActive() {
        require(hasStarted() == true, "Error: Sale has not started");
        require(hasEnded() == false, "Error: Sale has already ended");
        require(isPrivateRoundActive() || isRound1Active() || isRound2Active() || isRound3Active(), "Error: No round active at the moment");
        _;
    }

    //////////////////////////////////////
    // Events                           //
    //////////////////////////////////////
    event IPMPurchase(
        address indexed beneficiary,
        uint256 tokensPurchased,
        uint256 weiUsed
    );

    //////////////////////////////////////
    // Implementation                   //
    //////////////////////////////////////

    constructor() public {        
        paused  =   true;
        owner   =   msg.sender;
    }

    function getCurrentIPMRatio() external view returns(uint256) {
        return ipmPerETH;
    }

    function getCurrentRound() external view returns(string memory) {
        if(hasEnded()) {
            return "Finished";
        }
        if(isRound1Active()) {
            return "Pre-Sale Round 1";
        } else if(isRound2Active()) {
            return "Pre-Sale Round 2";
        } else if(isRound3Active()) {
            return "Pre-Sale Round 3";
        }
        return "Private Sale";
    }

    function getCurrentCap() public view returns (uint256) {
        if(hasEnded()) {
            return 0;
        }
        if(isRound1Active()) {
            return ROUND_1_CAP;
        } else if(isRound2Active()) {
            return ROUND_2_CAP;
        } else if(isRound3Active()) {
            return ROUND_3_CAP;
        }
        return PRIVATE_ROUND_CAP;
    }


    /**
     * @dev Used to update the current eth price of 1 IPM
     *      Function is needed to set the final price ahead
     *      of each round and for possible big price changes
     *      of eth itself to keep somewhat stable usd prices
     */
    function updateIPMPerETH(uint256 _tokens) external onlyOwner {
        require(hasEnded() == false, "Error: CrowdSale has ended, no need to update ratio");
        require(_tokens > 0, "Error: IPM per ETH can't be 0");
        require(_tokens != ipmPerETH, "Error: Prices are identical, no changes needed");
        require(_tokens < 100000, "Error: Amount of tokens per ETH seems unrealistically high. Input error?");

        ipmPerETH  =   _tokens;
    }

    function unpause() external onlyOwner ifPaused {
        paused = false;
    }   
    function pause() external onlyOwner ifNotPaused {
        paused = true;
    }


    function getTokenAddress() external view returns(address) {
        return ipmTokenAddress;
    }

    function setIPMTokenContract(address _token) external onlyOwner ifPaused {
        ipmTokenAddress =   _token;
    }

    function setWhitelist(address[] calldata _beneficiaries, uint256[] calldata _weiAmounts) external onlyOwner {
        require(_beneficiaries.length > 0, "Error: Beneficiaries are empty");
        require(_weiAmounts.length > 0, "Error: Investments are empty");
        require(_beneficiaries.length == _weiAmounts.length, "Error: Addresses length is not equal investments");
        
        for(uint256 i=0;i<_beneficiaries.length;i++) {
            whitelist[_beneficiaries[i]]    =   _weiAmounts[i];
        }
    }

    function addOrUpdateWhitelistEntry(address _beneficiary, uint256 _weiAmount) external onlyOwner {
        require(_weiAmount >= PRIVATE_PAYMENT_MIN, "Error: Investment is below private sell minimum");
        require(_weiAmount <= PAYMENT_MAX, "Error: Investment is above maximum sell amount");

        whitelist[_beneficiary]   =   _weiAmount;
    }

    function removeWhitelistEntry(address _beneficiary) external onlyOwner {
        require(whitelist[_beneficiary] > 0, "Error: Address is not whitelisted");
        whitelist[_beneficiary] =   0;
        delete whitelist[_beneficiary];

    }

    function isWhitelisted(address _beneficiary) public view returns(bool) {
        require(_beneficiary != address(0), 'Error: Address cannot be empty');
        return (whitelist[_beneficiary] > 0) ? true:false;
    }

    function setFoundation1Address(address _foundationAddress) external onlyOwner {
        require(_foundationAddress != address(0), 'Error: Address cannot be empty');
        foundation1Address = _foundationAddress;
    }

    function setFoundation2Address(address _foundationAddress) external onlyOwner {
        require(_foundationAddress != address(0), 'Error: Address cannot be empty');
        foundation2Address = _foundationAddress;
    }

    function withdrawFunds() external onlyOwner {
        require(hasStarted() == true, "Error: No reason to withdraw funds before sale has started");
        require(
            isPrivateRoundActive() == false &&
            isRound1Active() == false &&
            isRound2Active() == false &&
            isRound3Active() == false,
            "Error: Withdrawal during active rounds is not allowed"
        );
        require(foundation1Address != address(0), 'Error: No foundation1 wallet set');
        require(foundation2Address != address(0), 'Error: No foundation2 wallet set');

        uint256 fundsAvailable              =   address(this).balance;
        require(fundsAvailable > 0, "Error: No funds available to withdraw");

        uint256 amountForFoundation1Wallet  =   fundsAvailable.div(100).mul(70); 
        uint256 amountForFoundation2Wallet  =   fundsAvailable.sub(amountForFoundation1Wallet);
        require(amountForFoundation1Wallet.add(amountForFoundation2Wallet) == fundsAvailable, "Error: Amount to be sent is not equal the funds");

        payable(foundation1Address).transfer(amountForFoundation1Wallet);
        payable(foundation2Address).transfer(amountForFoundation2Wallet);  
    }

    /**
    * @dev Default fallback function that will also allow
    *      the contract owner to deposit additional ETH,
    *      without triggering the IPM purchase functionality.
    */
    receive() external payable {
        require(msg.value > 0, "Error: No ether received. Msg.value is empty");
        // no need for owner to buy
        if(msg.sender != owner) {
            // let others buy tokens
            _buyTokens(msg.sender, msg.value);
        }
    }

    function _buyTokens(address _beneficiary, uint256 _amountPayedInWei) internal saleActive {
        require(_beneficiary != address(0), "Error: Burn/Mint address cant purchase tokens");
        require(_hasAllowance(_beneficiary), "Error: Address is not allowed to purchase");
        
        require(_amountPayedInWei <= PAYMENT_MAX, "Error: Paymed exceeds maximum single purchase");
        
        uint256 tokensForPayment    =   _calculateTokensForPayment(_amountPayedInWei);
        uint256 tokensLeft          =   _getCurrentRemainingIPM();

        require(tokensForPayment > 0, "Error: payment too low. no tokens for this wei amount");
        require(tokensLeft > 0, "Error: No tokens left for this round");
        require(tokensLeft >= tokensForPayment, "Error: Purchase exceeds remaining tokens for this round");

        if(isPrivateRoundActive()) {
            uint256 alreadyPurchased    =   contributors[_beneficiary];
            uint256 allowedToPurchase   =   whitelist[_beneficiary];

            if(alreadyPurchased == 0) {
                require(_amountPayedInWei >= PRIVATE_PAYMENT_MIN, "Error: Payment smaller than minimum payment");
            }

            uint256 combinedPurchase    =   alreadyPurchased.add(_amountPayedInWei);

            require(combinedPurchase <= allowedToPurchase, "Error: This purchase exceeds the whitelisted limited");
        } 
        
        require(_amountPayedInWei >= PUBLIC_PAYMENT_MIN, "Error: Payment smaller than minimum payment");
        
        ERCMintable(ipmTokenAddress).mint(_beneficiary, tokensForPayment);
        
        if(isRound1Active()) {
            round1Sold = round1Sold.add(tokensForPayment);
        } else if(isRound2Active()) {
            round2Sold = round2Sold.add(tokensForPayment);
        } else if(isRound3Active()) {
            round3Sold = round3Sold.add(tokensForPayment);
        } else {
            privateRoundSold = privateRoundSold.add(tokensForPayment);
        }
        contributors[_beneficiary] =    contributors[_beneficiary].add(_amountPayedInWei);

        emit IPMPurchase(
            _beneficiary,
            tokensForPayment,
            _amountPayedInWei
        ); 
    }

    function _calculateTokensForPayment(uint256 payedWei) internal view returns(uint256) {
        require(payedWei > 0, "Error: Invalid wei amount");

        return payedWei.mul(ipmPerETH);
    }

    function _hasAllowance(address _beneficiary) internal view returns(bool) {
        if(isPrivateRoundActive()) {
            return (whitelist[_beneficiary] > 0);
        }
        return true;
    }

    function _getCurrentRemainingIPM() internal view returns(uint256) {
        if(isRound1Active()) {
            return ROUND_1_CAP.sub(round1Sold);
        } else if(isRound2Active()) {
            return ROUND_2_CAP.sub(round2Sold);
        } else if(isRound3Active()) {
            return ROUND_3_CAP.sub(round3Sold.add(_getPrivateRoundOverhead()));
        }
        return PRIVATE_ROUND_CAP.add(ROUND_3_CAP).sub(privateRoundSold);
    }

    function _getPrivateRoundOverhead() internal view returns(uint256) {
        if(privateRoundSold > PRIVATE_ROUND_CAP) {
            return privateRoundSold.sub(PRIVATE_ROUND_CAP);
        }

        return 0;
    }

}
/**
 * @title IPM Token CrowdSale
 * @dev  CrowdSale contract for IPM Token:
 *
 *       Tokens for Sale: 9M IPM
 *       Minted on demand up to the hard cap per round. 
 *       Unsold supply won't be minted and will result in a
 *       lower circulating supply after the sale. Unsold tokens of each round
 *       don't transfer to the next round
 *
 *       PRIVATE ROUND:
 *       - whitelisted
 *       - garuanteed allocation, overminted gets reduced from last round
 *       - duration of 2 days (10.09.2020 - 12.09.2020)
 *       - Min-Max allocation per address 2 ETH - 50 ETH
 *       - 1M flexible Cap (ETH price on launch could result in more)
 *       - 1 IPM = ~0.15 USD
 *
 *       ROUND 1:
 *       - duration of 2 days (14.09.2020 - 16.09.2020)
 *       - 1 IPM = ~0.2 USD
 *       - 1M IPM Hard Cap
 *
 *       ROUND 2:
 *       - duration of 2 days (18.09.2020 - 20.09.2020)
 *       - 1 IPM = 0.3 USD
 *       - 2M IPM Hard Cap
 *
 *       ROUND 3:
 *       - duration of 6 days (22.09.2020 - 28.09.2020)
 *       - 1 IPM = 0.4 USD
 *       - 5M IPM Hard Cap (possible less, based on private round)
 *
 *       After CrowdSale:
 *       Cooldown phase of 5 days begins
 *       and will unpause all tokens.
 *
 * More at https://timers.network/
 *
 * @author @KTimersnetwork
 */
function isPrivateRoundActive() public view returns(bool) {
    return (now >= PRIVATE_ROUND_START && now < PRIVATE_ROUND_END);
}

//////////////////////////////////////