contract DadiPublicSale is Ownable {
    using SafeMath for uint256;

    StandardToken public token;                         // The DADI ERC20 token */

    uint256 public tokenSupply;
    uint256 public tokensPurchased = 0;
    uint256 public individualCap = 5000 * 1000;         // USD$5,000
    uint256 public tokenPrice = 500;                    // USD$0.50
    uint256 public ethRate;                             // ETH to USD Rate, set by owner: 1 ETH = ethRate USD
    uint256 public maxGasPrice;                         // Max gas price for contributing transactions.
 
    address[] public saleWallets;
    mapping(address => Investor) private investors;
    address[] private investorIndex;

    struct Investor {
      uint256 tokens;
      uint256 contribution;
      bool distributed;
      uint index;
    }

    /*****
    * State for Sale Modes
    *  0 - Preparing:            All contract initialization calls
    *  1 - PublicSale:           Contract is in the Sale Period
    *  2 - PublicSaleFinalized   Sale period is finalized, no more payments are allowed
    *  3 - Success:              Sale Successful
    *  4 - TokenDistribution:    Ssale finished, tokens can be distributed
    *  5 - Closed:               Sale closed, no tokens more can be distributed
    */
    enum SaleState { Preparing, PublicSale, PublicSaleFinalized, Success, TokenDistribution, Closed }
    SaleState public state = SaleState.Preparing;

    event LogTokenPurchase(address indexed purchaser, address indexed beneficiary, uint256 value, uint256 tokens);
    event LogTokenDistribution(address recipient, uint256 tokens);
    event LogRedistributeTokens(address recipient, SaleState _state, uint256 tokens);
    event LogFundTransfer(address wallet, uint256 value);
    event LogRefund(address wallet, uint256 value);
    event LogStateChange(SaleState _state);

    /*****
    * @dev Modifier to check that amount transferred is not 0
    */
    modifier nonZero() {
        require(msg.value != 0);
        _;
    }

    /*****
    * @dev The constructor function to initialize the Public sale
    * @param _token                         address   the address of the ERC20 token for the sale
    * @param _tokenSupply                   uint256   the amount of tokens available
    */
    function DadiPublicSale (StandardToken _token, uint256 _tokenSupply) public {
        require(_token != address(0));
        require(_tokenSupply != 0);

        token = StandardToken(_token);
        tokenSupply = _tokenSupply * (uint256(10) ** 18);
        maxGasPrice = 60000000000;       // 60 Gwei
    }

    /*****
    * @dev Fallback Function to buy the tokens
    */
    function () public nonZero payable {
        require(state == SaleState.PublicSale);
        buyTokens(msg.sender, msg.value);
    }

    /*****
    * @dev Allows the contract owner to add a new distribution wallet, used to hold funds safely
    * @param _wallet        address     The address of the wallet
    * @return success       bool        Returns true if executed successfully
    */
    function addSaleWallet (address _wallet) public onlyOwner returns (bool) {
        require(_wallet != address(0));

        saleWallets.push(_wallet);
        return true;
    }

    /*****
    * @dev Calculates the number of tokens that can be bought for the amount of Wei transferred
    * @param _amount    uint256     The amount of money invested by the investor
    * @return tokens    uint256     The number of tokens purchased for the amount invested
    */
    function calculateTokens (uint256 _amount) public constant returns (uint256 tokens) {
        tokens = _amount * ethRate / tokenPrice;
        return tokens;
    }

    /*****
    * @dev Called by the owner of the contract to modify the sale state
    */
    function setState (uint256 _state) public onlyOwner {
        state = SaleState(uint(_state));
        LogStateChange(state);
    }

    /*****
    * @dev Called by the owner of the contract to start the Public sale
    * @param rate   uint256  the current ETH USD rate, multiplied by 1000
    */
    function startPublicSale (uint256 rate) public onlyOwner {
        state = SaleState.PublicSale;
        updateEthRate(rate);
        LogStateChange(state);
    }

    /*****
    * @dev Allow updating the ETH USD exchange rate
    * @param rate   uint256  the current ETH USD rate, multiplied by 1000
    * @return bool  Return true if successful
    */
    function updateEthRate (uint256 rate) public onlyOwner returns (bool) {
        require(rate >= 100000);
        
        ethRate = rate;
        return true;
    }

    /*****
    * @dev Allow updating the max gas price
    * @param _maxGasPrice   uint256  the maximum gas price for a transaction, in Gwei
    */
    function updateMaxGasPrice(uint256 _maxGasPrice) public onlyOwner {
        require(_maxGasPrice > 0);

        maxGasPrice = _maxGasPrice;
    }

    /*****
    * @dev Allows transfer of tokens to a recipient who has purchased offline, during the PublicSale
    * @param _recipient     address     The address of the recipient of the tokens
    * @param _tokens        uint256     The number of tokens purchased by the recipient
    * @return success       bool        Returns true if executed successfully
    */
    function offlineTransaction (address _recipient, uint256 _tokens) public onlyOwner returns (bool) {
        require(_tokens > 0);

        // Convert to a token with decimals 
        uint256 tokens = _tokens * (uint256(10) ** uint8(18));

        // if the number of tokens is greater than available, reject tx
        if (tokens >= getTokensAvailable()) {
            revert();
        }

        addToInvestor(_recipient, 0, tokens);

        // Increase the count of tokens purchased in the sale
        updateSaleParameters(tokens);

        LogTokenPurchase(msg.sender, _recipient, 0, tokens);

        return true;
    }

    /*****
    * @dev Called by the owner of the contract to finalize the ICO
    *      and redistribute funds (if any)
    */
    function finalizeSale () public onlyOwner {
        state = SaleState.Success;
        LogStateChange(state);

        // Transfer any ETH to one of the sale wallets
        if (this.balance > 0) {
            forwardFunds(this.balance);
        }
    }

    /*****
    * @dev Called by the owner of the contract to close the Sale and redistribute any crumbs.
    * @param recipient     address     The address of the recipient of the tokens
    */
    function closeSale (address recipient) public onlyOwner {
        state = SaleState.Closed;
        LogStateChange(state);

        // redistribute unsold tokens to DADI ecosystem
        uint256 remaining = getTokensAvailable();
        updateSaleParameters(remaining);

        if (remaining > 0) {
            token.transfer(recipient, remaining);
            LogRedistributeTokens(recipient, state, remaining);
        }
    }

    /*****
    * @dev Called by the owner of the contract to allow tokens to be distributed
    */
    function setTokenDistribution () public onlyOwner {
        state = SaleState.TokenDistribution;
        LogStateChange(state);
    }

    /*****
    * @dev Called by the owner of the contract to distribute tokens to investors
    * @param _address       address     The address of the investor for which to distribute tokens
    * @return success       bool        Returns true if executed successfully
    */
    function distributeTokens (address _address) public onlyOwner returns (bool) {
        require(state == SaleState.TokenDistribution);
        
        // get the tokens available for the investor
        uint256 tokens = investors[_address].tokens;
        require(tokens > 0);

        require(investors[_address].distributed == false);

        investors[_address].distributed = true;
        // investors[_address].tokens = 0;
        // investors[_address].contribution = 0;

        token.transfer(_address, tokens);
      
        LogTokenDistribution(_address, tokens);
        return true;
    }

    /*****
    * @dev Called by the owner of the contract to distribute tokens to investors who used a non-ERC20 wallet address
    * @param _purchaseAddress        address     The address the investor used to buy tokens
    * @param _tokenAddress           address     The address to send the tokens to
    * @return success                bool        Returns true if executed successfully
    */
    function distributeToAlternateAddress (address _purchaseAddress, address _tokenAddress) public onlyOwner returns (bool) {
        require(state == SaleState.TokenDistribution);
        
        // get the tokens available for the investor
        uint256 tokens = investors[_purchaseAddress].tokens;
        require(tokens > 0);

        require(investors[_purchaseAddress].distributed == false);

        investors[_purchaseAddress].distributed = true;

        token.transfer(_tokenAddress, tokens);
      
        LogTokenDistribution(_tokenAddress, tokens);
        return true;
    }

    /*****
    * @dev Called by the owner of the contract to redistribute tokens if an investor has been refunded offline
    * @param investorAddress         address     The address the investor used to buy tokens
    * @param recipient               address     The address to send the tokens to
    */
    function redistributeTokens (address investorAddress, address recipient) public onlyOwner {
        uint256 tokens = investors[investorAddress].tokens;
        require(tokens > 0);
        require(investors[investorAddress].distributed == false);
        
        // remove tokens, so they can't be redistributed
        // investors[investorAddress].tokens = 0;
        investors[investorAddress].distributed = true;
        token.transfer(recipient, tokens);

        LogRedistributeTokens(recipient, state, tokens);
    }

    /*****
    * @dev Get the amount of tokens left for purchase
    * @return uint256 the count of tokens available
    */
    function getTokensAvailable () public constant returns (uint256) {
        return tokenSupply - tokensPurchased;
    }

    /*****
    * @dev Get the total count of tokens purchased
    * @return uint256 the count of tokens purchased
    */
    function getTokensPurchased () public constant returns (uint256) {
        return tokensPurchased;
    }

    /*****
    * @dev Converts an amount sent in Wei to the equivalent in USD
    * @param _amount      uint256       the amount sent to the contract, in Wei
    * @return uint256  the amount sent to this contract, in USD
    */
    function ethToUsd (uint256 _amount) public constant returns (uint256) {
        return (_amount * ethRate) / (uint256(10) ** 18);
    }

    /*****
    * @dev Get count of contributors
    * @return uint     the number of unique contributors
    */
    function getInvestorCount () public constant returns (uint count) {
        return investorIndex.length;
    }

    /*****
    * @dev Get an investor
    * @param _address      address       the wallet address of the investor
    * @return uint256  the amount contributed by the user
    * @return uint256  the number of tokens assigned to the user
    * @return uint     the index of the user
    */
    function getInvestor (address _address) public constant returns (uint256 contribution, uint256 tokens, bool distributed, uint index) {
        require(isInvested(_address));
        return(investors[_address].contribution, investors[_address].tokens, investors[_address].distributed, investors[_address].index);
    }

    /*****
    * @dev Get a user's invested state
    * @param _address      address       the wallet address of the user
    * @return bool  true if the user has already contributed
    */
    function isInvested (address _address) internal constant returns (bool isIndeed) {
        if (investorIndex.length == 0) return false;
        return (investorIndex[investors[_address].index] == _address);
    }

    /*****
    * @dev Update a user's invested state
    * @param _address      address       the wallet address of the user
    * @param _value        uint256       the amount contributed in this transaction
    * @param _tokens       uint256       the number of tokens assigned in this transaction
    */
    function addToInvestor(address _address, uint256 _value, uint256 _tokens) internal {
        // add the user to the investorIndex if this is their first contribution
        if (!isInvested(_address)) {
            investors[_address].index = investorIndex.push(_address) - 1;
        }
      
        investors[_address].tokens = investors[_address].tokens.add(_tokens);
        investors[_address].contribution = investors[_address].contribution.add(_value);
        investors[_address].distributed = false;
    }

    /*****
    * @dev Send ether to the sale collection wallets
    */
    function forwardFunds (uint256 _value) internal {
        uint accountNumber;
        address account;

        // move funds to a random saleWallet
        if (saleWallets.length > 0) {
            accountNumber = getRandom(saleWallets.length) - 1;
            account = saleWallets[accountNumber];
            account.transfer(_value);
            LogFundTransfer(account, _value);
        }
    }

    /*****
    * @dev Internal function to assign tokens to the contributor
    * @param _address       address     The address of the contributing investor
    * @param _value         uint256     The amount invested 
    * @return success       bool        Returns true if executed successfully
    */
    function buyTokens (address _address, uint256 _value) internal returns (bool) {
        require(tx.gasprice <= maxGasPrice);

        require(isValidContribution(_address, _value));

        uint256 boughtTokens = calculateTokens(_value);
        require(boughtTokens != 0);

        // if the number of tokens calculated for the given value is 
        // greater than the tokens available, reject the payment
        require(boughtTokens <= getTokensAvailable());

        // update investor state
        addToInvestor(_address, _value, boughtTokens);

        forwardFunds(_value);

        updateSaleParameters(boughtTokens);

        LogTokenPurchase(msg.sender, _address, _value, boughtTokens);

        return true;
    }

    /*****
    * @dev Check that the amount sent in the transaction is below the individual cap
    * Factors in previous transactions by the same investor
    * @param _address         address     The address of the user making the transaction
    * @param _amount          uint256     The amount sent in the transaction
    * @return        bool        Returns true if the amount is valid
    */
    function isValidContribution (address _address, uint256 _amount) internal constant returns (bool valid) {
        return isBelowCap(_amount + investors[_address].contribution); 
    }

    /*****
    * @dev Check that the amount sent in the transaction is below the individual cap
    * @param _amount         uint256     The amount sent in the transaction
    * @return        bool        Returns true if the amount is below the individual cap
    */
    function isBelowCap (uint256 _amount) internal constant returns (bool) {
        return ethToUsd(_amount) < individualCap;
    }

    /*****
    * @dev Generates a random number from 1 to max based on the last block hash
    * @param max     uint  the maximum value 
    * @return a random number
    */
    function getRandom(uint max) internal constant returns (uint randomNumber) {
        return (uint(keccak256(block.blockhash(block.number - 1))) % max) + 1;
    }

    /*****
    * @dev Internal function to modify parameters based on tokens bought
    * @param _tokens        uint256     The number of tokens purchased
    */
    function updateSaleParameters (uint256 _tokens) internal {
        tokensPurchased = tokensPurchased.add(_tokens);
    }
}
/*****
 * @title The DADI Public sale Contract
 */
function updateEthRate (uint256 rate) public onlyOwner returns (bool) {
    require(rate >= 100000);
        
    ethRate = rate;
    return true;
}

/*****
 * @dev Allow updating the ETH USD exchange rate
 * @param rate   uint256  the current ETH USD rate, multiplied by 1000
 * @return bool  Return true if successful
 */