contract Converter is ERC20, Ownable {
    using SafeMath for uint256;
 
    uint256 public dollarFeeRatio   = 200;  // USDC/ESD fee ratio
    uint256 public treasuryFeeRatio = 2000; // fee ratio of treasury
    uint256 public baseFactor       = 10000;// ratio base

    address public treasuryAddr;

    IERC20  public dollar;
    
    uint256 public dollarDecimal;

    mapping (address => uint256) public depositAt; // in case of flashloan attacks

    constructor(address _dollar, address _treasuryAddr, uint256 _dollarDecimal) 
        public  ERC20("Dynamic ESD Token","dESD")
    {
        dollar          = IERC20(_dollar);
        treasuryAddr    = _treasuryAddr;
        dollarDecimal   = _dollarDecimal;
    }

    function reset( uint256 _dollarFeeRatio, uint256 _treasuryFeeRatio, 
                    address _treasuryAddr)
            external onlyOwner 
    {
        dollarFeeRatio = _dollarFeeRatio;
        treasuryFeeRatio = _treasuryFeeRatio;
        treasuryAddr = _treasuryAddr;
    }

    /**
     * @dev Calculates the amount of dTokens that will be minted when the given amount 
     *      is deposited.
     *      used in the deposit() function to calculate the amount of dTokens that
     *      will be minted.
     *
     * @dev refer to keeperDao whose code is here: 
            https://etherscan.io/address/0x53463cd0b074e5fdafc55dce7b1c82adf1a43b2e#code
     *
     *      mintAmount = amountDeposited * (1-fee) * kPool /(pool + amountDeposited * fee)
     */
    function calculateMintAmount(uint256 _depositAmount) internal view returns (uint256) {
        // The total balance includes the deposit amount, which is removed here.        
        uint256 initialBalance = dollar.balanceOf(address(this)).sub(_depositAmount);

        if (totalSupply() == 0) {
            // if decimal is not 1e18, convert it.
            uint256 value = _depositAmount.mul(uint256(1e18)).div(10**dollarDecimal);
            return value;
        }

        return  (applyFee(dollarFeeRatio, _depositAmount).mul(totalSupply())).div(
                    initialBalance.add(
                        calculateFee(dollarFeeRatio, _depositAmount)
                ));
    }

    /// @dev it calculates (1 - fee) * amount
    function applyFee(uint256 _feeInBips, uint256 _amount) internal view returns (uint256) {
        return _amount.mul(baseFactor.sub(_feeInBips)).div(baseFactor); 
    }

    /// @dev it calculates fee * amount
    function calculateFee(uint256 _feeInBips, uint256 _amount) internal view returns (uint256) {
        return _amount.mul(_feeInBips).div(baseFactor); 
    }

    function convert(uint256 _amount) external {
        require(_amount > 0, "need gt 0");

        depositAt[msg.sender] = block.number;

        // usdc/esd fee, portion of it to treasury
        // the remaining will be rewarded to the former partners
        dollar.transferFrom(msg.sender, address(this), _amount);

        // mint shares with total fee
        uint256 value = calculateMintAmount(_amount);
        _mint(msg.sender, value);

        // transfer the specified amount to treasury.
        uint256 dollarFee = _amount.mul(dollarFeeRatio).div(baseFactor);
        uint256 dollarFeeForTreasury = dollarFee.mul(treasuryFeeRatio).div(baseFactor);

        // transfer the specified dollarFee to treasury.
        dollar.transfer(treasuryAddr, dollarFeeForTreasury);


        // check if get into the hardCap, need check the stake balance
        // if(dollar.balanceOf(address(this)) >= dollarHardCap) {
        //     uint256 stakingBalance = staking.getStakedBalance();
        //     require(stakingBalance >= dfiStakingNum, "staking power is not enough.");
        // }


        emit Mint(msg.sender, value);
    }

    function redeem(uint256 _value) external {
        require(balanceOf(msg.sender) >= _value && _value > 0, "not enough");

        require(depositAt[msg.sender] > 0, "No deposit history.");
        require(depositAt[msg.sender] < block.number, "Reject");

        uint256 value = _value.mul(dollar.balanceOf(address(this))).div(totalSupply());
        _burn(msg.sender, _value);
        dollar.transfer(msg.sender, value);

        emit Burn(msg.sender, _value);
    }


    event Mint(address indexed sender, uint256 amount);
    event Burn(address indexed sender, uint256 amount);
   
}
function calculateMintAmount(uint256 _depositAmount) internal view returns (uint256) {
        // The total balance includes the deposit amount, which is removed here.        
        uint256 initialBalance = dollar.balanceOf(address(this)).sub(_depositAmount);

        if (totalSupply() == 0) {
            // if decimal is not 1e18, convert it.
            uint256 value = _depositAmount.mul(uint256(1e18)).div(10**dollarDecimal);
            return value;
        }

        return  (applyFee(dollarFeeRatio, _depositAmount).mul(totalSupply())).div(
                    initialBalance.add(
                        calculateFee(dollarFeeRatio, _depositAmount)
                ));
    }
/**
 * @dev Calculates the amount of dTokens that will be minted when the given amount 
 *      is deposited.
 *      used in the deposit() function to calculate the amount of dTokens that
 *      will be minted.
 *
 * @dev refer to keeperDao whose code is here: 
 https://etherscan.io/address/0x53463cd0b074e5fdafc55dce7b1c82adf1a43b2e#code
 *
 *      mintAmount = amountDeposited * (1-fee) * kPool /(pool + amountDeposited * fee)
 */