contract SignkeysCrowdsale is Ownable, ReentrancyGuard {
    using SafeMath for uint256;

    uint256 public constant INITIAL_TOKEN_PRICE_CENTS = 10;

    /* Token contract */
    SignkeysToken public signkeysToken;

    /* Bonus program contract*/
    SignkeysBonusProgram public signkeysBonusProgram;

    /* signer address, can be set by owner only */
    address public signer;

    /* ETH funds will be transferred to this address */
    address public wallet;

    /* Role that provide tokens calling sendTokens method */
    address public administrator;

    /* Current token price in cents */
    uint256 public tokenPriceCents;

    /* Buyer bought the amount of tokens with tokenPrice */
    event BuyTokens(
        address indexed buyer,
        address indexed tokenReceiver,
        uint256 tokenPrice,
        uint256 amount
    );

    /* Admin sent the amount of tokens to the tokenReceiver */
    event SendTokens(
        address indexed tokenReceiver,
        uint256 amount
    );

    /* Wallet changed */
    event WalletChanged(address newWallet);

    /* Administrator changed */
    event AdministratorChanged(address newAdministrator);

    /* Signer changed */
    event CrowdsaleSignerChanged(address newSigner);

    /* Token price changed */
    event TokenPriceChanged(uint256 oldPrice, uint256 newPrice);

    constructor(
        address _token,
        address _bonusProgram,
        address _wallet,
        address _signer
    ) public {
        require(_token != 0x0, "Token contract for crowdsale must be set");
        require(_bonusProgram != 0x0, "Referrer smart contract for crowdsale must be set");

        require(_wallet != 0x0, "Wallet for fund transferring must be set");
        require(_signer != 0x0, "Signer must be set");

        signkeysToken = SignkeysToken(_token);
        signkeysBonusProgram = SignkeysBonusProgram(_bonusProgram);

        signer = _signer;
        wallet = _wallet;

        tokenPriceCents = INITIAL_TOKEN_PRICE_CENTS;
    }

    function setSignerAddress(address _signer) external onlyOwner {
        signer = _signer;
        emit CrowdsaleSignerChanged(_signer);
    }

    function setWalletAddress(address _wallet) external onlyOwner {
        wallet = _wallet;
        emit WalletChanged(_wallet);
    }

    function setAdministratorAddress(address _administrator) external onlyOwner {
        administrator = _administrator;
        emit AdministratorChanged(_administrator);
    }

    function setBonusProgram(address _bonusProgram) external onlyOwner {
        signkeysBonusProgram = SignkeysBonusProgram(_bonusProgram);
    }

    function setTokenPriceCents(uint256 _tokenPriceCents) external onlyOwner {
        emit TokenPriceChanged(tokenPriceCents, _tokenPriceCents);
        tokenPriceCents = _tokenPriceCents;
    }

    /**
     * @dev Make an investment.
     *
     * @param _tokenReceiver address where the tokens need to be transfered
     * @param _referrer address of user that invited _tokenReceiver for this purchase
     * @param _tokenPrice price per one token including decimals
     * @param _minWei minimal amount of wei buyer should invest
     * @param _expiration expiration on token
     */
    function buyTokens(
        address _tokenReceiver,
        address _referrer,
        uint256 _couponCampaignId, // starts with 1 if there is some, 0 means no coupon
        uint256 _tokenPrice,
        uint256 _minWei,
        uint256 _expiration,
        uint8 _v,
        bytes32 _r,
        bytes32 _s
    ) payable external nonReentrant {
        require(_expiration >= now, "Signature expired");
        require(_tokenReceiver != 0x0, "Token receiver must be provided");
        require(_minWei > 0, "Minimal amount to purchase must be greater than 0");

        require(wallet != 0x0, "Wallet must be set");
        require(msg.value >= _minWei, "Purchased amount is less than min amount to invest");

        address receivedSigner = ecrecover(
            keccak256(
                abi.encodePacked(
                    _tokenPrice, _minWei, _tokenReceiver, _referrer, _couponCampaignId, _expiration
                )
            ), _v, _r, _s);

        require(receivedSigner == signer, "Something wrong with signature");

        uint256 tokensAmount = msg.value.mul(10 ** uint256(signkeysToken.decimals())).div(_tokenPrice);
        require(signkeysToken.balanceOf(this) >= tokensAmount, "Not enough tokens in sale contract");

        // Pocket the money, or fail the crowdsale if we for some reason cannot send the money to our wallet
        wallet.transfer(msg.value);

        _sendTokens(_tokenReceiver, _referrer, _couponCampaignId, tokensAmount);

        emit BuyTokens(msg.sender, _tokenReceiver, _tokenPrice, tokensAmount);
    }

    function sendTokens(
        address _tokenReceiver,
        address _referrer,
        uint256 _couponCampaignId,
        uint256 tokensAmount
    ) external {
        require(msg.sender == administrator, "sendTokens() method may be called only by administrator ");
        _sendTokens(_tokenReceiver, _referrer, _couponCampaignId, tokensAmount);
        emit SendTokens(_tokenReceiver, tokensAmount);
    }

    function _sendTokens(
        address _tokenReceiver,
        address _referrer,
        uint256 _couponCampaignId,
        uint256 tokensAmount
    ) private {
        signkeysToken.transfer(_tokenReceiver, tokensAmount);

        // send bonuses according to signkeys bonus program
        signkeysBonusProgram.sendBonus(
            _referrer,
            _tokenReceiver,
            tokensAmount,
            (tokensAmount.mul(tokenPriceCents).div(10 ** uint256(signkeysToken.decimals()))),
            _couponCampaignId);
    }


    /**
     * Don't expect to just send in money and get tokens.
     */
    function() payable external {
        revert();
    }

    /* Withdraw all tokens from contract for any emergence case */
    function withdrawTokens() external onlyOwner {
        uint256 amount = signkeysToken.balanceOf(this);
        address tokenOwner = signkeysToken.owner();
        signkeysToken.transfer(tokenOwner, amount);
    }
}
function() payable external {
    revert();
}

/**
 * Don't expect to just send in money and get tokens.
 */