contract ExchangeSuperUSDC is Ownable, Scheduller, InTokenWrapper {
	using SafeMath for uint256;
    using SafeERC20 for IERC20;
    using Address for address;
	
	//Constants
    uint256 private constant CALC_PRECISION = 1e18; 
    //Out Token Interface
    IERC20 public outToken;
    // period => Out Tokens available for exchange this period
    mapping (uint256 => uint256) public historyOutTokenForExchange;
    // Out Token units owed to users
    uint256 public outTokenDebt;
    
    /** 
    * @dev Updates Period before executing function 
    * @dev If Period changed, calculates new period Out Token for exchange & Out Token Debt
    * @param _startNow set periodStartTime = now if true
    */
    modifier updatePeriod(bool _startNow) {
    	uint256 _period = period; 
        _updatePeriod(_startNow);
        if(_period != period){
            uint256 _nextPeriodForExchange;
            uint256 _previousPeriodDebt;
            (_nextPeriodForExchange, _previousPeriodDebt) = _getOutTokenForExchange(_period);
            historyOutTokenForExchange[period] = _nextPeriodForExchange;
            outTokenDebt = outTokenDebt.add(_previousPeriodDebt);
        }
        _;
    }

    /** 
    * @dev Exchange constructor, calls constructors of helper classes InTokenWrapper and Scheduller
    * @param _outToken Out Token contract address
    * @param _inToken In Token contract address
    * @param _periodDays period length in Days
    */
    constructor (
    	address _outToken,
    	address _inToken,
    	uint256 _periodDays
    )
    	public
    	InTokenWrapper(_inToken)
    	Scheduller(_periodDays)
    {
    	setOutToken(_outToken);
    }

    /***************************************
                    PRIVATE
    ****************************************/

    /** 
    * @dev Returns Out Tokens available for exchange for next period & debt for previous period
    * @param _previousPeriod - previous period
    */
    function _getOutTokenForExchange (
        uint256 _previousPeriod
    )
        private
        view
        returns (uint256 _nextPeriodForExchange, uint256 _previousPeriodDebt)
    {
        _previousPeriodDebt = historyOutTokenForExchange[_previousPeriod];
        //If Out Token balance was greater than total In Token balance for previous period
        //reduce previous period debt to total supply
        if(_previousPeriodDebt > historyTotalSupply(_previousPeriod))
            _previousPeriodDebt = historyTotalSupply(_previousPeriod);
        _nextPeriodForExchange = outToken.balanceOf(address(this)).sub(
            outTokenDebt.add(_previousPeriodDebt)
        );
        return (_nextPeriodForExchange, _previousPeriodDebt);
    }

    /***************************************
                    ADMIN
    ****************************************/

    /** 
    * @dev Sets Out Token contract address, in case if proxy contract address changes
	* @param _tokenAddress - new Out Token contract address
	*/
    function setOutToken (
    	address _tokenAddress
    )
    	public
    	onlyOwner
    {
    	require(_tokenAddress.isContract(), "address must be a contract");
    	outToken = IERC20(_tokenAddress);
    }
 
    /***************************************
                    ACTIONS
    ****************************************/
    
    /**
     * @dev Places amount of In Tokes to be exchanged to Out Tokens current period
     * if user has balance for previous period executeExchage() is done first
     * @param _amount of In Tokens Tokens
     */
    function requestExchange (uint256 _amount)
        external
        updatePeriod(true)
    {
        require(_amount != 0, "Cannot exchange 0");
    	require(historyOutTokenForExchange[period] != 0, "No Out Tokens for current period");
    	address _user = msg.sender;
        uint256 _balance = balanceOf(_user);
    	if(userPeriod(_user) != period && _balance != 0){
    		executeExchange();
            _balance = 0;
    	}
        require(_balance.add(_amount) <= historyOutTokenForExchange[period], "Balance is greater Out Tokens for period");
    	_deposit(_user, _amount, period);
    }

	/**
     * @dev Withdraws amount of In Tokens deposited current period
     * @param _amount of In Tokens Tokens
     */
    function requestWithdraw (uint256 _amount)
        external
        updatePeriod(false)
    {
    	address _user = msg.sender;
    	require (userPeriod(_user) == period, "No balance for current period");
    	_withdraw(_user, _amount, period);
    }

	/**
     * @dev Sends owed Out Tokens to sender for previos periods In Tokens deposits
     */
    function executeExchange ()
    	public 
    	updatePeriod(false)
    {
    	address _user = msg.sender;
    	uint256 _out = calculateOut(_user);
    	require (_out != 0, "Nothing to exchange");
        outTokenDebt = outTokenDebt.sub(_out);
		outToken.safeTransfer(_user, _out);
		_withdrawExecute(_user, _out);
    }
    

    /***************************************
                    GETTERS
    ****************************************/

    /**
    * @dev Returns total amount of InTokens locked into contract
    */
    function getInTokenBalance()
        external
        view
        returns (uint256)
    {
        return totalSupply();
    }

    /**
    * @dev Returns OutTokens available for exchange current period
    */
    function getOutTokenForExchange()
        public
        view
        returns(uint256 _outTokenForExhange)
    {
        uint256 _period = currentPeriod();
        if(period == _period)
            _outTokenForExhange = historyOutTokenForExchange[period];
        else
            (_outTokenForExhange, ) = _getOutTokenForExchange(period);
    }
    
    /**
    * @dev Returns InToken balance available for withdraw
    * @param _user Address of the user
    */
    function getInTokenBalanceForWithdraw(address _user)
        external
        view
        returns(uint256)
    {
        uint256 _period = currentPeriod();
        uint256 _userPeriod = userPeriod(_user);
        if(_userPeriod == _period)
            return balanceOf(_user);
    }

    /**
    * @dev Calculates In Tokens contract will accept from user current period
    * @param _user Address of the user
    */
    function calculateIn (address _user)
        external
        view
        returns(uint256 _in)
    {
        uint256 _period = currentPeriod();
        uint256 _userPeriod = userPeriod(_user);
        uint256 _outTokenForExhange = getOutTokenForExchange();
        if(_userPeriod != _period){
            _in = _outTokenForExhange;
        }else{
            _in = _outTokenForExhange.sub(balanceOf(_user));
        }
    }
    
    /**
    * @dev Calculates Out Tokens owed to user for past periods
    * @param _user Address of the user
    */
    function calculateOut (address _user) 
    	public
    	view
    	returns (uint256 _owed)
    {
    	uint256 _period = currentPeriod();
    	uint256 _userPeriod = userPeriod(_user);
    	if(_userPeriod != _period){
    		if(historyOutTokenForExchange[_userPeriod] >= historyTotalSupply(_userPeriod)){
    			_owed = balanceOf(_user);
    		}else{
	    		_owed = historyOutTokenForExchange[_userPeriod].mul(
	    			balanceOf(_user)
	    		).mul(
	    			CALC_PRECISION
	    		).div(
	    			historyTotalSupply(_userPeriod)
	    		).div(
	    			CALC_PRECISION
	    		);
	    	}
	    }
    }
}
// 
// Libs
// Used contracts
//Internal
function requestWithdraw (uint256 _amount)
    external
    updatePeriod(false)
{
	address _user = msg.sender;
	require (userPeriod(_user) == period, "No balance for current period");
	_withdraw(_user, _amount, period);
}

/**
 * @dev Withdraws amount of In Tokens deposited current period
 * @param _amount of In Tokens Tokens
 */