contract HumanTokenAllocator {
    using SafeMath for uint;
    HumanToken public Human;
    uint public rateEth = 700; // Rate USD per ETH
    uint public tokenPerUsdNumerator = 1;
    uint public tokenPerUsdDenominator = 1;
    uint public firstStageRaised;
    uint public secondStageRaised;
    uint public firstStageCap = 7*10**24;
    uint public secondStageCap = 32*10**24;
    uint public FIFTY_THOUSANDS_LIMIT = 5*10**22;
    uint teamPart = 7*10**24;

    bool public publicAllocationEnabled;

    address public teamFund;
    address public owner;
    address public oracle; // Oracle address
    address public company;

    event LogBuyForInvestor(address investor, uint humanValue, string txHash);
    event ControllerAdded(address _controller);
    event ControllerRemoved(address _controller);
    event FirstStageStarted(uint _timestamp);
    event SecondStageStarted(uint _timestamp);
    event AllocationFinished(uint _timestamp);
    event PublicAllocationEnabled(uint _timestamp);
    event PublicAllocationDisabled(uint _timestamp);

    mapping(address => bool) public isController;

    // Allows execution by the owner only     
    modifier onlyOwner {
        require(msg.sender == owner);
        _;
    }

    // Allows execution by the oracle only
    modifier onlyOracle { 
        require(msg.sender == oracle);
        _; 
    }

    // Allows execution by the controllers only
    modifier onlyControllers { 
        require(isController[msg.sender]);
        _; 
    }

    // Possible statuses
    enum Status {
        Created,
        firstStage,
        secondStage,
        Finished
    }

    Status public status = Status.Created;

   /**
    *   @dev Contract constructor function sets outside addresses
    */
    function HumanTokenAllocator(
        address _owner,
        address _oracle,
        address _company,
        address _teamFund,
        address _eventManager
    ) public {
        owner = _owner;
        oracle = _oracle;
        company = _company;
        teamFund = _teamFund;
        Human = new HumanToken(address(this), _eventManager);
    }   

   /**
    *   @dev Fallback function calls buy(address _holder, uint _humanValue) function to issue tokens
    */
    function() external payable {
        require(publicAllocationEnabled);
        uint humanValue = msg.value.mul(rateEth).mul(tokenPerUsdNumerator).div(tokenPerUsdDenominator);
        if (status == Status.secondStage) {
            require(humanValue >= FIFTY_THOUSANDS_LIMIT);
        } 
        buy(msg.sender, humanValue);
    }

   /**
    *   @dev Function to set rate of ETH
    *   @param _rateEth       current ETH rate
    */
    function setRate(uint _rateEth) external onlyOracle {
        rateEth = _rateEth;
    }

   /**
    *   @dev Function to set current token price
    *   @param _numerator       human token per usd numerator
    *   @param _denominator     human token per usd denominator
    */
    function setPrice(uint _numerator, uint _denominator) external onlyOracle {
        tokenPerUsdNumerator = _numerator;
        tokenPerUsdDenominator = _denominator;
    }
    

   /**
    *   @dev Function to issues tokens for investors who made purchases in other cryptocurrencies
    *   @param _holder        address the tokens will be issued to
    *   @param _humanValue    number of Human tokens
    *   @param _txHash        transaction hash of investor's payment
    */

    function buyForInvestor(
        address _holder, 
        uint _humanValue, 
        string _txHash
    ) 
        external 
        onlyControllers {
        buy(_holder, _humanValue);
        LogBuyForInvestor(_holder, _humanValue, _txHash);
    }

   /**
    *   @dev Function to issue tokens for investors who paid in ether
    *   @param _holder         address which the tokens will be issued tokens
    *   @param _humanValue     number of Human tokens
    */
    function buy(address _holder, uint _humanValue) internal {
        require(status == Status.firstStage || status == Status.secondStage);
        if (status == Status.firstStage) {
            require(firstStageRaised + _humanValue <= firstStageCap);
            firstStageRaised = firstStageRaised.add(_humanValue);
        } else {
            require(secondStageRaised + _humanValue <= secondStageCap);
            secondStageRaised = secondStageRaised.add(_humanValue);            
        }
        Human.mintTokens(_holder, _humanValue);
    }


  /**
   * @dev Function to add an address to the controllers
   * @param _controller         an address that will be added to managers list
   */
    function addController(address _controller) onlyOwner external {
        require(!isController[_controller]);
        isController[_controller] = true;
        ControllerAdded(_controller);
    }

  /**
   * @dev Function to remove an address to the controllers
   * @param _controller         an address that will be removed from managers list
   */
    function removeController(address _controller) onlyOwner external {
        require(isController[_controller]);
        isController[_controller] = false;
        ControllerRemoved(_controller);
    }

 /**
   * @dev Function to start the first stage of human token allocation
   *      and to issue human token for team fund
   */
    function startFirstStage() public onlyOwner {
        require(status == Status.Created);
        Human.mintTokens(teamFund, teamPart);
        status = Status.firstStage;
        FirstStageStarted(now);
    }

  /**  
   * @dev Function to start the second stage of human token allocation
   */
    function startSecondStage() public onlyOwner {
        require(status == Status.firstStage);
        status = Status.secondStage;
        SecondStageStarted(now);
    }

  /**  
   * @dev Function to finish human token allocation and to finish token issue
   */
    function finish() public onlyOwner {
        require (status == Status.secondStage);
        status = Status.Finished;
        AllocationFinished(now);
    }

  /**  
   * @dev Function to enable public token allocation
   */
    function enable() public onlyOwner {
        publicAllocationEnabled = true;
        PublicAllocationEnabled(now);
    }

  /**  
   * @dev Function to disable public token allocation
   */
    function disable() public onlyOwner {
        publicAllocationEnabled = false;
        PublicAllocationDisabled(now);
    }

  /**  
   * @dev Function to withdraw ether
   */    
    function withdraw() external onlyOwner {
        company.transfer(address(this).balance);
    }

    /** 
    *   @dev Allows owner to transfer out any accidentally sent ERC20 tokens
    *   @param tokenAddress  token address
    *   @param tokens        transfer amount
    */
    function transferAnyTokens(address tokenAddress, uint tokens) 
        public
        onlyOwner
        returns (bool success) {
        return ERC20(tokenAddress).transfer(owner, tokens);
    }      
}
/**
 *   @title HumanTokenAllocator contract  -  issues Human tokens
 */
function addController(address _controller) onlyOwner external {
    require(!isController[_controller]);
    isController[_controller] = true;
    ControllerAdded(_controller);
}

/**
 * @dev Function to add an address to the controllers
 * @param _controller         an address that will be added to managers list
 */