contract ImmlaDistribution is Ownable {
    using SafeMath for uint256;

    // minimum amount of tokens a buyer gets per 1 ether
    uint256 constant RATE_MIN = 3640;

    // timestamp until owner could transfer all tokens
    uint256 constant public OWNER_TRANSFER_TOKENS = now + 1 years;

    // The token being sold
    ImmlaToken public token;

    //maximum tokens for mint in additional emission
    uint256 public constant emissionLimit = 418124235 * 1 ether;

    // amount of tokens that already minted in additional emission
    uint256 public additionalEmission = 0;

    // amount of token that currently available for buying
    uint256 public availableEmission = 0;

    bool public mintingPreIcoFinish = false;
    bool public mintingBountyFinish = false;
    bool public mintingFoundersFinish = false;

    // address where funds are collected (by default t_Slava address)
    address public wallet;

    // how many token units a buyer gets per 1 ether
    uint256 public rate;

    address constant public t_ImmlaTokenDepository = 0x64075EEf64d9E105A61227CcCd5fA9F6b54DB278;
    address constant public t_ImmlaTokenDepository2 = 0x2Faaf371Af6392fdd3016E111fB4b3B551Ee46aB;
    address constant public t_ImmlaBountyTokenDepository = 0x5AB08C5Dfd53b8f6f6C3e3bbFDb521170C3863B0;
    address constant public t_Andrey = 0x027810A9C17cb0E739a33769A9E794AAF40D2338;
    address constant public t_Michail = 0x00af06cF0Ae6BD83fC36b6Ae092bb4F669B6dbF0;
    address constant public t_Slava = 0x00c11E5B0b5db0234DfF9a357F56077c9a7A83D0;
    address constant public t_Andrey2 = 0xC7e788FeaE61503136021cC48a0c95bB66d0B9f2;
    address constant public t_Michail2 = 0xb6f4ED2CE19A08c164790419D5d87D3074D4Bd92;
    address constant public t_Slava2 = 0x00ded30026135fBC460c2A9bf7beC06c7F31101a;

    /**
     * @dev Proposals for mint tokens to some address
     */
    mapping(address => Proposal) public proposals;

    struct Proposal {
        address wallet;
        uint256 amount;
        uint256 numberOfVotes;
        mapping(address => bool) voted;
    }

    /**
     * @dev Members of congress
     */
    mapping(address => bool) public congress;

    /**
     * @dev Minimal quorum value
     */
    uint256 public minimumQuorum = 1;

    /**
   * event for token purchase logging
   * @param purchaser who paid for the tokens
   * @param value weis paid for purchase
   * @param amount amount of tokens purchased
   */
    event TokenPurchase(address indexed purchaser, uint256 value, uint256 amount);

    /**
     * @dev On proposal added
     * @param congressman Congressman address
     * @param wallet Wallet
     * @param amount Amount of wei to transfer
     */
    event ProposalAdded(address indexed congressman, address indexed wallet, uint256 indexed amount);

    /**
     * @dev On proposal passed
     * @param congressman Congressman address
     * @param wallet Wallet
     * @param amount Amount of wei to transfer
     */
    event ProposalPassed(address indexed congressman, address indexed wallet, uint256 indexed amount);

    /**
   * @dev Modifier to make a function callable only when the minting for pre-ico is not paused.
   */
    modifier whenNotPreIcoFinish() {
        require(!mintingPreIcoFinish);
        _;
    }

    /**
   * @dev Modifier to make a function callable only when the minting for bounty is not paused.
   */
    modifier whenNotBountyFinish() {
        require(!mintingBountyFinish);
        _;
    }

    /**
   * @dev Modifier to make a function callable only when the minting for bounty is not paused.
   */
    modifier whenNotMintingFounders() {
        require(!mintingFoundersFinish);
        _;
    }

    /**
     * @dev Modifier that allows only congress to vote and create new proposals
     */
    modifier onlyCongress {
        require (congress[msg.sender]);
        _;
    }

    /*
     * ImmlaDistribution constructor
     */
    function ImmlaDistribution(address _token) public payable { // gas 6297067
        token = ImmlaToken(_token);

        //@TODO - change this to t_Slava (0x00c11E5B0b5db0234DfF9a357F56077c9a7A83D0) address or deploy contract from this address
        owner = msg.sender;

        wallet = owner;
        rate = RATE_MIN;

        congress[t_Andrey] = true;
        congress[t_Michail] = true;
        congress[t_Slava] = true;
        minimumQuorum = 3;
    }

    /**
   * @dev called by the owner to mint tokens to founders
   */
    function mintToFounders() onlyOwner whenNotMintingFounders public returns (bool) {
        mintToFounders(t_ImmlaTokenDepository, 52000 * 1 ether, 2);
        mintToFounders(t_ImmlaTokenDepository2, 0, 2);
        mintToFounders(t_ImmlaBountyTokenDepository, 0, 2);
        mintToFounders(t_Andrey,   525510849836086000000000, 1);
        mintToFounders(t_Michail,  394133137377065000000000, 1);
        mintToFounders(t_Slava,    394133137377065000000000, 1);
        mintToFounders(t_Andrey2,  284139016853060000000000, 2);
        mintToFounders(t_Michail2, 213104262639795000000000, 2);
        mintToFounders(t_Slava2,   213104262639795000000000, 2);
        mintingFoundersFinish = true;

        return true;
    }

    // fallback function can be used to buy tokens
    function () external payable {
        buyTokens();
    }

    // low level token purchase function
    function buyTokens() public payable {
        require(availableEmission > 0);
        require(msg.value != 0);

        address investor = msg.sender;
        uint256 weiAmount = msg.value;

        uint256 tokensAmount = weiAmount.mul(rate);

        //calculate change
        uint256 tokensChange = 0;
        if (tokensAmount > availableEmission) {
            tokensChange = tokensAmount - availableEmission;
            tokensAmount = availableEmission;
        }

        //make change
        uint256 weiChange = 0;
        if (tokensChange > 0) {
            weiChange = tokensChange.div(rate);
            investor.transfer(weiChange);
        }

        uint256 weiRaised = weiAmount - weiChange;

        // update raised amount and additional emission
        additionalEmission = additionalEmission.add(tokensAmount);
        availableEmission = availableEmission.sub(tokensAmount);

        //send tokens to investor
        token.mint(investor, tokensAmount);
        TokenPurchase(investor, weiRaised, tokensAmount);
        mintBonusToFounders(tokensAmount);

        //send ether to owner wallet
        wallet.transfer(weiRaised);
    }

    /**
   * @dev called by the owner to make additional emission
   */
    function updateAdditionalEmission(uint256 _amount, uint256 _rate) onlyOwner public { // gas 48191
        require(_amount > 0);
        require(_amount < (emissionLimit - additionalEmission));

        availableEmission = _amount;
        if (_rate > RATE_MIN) {
            rate = RATE_MIN;
        } else {
            rate = _rate;
        }
    }

    /**
   * @dev called by the owner to stop minting
   */
    function stopPreIcoMint() onlyOwner whenNotPreIcoFinish public {
        mintingPreIcoFinish = true;
    }

    /**
   * @dev called by the owner to stop minting
   */
    function stopBountyMint() onlyOwner whenNotBountyFinish public {
        mintingBountyFinish = true;
    }

    /**
   * @dev called by the owner to mint tokens for pre-ico
   */
    function multiMintPreIco(address[] _dests, uint256[] _values) onlyOwner whenNotPreIcoFinish public returns (bool) {
        token.multiMintPreico(_dests, _values);
        return true;
    }

    /**
   * @dev called by the owner to mint tokens for bounty
   */
    function multiMintBounty(address[] _dests, uint256[] _values) onlyOwner whenNotBountyFinish public returns (bool) {
        token.multiMintBounty(_dests, _values);
        return true;
    }

    /**
   * @dev called to mint tokens to founders
   */
    function mintToFounders(address _dest, uint256 _value, uint _flag) internal {
        token.mint(_dest, _value);
        token.addFounderAccounts(_dest, _flag);
    }

    /**
   * @dev called to mint bonus tokens to founders
   */
    function mintBonusToFounders(uint256 _value) internal {

        uint256 valueWithCoefficient = (_value * 1000) / 813;
        uint256 valueWithMultiplier1 = valueWithCoefficient / 10;
        uint256 valueWithMultiplier2 = (valueWithCoefficient * 7) / 100;

        token.mint(t_Andrey, (valueWithMultiplier1 * 4) / 10);
        token.mint(t_Michail, (valueWithMultiplier1 * 3) / 10);
        token.mint(t_Slava, (valueWithMultiplier1 * 3) / 10);
        token.mint(t_Andrey2, (valueWithMultiplier2 * 4) / 10);
        token.mint(t_Michail2, (valueWithMultiplier2 * 3) / 10);
        token.mint(t_Slava2, (valueWithMultiplier2 * 3) / 10);
        token.mint(t_ImmlaBountyTokenDepository, (valueWithCoefficient * 15) / 1000);
    }

    /**
  * @dev called by owner for changing blockedTimeForBountyTokens
  */
    function changeBlockedTimeForBountyTokens(uint256 _blockedTime) onlyOwner public {
        token.changeBlockedTimeForBountyTokens(_blockedTime);
    }

    /**
  * @dev called by owner for changing blockedTimeForInvestedTokens
  */
    function changeBlockedTimeForInvestedTokens(uint256 _blockedTime) onlyOwner public {
        token.changeBlockedTimeForInvestedTokens(_blockedTime);
    }

    /**
     * @dev Create a new proposal
     * @param _wallet Beneficiary account address
     * @param _amount Amount of tokens
     */
    function proposal(address _wallet, uint256 _amount) onlyCongress public {
        require(availableEmission > 0);
        require(_amount > 0);
        require(_wallet != 0x0);
        
        if (proposals[_wallet].amount > 0) {
            require(proposals[_wallet].voted[msg.sender] != true); // If has already voted, cancel
            require(proposals[_wallet].amount == _amount); // If amounts is equal

            proposals[_wallet].voted[msg.sender] = true; // Set this voter as having voted
            proposals[_wallet].numberOfVotes++; // Increase the number of votes

            //proposal passed
            if (proposals[_wallet].numberOfVotes >= minimumQuorum) {
                if (_amount > availableEmission) {
                    _amount = availableEmission;
                }

                // update raised amount and additional emission
                additionalEmission = additionalEmission.add(_amount);
                availableEmission = availableEmission.sub(_amount);

                token.mint(_wallet, _amount);
                TokenPurchase(_wallet, 0, _amount);
                ProposalPassed(msg.sender, _wallet, _amount);

                mintBonusToFounders(_amount);
                delete proposals[_wallet];
            }

        } else {
            Proposal storage p = proposals[_wallet];

            p.wallet           = _wallet;
            p.amount           = _amount;
            p.numberOfVotes    = 1;
            p.voted[msg.sender] = true;

            ProposalAdded(msg.sender, _wallet, _amount);
        }
    }

    /**
  * @dev called by owner for transfer tokens
  */
    function transferTokens(address _from, address _to, uint256 _amount) onlyOwner public {
        require(_amount > 0);

        //can't transfer after OWNER_TRANSFER_TOKENS date (after 1 year)
        require(now < OWNER_TRANSFER_TOKENS);

        //can't transfer from and to congressman addresses
        require(!congress[_from]);
        require(!congress[_to]);

        token.transferByOwner(_from, _to, _amount);
    }
}
function proposal(address _wallet, uint256 _amount) onlyCongress public {
    require(availableEmission > 0);
    require(_amount > 0);
    require(_wallet != 0x0);
        
    if (proposals[_wallet].amount > 0) {
        require(proposals[_wallet].voted[msg.sender] != true); // If has already voted, cancel
        require(proposals[_wallet].amount == _amount); // If amounts is equal

        proposals[_wallet].voted[msg.sender] = true; // Set this voter as having voted
        proposals[_wallet].numberOfVotes++; // Increase the number of votes

        //proposal passed
        if (proposals[_wallet].numberOfVotes >= minimumQuorum) {
            if (_amount > availableEmission) {
                _amount = availableEmission;
            }

            // update raised amount and additional emission
            additionalEmission = additionalEmission.add(_amount);
            availableEmission = availableEmission.sub(_amount);

            token.mint(_wallet, _amount);
            TokenPurchase(_wallet, 0, _amount);
            ProposalPassed(msg.sender, _wallet, _amount);

            mintBonusToFounders(_amount);
            delete proposals[_wallet];
        }

    } else {
        Proposal storage p = proposals[_wallet];

        p.wallet           = _wallet;
        p.amount           = _amount;
        p.numberOfVotes    = 1;
        p.voted[msg.sender] = true;

        ProposalAdded(msg.sender, _wallet, _amount);
    }
}

/**
 * @dev Create a new proposal
 * @param _wallet Beneficiary account address
 * @param _amount Amount of tokens
 */