contract WhiskyTokenCrowdsale is Ownable, Operated {
    using SafeMath for uint256;
    using SafeMath for uint64;

    // Address of the beneficiary which will receive the raised ETH 
    // Initialized during deployment
    address public beneficiary;

    // Deadline of the ICO as epoch time
    // Initialized when entering the first phase
    uint256 public deadline;

    // Amount raised by the ICO in Ether
    // Initialized during deployment
    uint256 public amountRaisedETH;

    // Amount raised by the ICO in Euro
    // Initialized during deployment
    uint256 public amountRaisedEUR;

    // Amount of tokens sold in Sip
    // Initialized during deployment
    uint256 public tokenSold;

    // Indicator if the funding goal has been reached
    // Initialized during deployment
    bool public fundingGoalReached;

    // Indicator if the ICO already closed
    // Initialized during deployment
    bool public crowdsaleClosed;

    // Internal indicator if we have checked our goals at the end of the ICO
    // Initialized during deployment
    bool private goalChecked;

    // Instance of our deployed Whisky Token
    // Initialized during deployment
    WhiskyToken public tokenReward;

    // Instance of the FIAT contract we use for ETH/EUR conversion
    // Initialized during deployment
    FiatContract public fiat;

    // Amount of Euro cents we need to reach for the softcap
    // 2.000.000 EUR
    uint256 private minTokenSellInEuroCents = 200000000;

    // Minimum amount of Euro cents you need to pay per transaction
    // 30 EUR    
    uint256 private minTokenBuyEuroCents = 3000;

    // Minimum amount of tokens (in Sip) which are sold at the softcap
    // 2.583.333 token
    uint256 private minTokenSell = 2583333 * 1 ether;

    // Maximum amount of tokens (in Sip) which are sold at the hardcap
    // 25.250.000 tokens
    uint256 private maxTokenSell = 25250000 * 1 ether;

    // Minimum amount of tokens (in Sip) which the beneficiary will receive
    // for the founders at the softcap
    // 308.627 tokens
    uint256 private minFounderToken = 308627 * 1 ether;

    // Maximum amount of tokens (in Sip) which the beneficiary will receive
    // for the founders at the hardcap
    // 1.405.000 tokens
    uint256 private maxFounderToken = 1405000 * 1 ether;

    // Minimum amount of tokens (in Sip) which the beneficiary will receive
    // for Research & Development and the Advisors after the ICO
    // 154.313 tokens
    uint256 private minRDAToken = 154313 * 1 ether;

    // Maximum amount of tokens (in Sip) which the beneficiary will receive
    // for Research & Development and the Advisors after the ICO
    // 1.405.000 tokens
    uint256 private maxRDAToken = 1405000 * 1 ether;

    // Amount of tokens (in Sip) which a customer will receive as bounty
    // 5 tokens
    uint256 private bountyTokenPerPerson = 5 * 1 ether;

    // Maximum amount of tokens (in Sip) which are available for bounty
    // 40.000 tokens
    uint256 private maxBountyToken = 40000 * 1 ether;

    // Amount of tokens which are left for bounty
    // Initialized during deployment
    uint256 public tokenLeftForBounty;

    // The pre-sale phase of the ICO
    // 333.333 tokens for 60 cent/token
    Phase private preSalePhase = Phase({
        id: PhaseID.PreSale,
        tokenPrice: 60,
        tokenForSale: 333333 * 1 ether,
        tokenLeft: 333333 * 1 ether
    });

    // The first public sale phase of the ICO
    // 2.250.000 tokens for 80 cent/token
    Phase private firstPhase = Phase({
        id: PhaseID.First,
        tokenPrice: 80,
        tokenForSale: 2250000 * 1 ether,
        tokenLeft: 2250000 * 1 ether
    });

    // The second public sale phase of the ICO
    // 21.000.000 tokens for 100 cent/token
    Phase private secondPhase = Phase({
        id: PhaseID.Second,
        tokenPrice: 100,
        tokenForSale: 21000000 * 1 ether,
        tokenLeft: 21000000 * 1 ether
    });

    // The third public sale phase of the ICO
    // 1.666.667 tokens for 120 cent/token
    Phase private thirdPhase = Phase({
        id: PhaseID.Third,
        tokenPrice: 120,
        tokenForSale: 1666667 * 1 ether,
        tokenLeft: 1666667 * 1 ether
    });

    // The closed phase of the ICO
    // No token for sell
    Phase private closedPhase = Phase({
        id: PhaseID.Closed,
        tokenPrice: ~uint64(0),
        tokenForSale: 0,
        tokenLeft: 0
    });

    // Points to the current phase
    Phase public currentPhase;

    // Structure for the phases
    // Consists of an id, the tokenPrice and the amount
    // of tokens available and left for sale
    struct Phase {
        PhaseID id;
        uint64 tokenPrice;
        uint256 tokenForSale;
        uint256 tokenLeft;
    }

    // Enumeration for identification of the phases
    enum PhaseID {
        PreSale,        // 0 
        First,          // 1
        Second,         // 2
        Third,          // 3
        Closed          // 4
    }    

    // Mapping of an address to a customer
    mapping(address => Customer) public customer;

    // Structure representing a customer
    // Consists of a rating, the amount of Ether and Euro the customer raised,
    // and a boolean indicating if he/she has already received a bounty
    struct Customer {
        Rating rating;
        uint256 amountRaisedEther;
        uint256 amountRaisedEuro;
        uint256 amountReceivedWhiskyToken;
        bool hasReceivedBounty;
    }

    // Enumeration for identification of a rating for a customer
    enum Rating {
        Unlisted,       // 0: No known customer, can't buy any token
        Whitelisted     // 1: Known customer by personal data, allowed to buy token
    }

    // Event definitions
    event SaleClosed();
    event GoalReached(address recipient, uint256 tokensSold, uint256 totalAmountRaised);
    event WhitelistUpdated(address indexed _account, uint8 _phase);
    event PhaseEntered(PhaseID phaseID);
    event TokenSold(address indexed customer, uint256 amount);
    event BountyTransfer(address indexed customer, uint256 amount);
    event FounderTokenTransfer(address recipient, uint256 amount);
    event RDATokenTransfer(address recipient, uint256 amount);
    event FundsWithdrawal(address indexed recipient, uint256 amount);

    // Constructor which gets called once on contract deployment
    constructor() public {
        setOps(msg.sender, true);
        beneficiary = msg.sender;
        tokenReward = new WhiskyToken(msg.sender);
        fiat = FiatContract(0x8055d0504666e2B6942BeB8D6014c964658Ca591); // Main
        currentPhase = preSalePhase;
        fundingGoalReached = false;
        crowdsaleClosed = false;
        goalChecked = false;
        tokenLeftForBounty = maxBountyToken;
        tokenReward.transfer(msg.sender, currentPhase.tokenForSale);
        currentPhase.tokenLeft = 0;
        tokenSold += currentPhase.tokenForSale;
        amountRaisedEUR = amountRaisedEUR.add((currentPhase.tokenForSale.div(1 ether)).mul(currentPhase.tokenPrice));
    }

    /**
     * @notice Not for public use!
     * @dev Advances the crowdsale to the next phase.
     */
    function nextPhase() public onlyOwner {
        require(currentPhase.id != PhaseID.Closed, "already reached the closed phase");

        uint8 nextPhaseNum = uint8(currentPhase.id) + 1;

        if (PhaseID(nextPhaseNum) == PhaseID.First) {
            currentPhase = firstPhase;
            deadline = now + 365 * 1 days;
        }
        if (PhaseID(nextPhaseNum) == PhaseID.Second) {
            currentPhase = secondPhase;
        }
        if (PhaseID(nextPhaseNum) == PhaseID.Third) {
            currentPhase = thirdPhase;
        }
        if (PhaseID(nextPhaseNum) == PhaseID.Closed) {
            currentPhase = closedPhase;
        }

        emit PhaseEntered(currentPhase.id);
    }

    /**
     * @notice Not for public use!
     * @dev Set the rating of a customer by address.
     * @param _account The address of the customer you want to change the rating of.
     * @param _phase The rating as an uint:
     * 0 => Unlisted
     * 1 => Whitelisted
     */
    function updateWhitelist(address _account, uint8 _phase) external onlyOps returns (bool) {
        require(_account != address(0), "zero address is not allowed");
        require(_phase == uint8(Rating.Unlisted) || _phase == uint8(Rating.Whitelisted), "invalid rating");

        Rating rating = Rating(_phase);
        customer[_account].rating = rating;
        emit WhitelistUpdated(_account, _phase);

        if (rating > Rating.Unlisted && !customer[_account].hasReceivedBounty && tokenLeftForBounty > 0) {
            customer[_account].hasReceivedBounty = true;
            customer[_account].amountReceivedWhiskyToken = customer[_account].amountReceivedWhiskyToken.add(bountyTokenPerPerson);
            tokenLeftForBounty = tokenLeftForBounty.sub(bountyTokenPerPerson);
            require(tokenReward.transfer(_account, bountyTokenPerPerson), "token transfer failed");
            emit BountyTransfer(_account, bountyTokenPerPerson);
        }

        return true;
    }

    /**
     * @dev Checks if the deadline is reached or the crowdsale has been closed.
     */
    modifier afterDeadline() {
        if ((now >= deadline && currentPhase.id >= PhaseID.First) || currentPhase.id == PhaseID.Closed) {
            _;
        }
    }

    /**
     * @notice Check if the funding goal was reached.
     * Can only be called after the deadline or if the crowdsale has been closed.
     * @dev Checks if the goal or time limit has been reached and ends the campaign.
     * Should be directly called after the ICO.
     */
    function checkGoalReached() public afterDeadline {
        if (!goalChecked) {
            if (_checkFundingGoalReached()) {
                emit GoalReached(beneficiary, tokenSold, amountRaisedETH);
            }
            if (!crowdsaleClosed) {
                crowdsaleClosed = true;
                emit SaleClosed();
            }
            goalChecked = true;
        }
    }

    /**
     * @dev Internal function for checking if we reached our funding goal.
     * @return Indicates if the funding goal has been reached.
     */
    function _checkFundingGoalReached() internal returns (bool) {
        if (!fundingGoalReached) {
            if (amountRaisedEUR >= minTokenSellInEuroCents) {
                fundingGoalReached = true;
            }
        }
        return fundingGoalReached;
    }

    /**
     * @dev Fallback function
     * The function without name is the default function that is called whenever anyone sends funds to a contract
     */
    function () external payable {
        _buyToken(msg.sender);
    }

    /**
     * @notice Buy tokens for ether. You can also just send ether to the contract to buy tokens.
     * Your address needs to be whitelisted first.
     * @dev Allows the caller to buy token for his address.
     * Implemented for the case that other contracts want to buy tokens.
     */
    function buyToken() external payable {
        _buyToken(msg.sender);
    }

    /**
     * @notice Buy tokens for another address. The address still needs to be whitelisted.
     * @dev Allows the caller to buy token for a different address.
     * @param _receiver Address of the person who should receive the tokens.
     */
    function buyTokenForAddress(address _receiver) external payable {
        require(_receiver != address(0), "zero address is not allowed");
        _buyToken(_receiver);
    }

    /**
     * @notice Not for public use!
     * @dev Send tokens to receiver who has payed with FIAT or other currencies.
     * @param _receiver Address of the person who should receive the tokens.
     * @param _cent The amount of euro cents which the person has payed.
     */
    function buyTokenForAddressWithEuroCent(address _receiver, uint64 _cent) external onlyOps {
        require(!crowdsaleClosed, "crowdsale is closed");
        require(_receiver != address(0), "zero address is not allowed");
        require(currentPhase.id != PhaseID.PreSale, "not allowed to buy token in presale phase");
        require(currentPhase.id != PhaseID.Closed, "not allowed to buy token in closed phase");
        require(customer[_receiver].rating == Rating.Whitelisted, "address is not whitelisted");
        _sendTokenReward(_receiver, _cent);        
        _checkFundingGoalReached();
    }

    /**
     * @dev Internal function for buying token.
     * @param _receiver Address of the person who should receive the tokens.
     */
    function _buyToken(address _receiver) internal {
        require(!crowdsaleClosed, "crowdsale is closed");
        require(currentPhase.id != PhaseID.PreSale, "not allowed to buy token in presale phase");
        require(currentPhase.id != PhaseID.Closed, "not allowed to buy token in closed phase");
        require(customer[_receiver].rating == Rating.Whitelisted, "address is not whitelisted");
        _sendTokenReward(_receiver, 0);
        _checkFundingGoalReached();
    }

    /**
     * @dev Internal function for sending token as reward for ether.
     * @param _receiver Address of the person who should receive the tokens.
     */
    function _sendTokenReward(address _receiver, uint64 _cent) internal {
        // Remember the ETH amount of the message sender, not the token receiver!
        // We need this because if the softcap was not reached
        // the message sender should be able to retrive his ETH
        uint256 amountEuroCents;
        uint256 tokenAmount;
        if (msg.value > 0) {
            uint256 amount = msg.value;
            customer[msg.sender].amountRaisedEther = customer[msg.sender].amountRaisedEther.add(amount);
            amountRaisedETH = amountRaisedETH.add(amount);
            amountEuroCents = amount.div(fiat.EUR(0));
            tokenAmount = (amount.div(getTokenPrice())) * 1 ether;
        } else if (_cent > 0) {
            amountEuroCents = _cent;
            tokenAmount = (amountEuroCents.div(currentPhase.tokenPrice)) * 1 ether;
        } else {
            revert("this should never happen");
        }
        
        uint256 sumAmountEuroCents = customer[_receiver].amountRaisedEuro.add(amountEuroCents);
        customer[_receiver].amountRaisedEuro = sumAmountEuroCents;
        amountRaisedEUR = amountRaisedEUR.add(amountEuroCents);

        require(((tokenAmount / 1 ether) * currentPhase.tokenPrice) >= minTokenBuyEuroCents, "must buy token for at least 30 EUR");
        require(tokenAmount <= currentPhase.tokenLeft, "not enough token left in current phase");
        currentPhase.tokenLeft = currentPhase.tokenLeft.sub(tokenAmount);

        customer[_receiver].amountReceivedWhiskyToken = customer[_receiver].amountReceivedWhiskyToken.add(tokenAmount);
        tokenSold = tokenSold.add(tokenAmount);
        require(tokenReward.transfer(_receiver, tokenAmount), "token transfer failed");
        emit TokenSold(_receiver, tokenAmount);
    }

    /**
     * @notice Withdraw your funds if the ICO softcap has not been reached.
     * @dev Checks to see if goal or time limit has been reached, and if so, and the funding goal was reached,
     * sends the entire ether amount to the beneficiary.
     * Also caluclates and sends the tokens for the founders, research & development and advisors.
     * All tokens which were not sold or send will be burned at the end.
     * If goal was not reached, each contributor can withdraw the amount they contributed.
     */
    function safeWithdrawal() public afterDeadline {
        require(crowdsaleClosed, "crowdsale must be closed");
        
        if (!fundingGoalReached) {
            // Let customers retrieve their ether
            require(customer[msg.sender].amountRaisedEther > 0, "message sender has not raised any ether to this contract");
            uint256 amount = customer[msg.sender].amountRaisedEther;
            customer[msg.sender].amountRaisedEther = 0;
            msg.sender.transfer(amount);
            emit FundsWithdrawal(msg.sender, amount);
        } else {
            // Let owner retrive current ether amount and founder token
            require(beneficiary == msg.sender, "message sender is not the beneficiary");
            uint256 ethAmount = address(this).balance;
            beneficiary.transfer(ethAmount);
            emit FundsWithdrawal(beneficiary, ethAmount);

            // Calculate and transfer founder token
            uint256 founderToken = (tokenSold - minTokenSell) * (maxFounderToken - minFounderToken) / (maxTokenSell - minTokenSell) + minFounderToken - (maxBountyToken - tokenLeftForBounty);
            require(tokenReward.transfer(beneficiary, founderToken), "founder token transfer failed");
            emit FounderTokenTransfer(beneficiary, founderToken);

            // Calculate and transfer research and advisor token
            uint256 rdaToken = (tokenSold - minTokenSell) * (maxRDAToken - minRDAToken) / (maxTokenSell - minTokenSell) + minRDAToken;
            require(tokenReward.transfer(beneficiary, rdaToken), "RDA token transfer failed");
            emit RDATokenTransfer(beneficiary, rdaToken);

            // Burn all leftovers
            tokenReward.burn(tokenReward.balanceOf(this));
        }
    }

    /**
     * @notice Not for public use!
     * @dev Allows early withdrawal of ether from the contract if the funding goal is reached.
     * Only the owner and beneficiary of the contract can call this function.
     * @param _amount The amount of ETH (in wei) which should be retreived.
     */
    function earlySafeWithdrawal(uint256 _amount) public onlyOwner {
        require(fundingGoalReached, "funding goal has not been reached");
        require(beneficiary == msg.sender, "message sender is not the beneficiary");
        require(address(this).balance >= _amount, "contract has less ether in balance than requested");

        beneficiary.transfer(_amount);
        emit FundsWithdrawal(beneficiary, _amount);
    }

    /**
     * @dev Internal function to calculate token price based on the ether price and current phase.
     */
    function getTokenPrice() internal view returns (uint256) {
        return getEtherInEuroCents() * currentPhase.tokenPrice / 100;
    }

    /**
     * @dev Internal function to calculate 1 EUR in WEI.
     */
    function getEtherInEuroCents() internal view returns (uint256) {
        return fiat.EUR(0) * 100;
    }

    /**
     * @notice Not for public use!
     * @dev Change the address of the fiat contract
     * @param _fiat The new address of the fiat contract
     */
    function setFiatContractAddress(address _fiat) public onlyOwner {
        require(_fiat != address(0), "zero address is not allowed");
        fiat = FiatContract(_fiat);
    }

    /**
     * @notice Not for public use!
     * @dev Change the address of the beneficiary
     * @param _beneficiary The new address of the beneficiary
     */
    function setBeneficiary(address _beneficiary) public onlyOwner {
        require(_beneficiary != address(0), "zero address is not allowed");
        beneficiary = _beneficiary;
    }
}
/**
 * @title WHISKY TOKEN ICO
 * @author WHYTOKEN GmbH
 * @notice WHISKY TOKEN (WHY) stands for a disruptive new possibility in the crypto currency market
 * due to the combination of High-End Whisky and Blockchain technology.
 * WHY is a german based token, which lets everyone participate in the lucrative crypto market
 * with minimal risk and effort through a high-end whisky portfolio as security.
 */
function buyTokenForAddress(address _receiver) external payable {
    require(_receiver != address(0), "zero address is not allowed");
    _buyToken(_receiver);
}

/**
 * @notice Buy tokens for another address. The address still needs to be whitelisted.
 * @dev Allows the caller to buy token for a different address.
 * @param _receiver Address of the person who should receive the tokens.
 */