contract AcexDeFi is
    Ownable,
    ReentrancyGuard
{
    using SafeMath for uint256;
    
    address private _usdtAddress = 0xdAC17F958D2ee523a2206206994597C13D831ec7; // Mainnet USDT
    ERC20 private _usdtContract = ERC20(_usdtAddress);
    uint256 public _minAmount = 0;
    uint256 public _processPeriod = 3 days;
    
    Lending.Round[] private _rounds;
    mapping (uint256 => mapping (address => uint256)) private _lendingAmounts;
    mapping (uint256 => mapping (address => bool)) private _redeemed;
    
    event Lend (
        address indexed lender,
        uint256 amount,
        uint256 round
    );
    
    event Redeem (
        address indexed lender,
        uint256 amount,
        uint256 round
    );
    
    function addRound (
        uint256 startTime,
        uint256 duration,
        uint256 apr,
        uint256 softCap,
        uint256 hardCap,
        uint256 personalCap
    )
        public
        onlyOwner 
    {
        _rounds.push(Lending.Round(
            startTime,
            duration,
            apr,
            softCap,
            hardCap,
            personalCap,
            0,
            false
        ));
    }
    
    function ownerUpdateMinAmount(uint256 minAmount)
        public
        onlyOwner
    {
        _minAmount = minAmount;
    }
    
    function ownerUpdateProcessPeriod(uint256 processPeriod)
        public
        onlyOwner
    {
        _processPeriod = processPeriod;
    }
    
    function ownerWithdrawRound(uint256 index)
        public
        onlyOwner
    {
        require(!_rounds[index].withdrawn, "ACEX DeFi: Round already withdrawn");
        
        _rounds[index].withdrawn = true;
        _usdtContract.transfer(msg.sender, _rounds[index].totalLendingAmount);
    }
    
    // Safety method in case user sends in ETH
    function ownerWithdrawAllETH()
        public
        onlyOwner
    {
        msg.sender.transfer(address(this).balance);
    }
    
    // Safety method in case user sends in ERC20 token
    function ownerWithdrawAllERC20(address tokenAddress)
        public
        onlyOwner
    {
        ERC20 erc20Contract = ERC20(tokenAddress);
        erc20Contract.transfer(msg.sender, erc20Contract.balanceOf(address(this)));
    }
    
    function getRounds()
        public
        view
        returns (Lending.Round[] memory rounds)
    {
        return _rounds;
    }
    
    function getPersonalRounds()
        public
        view
        returns (Lending.PersonalRound[] memory rounds)
    {
        rounds = new Lending.PersonalRound[](_rounds.length);
        
        for(uint i = 0; i < _rounds.length; i++) {
            rounds[i].round = _rounds[i];
            rounds[i].lendingAmount = _lendingAmounts[i][msg.sender];
            rounds[i].redeemed = _redeemed[i][msg.sender];
        }
        
        return rounds;
    }
    
    function lend (
        uint256 index,
        uint256 amount
    )
        public
        nonReentrant
    {
        Lending.Round storage round = _rounds[index];
        
        // Check current time (funding phase)
        require(now < round.startTime, "ACEX DeFi: Funding phase has passed.");
        
        // Check minimum amount
        require(amount > _minAmount, "ACEX DeFi: Amount too low");
        
        // Check personal cap
        uint256 personalLendingAmount = _lendingAmounts[index][msg.sender].add(amount);
        require(personalLendingAmount <= round.personalCap, "ACEX DeFi: Exceeds personal cap.");
        
        // Check hard cap
        uint256 totalLendingAmount = round.totalLendingAmount.add(amount);
        require(totalLendingAmount <= round.hardCap, "ACEX DeFi: Exceeds round hard cap.");
        
        _usdtContract.transferFrom(msg.sender, address(this), amount);
        _lendingAmounts[index][msg.sender] = personalLendingAmount;
        round.totalLendingAmount = totalLendingAmount;
        
        emit Lend(msg.sender, amount, index);
    }
    
    function redeem (
        uint256 index
    )
        public
        nonReentrant
    {
        Lending.Round storage round = _rounds[index];
        
        // Check current time (after startTime)
        require(now > round.startTime, "ACEX DeFi: Cannot redeem in funding phase.");
        
        // Check if user has redeemed
        require(!_redeemed[index][msg.sender], "ACEX DeFi: Already redeemed.");
        
        if (round.totalLendingAmount < round.softCap) {
            // Did not reach softCap, users can redeem after "startTime"
            
            // Pay back to user
            uint256 originalAmount = _lendingAmounts[index][msg.sender];
            
            _usdtContract.transfer(msg.sender, originalAmount);
            _redeemed[index][msg.sender] = true;
            emit Redeem(msg.sender, originalAmount, index);
        } else {
            // Reached softCap, users can redeem "amount + interest" after "startTime + duration + 3 days"
            
            // Check current time (redeem phase)
            require(now > round.startTime.add(round.duration).add(_processPeriod), "ACEX DeFi: Not redeem phase yet.");
            
            uint256 originalAmount = _lendingAmounts[index][msg.sender];
            
            // Interest = original * (apr * duration / 1 year in seconds)
            uint256 interestAmount = originalAmount.mul(round.apr).mul(round.duration).div(1000).div(365 days);
            uint256 totalAmount = originalAmount + interestAmount;
            
            _usdtContract.transfer(msg.sender, totalAmount);
            _redeemed[index][msg.sender] = true;
            emit Redeem(msg.sender, totalAmount, index);
        }
    }
}
function ownerWithdrawAllETH()
    public
    onlyOwner
{
    msg.sender.transfer(address(this).balance);
}

// Safety method in case user sends in ETH