contract Crowdsale is ReentrancyGuard, WhitelistedRole, ConverterRole {
  using SafeMath for uint256;
  using SafeERC20 for FundamentToken;

  struct Period {
    bool converted;
    address[] investors;
    mapping(address => uint256) balances;
  }

  Period[] periods;
  uint currentPeriodIndex;

  bool private _running;
  // The token being sold
  FundamentToken private _token;

  // Address where funds are collected
  address payable private _treasury;

  // Amount of wei raised
  uint256 private _weiRaised;

  // Maximum number of allowed investors per period
  uint256 _maxInvestors;

  /**
    * Event for recieving tokens after convertion
    * @param investor who comitted the ether
    * @param amount token amount obtained from convertion
    */ 
  event Payout (address investor, uint256 amount);

  /**
    * Event for committing logging
    * @param investor who comitted the ether
    * @param value weis comitted for purchase
    */
  event Committed
  (address indexed investor, uint256 value);

  /**
    * @param treasury Address where collected funds will be forwarded to
    * @param token Address of the token being sold
    * @param maxInvestors Number of maximum Investors per period
    */
  constructor (address payable treasury, FundamentToken token, uint256 maxInvestors) public {
      require(treasury != address(0));
      require(address(token) != address(0));
      require(maxInvestors > 0);

      periods.push(Period(true, new address[](0)));
      periods.push(Period(false, new address[](0)));
      currentPeriodIndex = 1;

      _treasury = treasury;
      _token = token;
      _maxInvestors = maxInvestors;
      _running = true; 
  }

  /**
    * @return the token being sold.
    */
  function token() public view returns (FundamentToken) {
      return _token;
  }
  /**
    * @dev Ends the crowdsale for good. Disallows any furhter commitment.
    *
    */
  function end() public onlyOwner {
    _running = false;
  }

  modifier onlyWhenRunning() {
    require(_running == true, "Sale has ended!");
    _;
  }

  /**
    * @return the address where funds are collected.
    */
  function treasury() public view returns (address payable) {
      return _treasury;
  }

  /**
    * @return the amount of wei raised.
    */
  function weiRaised() public view returns (uint256) {
      return _weiRaised;
  }

  /**
    * @dev This function has a non-reentrancy guard, so it shouldn't be called by
    * another `nonReentrant` function.
    */
  function commit() public nonReentrant payable onlyWhenRunning {
      require(msg.value > 0, 'You need to give some money');
      address from = msg.sender;
      uint256 weiAmount = msg.value;
      _preValidatePurchase(from, weiAmount);
      _token.mint(from, 0); // This is to initialize the balances array entry on the cost of the investor.
      if (periods[currentPeriodIndex].balances[from] == 0) {
        require(periods[currentPeriodIndex].investors.length < _maxInvestors, 'The limit of investors for this period has been reached.');
        periods[currentPeriodIndex].investors.push(from);
      }
      periods[currentPeriodIndex].balances[from] += weiAmount;

      // update state
      _weiRaised = _weiRaised.add(weiAmount);

      emit Committed(from, weiAmount);

      _updatePurchasingState(from, weiAmount);

      _forwardFunds();
      _postValidatePurchase(from, weiAmount);
  }

  function committedBalance(address investor) public view returns (uint256 committedAmount) {
      uint256 balanceInClosedPeriod = periods[1-currentPeriodIndex].balances[investor];
      uint256 balanceInCurrentPeriod = periods[currentPeriodIndex].balances[investor];

      if (periods[1-currentPeriodIndex].converted) {
        committedAmount = balanceInCurrentPeriod;  
      } else {
        committedAmount = balanceInClosedPeriod + balanceInCurrentPeriod;
      }
  }

  /**
    * @dev Validation of an incoming purchase. Use require statements to revert state when conditions are not met.
    * Use `super` in contracts that inherit from Crowdsale to extend their validations.
    * Example from CappedCrowdsale.sol's _preValidatePurchase method:
    *     super._preValidatePurchase(beneficiary, weiAmount);
    *     require(weiRaised().add(weiAmount) <= cap);
    * @param investor Address performing the token purchase
    * @param weiAmount Value in wei involved in the purchase
    */
  function _preValidatePurchase(address investor, uint256 weiAmount) internal view {
      require(isWhitelisted(investor));
      require(investor != address(0));
      require(weiAmount != 0);
  }

  /**
    * @dev Validation of an executed purchase. Observe state and use revert statements to undo rollback when valid
    * conditions are not met.
    * @param investor Address performing the token purchase
    * @param weiAmount Value in wei involved in the purchase
    */
  function _postValidatePurchase(address investor, uint256 weiAmount) internal view {
      // solhint-disable-previous-line no-empty-blocks
  }

  /**
     * @dev Overrides delivery by minting tokens upon purchase.
     * @param investor Token purchaser
     * @param tokenAmount Number of tokens to be minted
     */
    function _deliverTokens(address investor, uint256 tokenAmount) internal {
        // Potentially dangerous assumption about the type of the token.
        _token.mint(investor, tokenAmount);
    }

  /**
    * @dev Executed when a purchase has been validated and is ready to be executed. Doesn't necessarily emit/send
    * tokens.
    * @param investor Address receiving the tokens
    * @param tokenAmount Number of tokens to be purchased
    */
  function _processPurchase(address investor, uint256 tokenAmount) internal {
      _deliverTokens(investor, tokenAmount);
  }

  /**
    * @dev Override for extensions that require an internal state to check for validity (current user contributions,
    * etc.)
    * @param investor Address receiving the tokens
    * @param weiAmount Value in wei involved in the purchase
    */
  function _updatePurchasingState(address investor, uint256 weiAmount) internal {
      // solhint-disable-previous-line no-empty-blocks
  }

  /**
    * @dev Determines how ETH is stored/forwarded on purchases.
    */
  function _forwardFunds() internal {
      _treasury.transfer(msg.value);
  }

  function closePeriod() public onlyConverter {
    require(periods[1-currentPeriodIndex].converted, 'Please convert old period first.');
    periods[1-currentPeriodIndex].converted = false;
    currentPeriodIndex = (currentPeriodIndex + 1) % 2;
  }

  function convert(uint256 rate) public onlyConverter {
    require(!periods[1-currentPeriodIndex].converted, 'You already converted');
    for (uint i = 0; i < periods[1-currentPeriodIndex].investors.length; i++) {
      address investor = periods[1-currentPeriodIndex].investors[i];
      uint256 etherBalance = periods[1-currentPeriodIndex].balances[investor];

      uint256 tokenAmount = (rate * etherBalance) / 1 ether;

      emit Payout(investor, tokenAmount);

      _processPurchase(investor, tokenAmount);
      periods[1-currentPeriodIndex].balances[investor] = 0;
    }
    periods[1-currentPeriodIndex].investors.length = 0;
    periods[1-currentPeriodIndex].converted = true;
  }

  function transferOwnership(address newOwner) public onlyOwner {
        _addWhitelistAdmin(newOwner);
        _addConverter(newOwner);
        Ownable.transferOwnership(newOwner);
  }
}
/**
 * @title Crowdsale
 * @dev Crowdsale is an extended verion for fundament from the base crowdsale contract for managing a token crowdsale,
 * allowing investors to purchase tokens with ether. This contract implements
 * such functionality in its most fundamental form and can be extended to provide additional
 * functionality and/or custom behavior.
 * The external interface represents the basic interface for purchasing tokens, and conform
 * the base architecture for crowdsales. They are *not* intended to be modified / overridden.
 * The internal interface conforms the extensible and modifiable surface of crowdsales. Override
 * the methods to add functionality. Consider using 'super' where appropriate to concatenate
 * behavior.
 */
function _preValidatePurchase(address investor, uint256 weiAmount) internal view {
    require(isWhitelisted(investor));
    require(investor != address(0));
    require(weiAmount != 0);
}

/**
 * @dev Validation of an incoming purchase. Use require statements to revert state when conditions are not met.
 * Use `super` in contracts that inherit from Crowdsale to extend their validations.
 * Example from CappedCrowdsale.sol's _preValidatePurchase method:
 *     super._preValidatePurchase(beneficiary, weiAmount);
 *     require(weiRaised().add(weiAmount) <= cap);
 * @param investor Address performing the token purchase
 * @param weiAmount Value in wei involved in the purchase
 */