contract GoTokenDutchAuction {
/*
---------------------------------------------------------------------------------------------
    GoToken Uniform Price Dutch auction contract - distribution of a fixed
    number of tokens using seconprice auction, where everybody gets the lowest
    price when the auction ends i.e. the ending bid becomes the finalized
    price per token. This is the mechanism for price discovery.

    The uniform price Dutch auction is set up to discover a fair price for a
    fixed amount of GOT tokens. It starts with a very high price which
    continuously declines with every block over time, based on a predefined
    formula. After the auction is started, participants can send in ETH to bid.
    The auction ends once the price multiplied with the number of offered tokens
    equals the total ETH amount sent to the auction. All participants receive
    their tokens at the same final price.

    The main goals of the auction are to enable everyone to participate while
    offering certainty about the maximum total value of all tokens at the time
    of the bid.

    All token balances are transferable.
    Token name, ticker symbol and decimals
    1 token (GOT) = 1 indivisible unit * multiplier
    multiplier set from token's number of decimals (i.e. 10 ** decimals)

---------------------------------------------------------------------------------------------
*/

/*
---------------------------------------------------------------------------------------------
    Data structures for Storage
---------------------------------------------------------------------------------------------
*/

    GoToken public token;
    address public owner_address;
    address public wallet_address;
    address public whitelister_address;
    address public distributor_address;

    // Minimum bid value during the auction
    uint256 constant public bid_threshold = 10 finney;

    // Maximum contribution per ETH address during public sale
    //uint256 constant public MAX_CONTRIBUTION_PUBLICSALE = 20 ether;

    // token multiplied derived out of decimals
    uint256 public token_multiplier;

    // Total number of indivisible GoTokens (GOT * token_multiplier) that will be auctioned
    uint256 public num_tokens_auctioned;

/*
---------------------------------------------------------------------------------------------
    Price decay function parameters to be changed depending on the desired outcome
    This is modeled based on composite exponentially decaying curve auction price model.
    The price curves are mathematically modeled per the business needs. There are two
    exponentially decaying curves for teh auction: curve 1 is for teh first eight days
    and curve 2 is for the remaining days until the auction is finalized.
---------------------------------------------------------------------------------------------
*/

    // Starting price in WEI; e.g. 2 * 10 ** 18
    uint256 public price_start;

    uint256 constant public CURVE_CUTOFF_DURATION = 8 days;

    // Price constant for first eight days of the price curve; e.g. 1728
    uint256 public price_constant1;

    // Price exponent for first eight days of the price curve; e.g. 2
    uint256 public price_exponent1;

    // Price constant for eight plus days of the price curve; e.g. 1257
    uint256 public price_constant2;

    // Price exponent for eight plus days of the price curve; e.g. 2
    uint256 public price_exponent2;

    // For private sale start time (same as auction deployement time)
    uint256 public privatesale_start_time;

    // For calculating elapsed time for price auction
    uint256 public auction_start_time;
    uint256 public end_time;
    uint256 public start_block;

    // All ETH received from the bids
    uint256 public received_wei;
    uint256 public received_wei_with_bonus;

    // Cumulative ETH funds for which the tokens have been claimed
    uint256 public funds_claimed;

    // Wei per token (GOT * token_multiplier)
    uint256 public final_price;

    struct Account {
  		uint256 accounted;	// bid value including bonus
  		uint256 received;	// the amount received, without bonus
  	}

    // Address of the Bidder => bid value
    mapping (address => Account) public bids;

    // privatesalewhitelist for private ETH addresses
    mapping (address => bool) public privatesalewhitelist;

    // publicsalewhitelist for addresses that want to bid in public sale excluding private sale accounts
    mapping (address => bool) public publicsalewhitelist;

/*
---------------------------------------------------------------------------------------------
    Bonus tiers
---------------------------------------------------------------------------------------------
*/
    // Maximum duration after sale begins that 15% bonus is active.
  	uint256 constant public BONUS_DAY1_DURATION = 24 hours; ///24 hours;

  	// Maximum duration after sale begins that 10% bonus is active.
  	uint256 constant public BONUS_DAY2_DURATION = 48 hours; ///48 hours;

  	// Maximum duration after sale begins that 5% bonus is active.
  	uint256 constant public BONUS_DAY3_DURATION = 72 hours; ///72 hours;

    // The current percentage of bonus that contributors get.
  	uint256 public currentBonus = 0;

    // Waiting time in days before a participant can claim tokens after the end of the auction
    uint256 constant public TOKEN_CLAIM_WAIT_PERIOD = 0 days;

    // Keep track of stages during the auction and contract deployment process
    Stages public stage;

    enum Stages {
        AuctionDeployed,
        AuctionSetUp,
        AuctionStarted,
        AuctionEnded,
        TokensDistributed
    }

/*
---------------------------------------------------------------------------------------------
    Modifiers
---------------------------------------------------------------------------------------------
*/
    // State of the auction
    modifier atStage(Stages _stage) {
        require(stage == _stage);
        _;
    }

    // Only owner of the contract
    modifier isOwner() {
        require(msg.sender == owner_address);
        _;
    }

    // Only who is allowed to whitelist the participant ETH addresses (specified
    // during the contract deployment)
    modifier isWhitelister() {
        require(msg.sender == whitelister_address);
        _;
    }

    // Only who is allowed to distribute the GOT to the participant ETH addresses (specified
    // during the contract deployment)
    modifier isDistributor() {
        require(msg.sender == distributor_address);
        _;
    }
/*
---------------------------------------------------------------------------------------------
    Events
---------------------------------------------------------------------------------------------
*/

    event Deployed(uint256 indexed _price_start, uint256 _price_constant1, uint256 _price_exponent1, uint256  _price_constant2, uint256 _price_exponent2);
    event Setup();
    event AuctionStarted(uint256 indexed _auction_start_time, uint256 indexed _block_number);
    event BidSubmission(address indexed _sender,uint256 _amount, uint256 _amount_with_bonus, uint256 _remaining_funds_to_end_auction);
    event ClaimedTokens(address indexed _recipient, uint256 _sent_amount);
    event AuctionEnded(uint256 _final_price);
    event TokensDistributed();

    /// whitelisting events for private sale and public sale ETH addresses
  	event PrivateSaleWhitelisted(address indexed who);
    event RemovedFromPrivateSaleWhitelist(address indexed who);
    event PublicSaleWhitelisted(address indexed who);
    event RemovedFromPublicSaleWhitelist(address indexed who);

/*
---------------------------------------------------------------------------------------------
    Public facing functions
---------------------------------------------------------------------------------------------
*/

    /// @dev GoToken Contract constructor function sets the starting price,
    /// price constant and price exponent for calculating the Dutch Auction price.
    /// @param _wallet_address Wallet address to which all contributed ETH will be forwarded.
    function GoTokenDutchAuction(
        address _wallet_address,
        address _whitelister_address,
        address _distributor_address,
        uint256 _price_start,
        uint256 _price_constant1,
        uint256 _price_exponent1,
        uint256 _price_constant2,
        uint256 _price_exponent2)
        public
    {
        // Address shouldn't be null
        require(_wallet_address != 0x0);
        require(_whitelister_address != 0x0);
        require(_distributor_address != 0x0);
        wallet_address = _wallet_address;
        whitelister_address = _whitelister_address;
        distributor_address = _distributor_address;

        owner_address = msg.sender;
        stage = Stages.AuctionDeployed;
        changePriceCurveSettings(_price_start, _price_constant1, _price_exponent1, _price_constant2, _price_exponent2);
        Deployed(_price_start, _price_constant1, _price_exponent1, _price_constant2, _price_exponent2);
    }

    /// @dev Fallback function for the contract, which calls bid()
    function () public payable {
        bid();
    }

    /// @notice Set "_token_address" as the token address to be used in the auction.
    /// @dev Setup function sets external contracts addresses.
    /// @param _token_address Token address.
    function setup(address _token_address) public isOwner atStage(Stages.AuctionDeployed) {
        require(_token_address != 0x0);
        token = GoToken(_token_address);

        // Get number of GoToken indivisible tokens (GOT * token_multiplier)
        // to be auctioned from token auction balance
        num_tokens_auctioned = token.balanceOf(address(this));

        // Set the number of the token multiplier for its decimals
        token_multiplier = 10 ** (token.decimals());

        // State is set to Auction Setup
        stage = Stages.AuctionSetUp;
        Setup();
    }

    /// @notice Set "_price_start", "_price_constant1" and "_price_exponent1"
    ///  "_price_constant2" and "_price_exponent2" as
    /// the new starting price, price constant and price exponent for the auction price.
    /// @dev Changes auction price function parameters before auction is started.
    /// @param _price_start Updated start price.
    /// @param _price_constant1 Updated price divisor constant.
    /// @param _price_exponent1 Updated price divisor exponent.
    /// @param _price_constant2 Updated price divisor constant.
    /// @param _price_exponent2 Updated price divisor exponent.
    function changePriceCurveSettings(
        uint256 _price_start,
        uint256 _price_constant1,
        uint256 _price_exponent1,
        uint256 _price_constant2,
        uint256 _price_exponent2)
        internal
    {
        // You can change the price curve settings only when either the auction is Deployed
        // or the auction is setup. You can't change during the auction is running or ended.
        require(stage == Stages.AuctionDeployed || stage == Stages.AuctionSetUp);
        require(_price_start > 0);
        require(_price_constant1 > 0);
        require(_price_constant2 > 0);

        price_start = _price_start;
        price_constant1 = _price_constant1;
        price_exponent1 = _price_exponent1;
        price_constant2 = _price_constant2;
        price_exponent2 = _price_exponent2;
    }

/*
---------------------------------------------------------------------------------------------
    Functions related to whitelisting of presale and public sale ETH addresses.
    The Whitelister must add the participant's ETH address before they can bid.
---------------------------------------------------------------------------------------------
*/
    // @notice Adds account addresses to public sale ETH whitelist.
    // @dev Adds account addresses to public sale ETH whitelist.
    // @param _bidder_addresses Array of addresses. Use double quoted array.
    function addToPublicSaleWhitelist(address[] _bidder_addresses) public isWhitelister {
        for (uint32 i = 0; i < _bidder_addresses.length; i++) {
            require(!privatesalewhitelist[_bidder_addresses[i]]); //Can't be in public whitelist
            publicsalewhitelist[_bidder_addresses[i]] = true;
            PublicSaleWhitelisted(_bidder_addresses[i]);
        }
    }

    // @notice Removes account addresses from public sale ETH whitelist.
    // @dev Removes account addresses from public sale ETH whitelist.
    // @param _bidder_addresses Array of addresses.  Use double quoted array.
    function removeFromPublicSaleWhitelist(address[] _bidder_addresses) public isWhitelister {
        for (uint32 i = 0; i < _bidder_addresses.length; i++) {
            publicsalewhitelist[_bidder_addresses[i]] = false;
            RemovedFromPublicSaleWhitelist(_bidder_addresses[i]);
        }
    }

    // Private sale contributors whitelist. Only Admin can add or remove

  	// @notice Adds presale account addresses to privatesalewhitelist.
    // @ Admin Adds presale account addresses to privatesalewhitelist.
    // @param _bidder_addresses Array of addresses.
    function addToPrivateSaleWhitelist(address[] _bidder_addresses) public isOwner {
        for (uint32 i = 0; i < _bidder_addresses.length; i++) {
              privatesalewhitelist[_bidder_addresses[i]] = true;
  						PrivateSaleWhitelisted(_bidder_addresses[i]);
          }
      }

      // @notice Removes presale account addresses from privatesalewhitelist.
      // @ Admin Removes presale account addresses from privatesalewhitelist.
      // @param _bidder_addresses Array of addresses.
      function removeFromPrivateSaleWhitelist(address[] _bidder_addresses) public isOwner {
          for (uint32 i = 0; i < _bidder_addresses.length; i++) {
              privatesalewhitelist[_bidder_addresses[i]] = false;
  						RemovedFromPrivateSaleWhitelist(_bidder_addresses[i]);
          }
      }

    // @notice Start the auction.
    // @dev Starts auction and sets auction_start_time.
    function startAuction() public isOwner atStage(Stages.AuctionSetUp) {
        stage = Stages.AuctionStarted;
        auction_start_time = now;
        start_block = block.number;
        AuctionStarted(auction_start_time, start_block);
    }

    /// @notice Send "msg.value" WEI to the auction from the "msg.sender" account.
    /// @dev Allows to send a bid to the auction.
    function bid() public payable
    {
        // Address shouldn't be null and the minimum bid amount of contribution is met.
        // Private sale contributor can submit a bid at AuctionSetUp before AuctionStarted
        // When AuctionStarted only private sale and public sale whitelisted ETH addresses can participate
        require(stage == Stages.AuctionSetUp || stage == Stages.AuctionStarted);
        require(privatesalewhitelist[msg.sender] || publicsalewhitelist[msg.sender]);
        if (stage == Stages.AuctionSetUp){
          require(privatesalewhitelist[msg.sender]);
        }
        require(msg.value > 0);
        require(bids[msg.sender].received + msg.value >= bid_threshold);
        assert(bids[msg.sender].received + msg.value >= msg.value);

        // Maximum public sale contribution per ETH account
        //if (stage == Stages.AuctionStarted && publicsalewhitelist[msg.sender]) {
        //  require (bids[msg.sender].received + msg.value <= MAX_CONTRIBUTION_PUBLICSALE);
        //}

        // Remaining funds without the current bid value to end the auction
        uint256 remaining_funds_to_end_auction = remainingFundsToEndAuction();

        // The bid value must be less than the funds remaining to end the auction
        // at the current price.
        require(msg.value <= remaining_funds_to_end_auction);

/*
---------------------------------------------------------------------------------------------
        Bonus period settings
---------------------------------------------------------------------------------------------
*/
        //Private sale bids before Auction starts
        if (stage == Stages.AuctionSetUp){
          require(privatesalewhitelist[msg.sender]);
          currentBonus = 25; //private sale bonus before AuctionStarted
        }
        else if (stage == Stages.AuctionStarted) {
          // private sale contributors bonus period settings
      		if (privatesalewhitelist[msg.sender] && now >= auction_start_time  && now < auction_start_time + BONUS_DAY1_DURATION) {
      				currentBonus = 25; //private sale contributor Day 1 bonus
      		}
          else if (privatesalewhitelist[msg.sender] && now >= auction_start_time + BONUS_DAY1_DURATION && now < auction_start_time + BONUS_DAY2_DURATION ) {
      				currentBonus = 25; //private sale contributor Day 2 bonus
      		}
      		else if (privatesalewhitelist[msg.sender] && now >= auction_start_time + BONUS_DAY2_DURATION && now < auction_start_time + BONUS_DAY3_DURATION) {
      				currentBonus = 25; //private sale contributor Day 3 bonus
      		}
          else if (privatesalewhitelist[msg.sender] && now >= auction_start_time + BONUS_DAY3_DURATION) {
              currentBonus = 25; //private sale contributor Day 4+ bonus
          }
          else if (publicsalewhitelist[msg.sender] && now >= auction_start_time  && now < auction_start_time + BONUS_DAY1_DURATION) {
      				currentBonus = 15; //private sale contributor Day 1 bonus
      		}
          else if (publicsalewhitelist[msg.sender] && now >= auction_start_time + BONUS_DAY1_DURATION && now < auction_start_time + BONUS_DAY2_DURATION ) {
      				currentBonus = 10; //private sale contributor Day 2 bonus
      		}
      		else if (publicsalewhitelist[msg.sender] && now >= auction_start_time + BONUS_DAY2_DURATION && now < auction_start_time + BONUS_DAY3_DURATION) {
      				currentBonus = 5; //private sale contributor Day 3 bonus
      		}
          else if (publicsalewhitelist[msg.sender] && now >= auction_start_time + BONUS_DAY3_DURATION) {
              currentBonus = 0; //private sale contributor Day 4+ bonus
          }
      		else {
      				currentBonus = 0;
      		}
        }
        else {
          currentBonus = 0;
        }

        // amount raised including bonus
        uint256 accounted = msg.value + msg.value * (currentBonus) / 100;

        // Save the bid amount received with and without bonus.
    		bids[msg.sender].accounted += accounted; //including bonus
    		bids[msg.sender].received += msg.value;

        // keep track of total amount raised and with bonus
        received_wei += msg.value;
        received_wei_with_bonus += accounted;

        // Send bid amount to wallet
        wallet_address.transfer(msg.value);

        //Log the bid
        BidSubmission(msg.sender, msg.value, accounted, remaining_funds_to_end_auction);

        assert(received_wei >= msg.value);
        assert(received_wei_with_bonus >= accounted);
    }

    // @notice Finalize the auction - sets the final GoToken price and
    // changes the auction stage after no bids are allowed. Only owner can finalize the auction.
    // The owner can end the auction anytime after either the auction is deployed or started.
    // @dev Finalize auction and set the final GOT token price.
    function finalizeAuction() public isOwner
    {
        // The owner can end the auction anytime during the stages
        // AuctionSetUp and AuctionStarted
        require(stage == Stages.AuctionSetUp || stage == Stages.AuctionStarted);

        // Calculate the final price = WEI / (GOT / token_multiplier)
        final_price = token_multiplier * received_wei_with_bonus / num_tokens_auctioned;

        // End the auction
        end_time = now;
        stage = Stages.AuctionEnded;
        AuctionEnded(final_price);

        assert(final_price > 0);
    }

    // @notice Distribute GoTokens for "receiver_address" after the auction has ended by the owner.
    // @dev Distribute GoTokens for "receiver_address" after auction has ended by the owner.
    // @param receiver_address GoTokens will be assigned to this address if eligible.
    function distributeGoTokens(address receiver_address)
        public isDistributor atStage(Stages.AuctionEnded) returns (bool)
    {
        // Waiting period in days after the end of the auction, before anyone can claim GoTokens.
        // Ensures enough time to check if auction was finalized correctly
        // before users start transacting tokens
        require(now > end_time + TOKEN_CLAIM_WAIT_PERIOD);
        require(receiver_address != 0x0);
        require(bids[receiver_address].received > 0);

        if (bids[receiver_address].received == 0 || bids[receiver_address].accounted == 0) {
            return false;
        }

        // Number of GOT = bid_wei_with_bonus / (wei_per_GOT * token_multiplier)
        // Includes the bonus
        uint256 num = (token_multiplier * bids[receiver_address].accounted) / final_price;

        // Due to final_price rounding, the number of assigned tokens may be higher
        // than expected. Therefore, the number of remaining unassigned auction tokens
        // may be smaller than the number of tokens needed for the last claimTokens call
        uint256 auction_tokens_balance = token.balanceOf(address(this));
        if (num > auction_tokens_balance) {
            num = auction_tokens_balance;
        }

        // Update the total amount of funds for which tokens have been claimed
        funds_claimed += bids[receiver_address].received;

        // Set receiver bid to 0 before assigning tokens
        bids[receiver_address].accounted = 0;
        bids[receiver_address].received = 0;

        // Send the GoTokens to the receiver address including the qualified bonus
        require(token.transfer(receiver_address, num));

        // Log the event for claimed GoTokens
        ClaimedTokens(receiver_address, num);

        // After the last tokens are claimed, change the auction stage
        // Due to the above logic described, rounding errors will not be an issue here.
        if (funds_claimed == received_wei) {
            stage = Stages.TokensDistributed;
            TokensDistributed();
        }

        assert(token.balanceOf(receiver_address) >= num);
        assert(bids[receiver_address].accounted == 0);
        assert(bids[receiver_address].received == 0);
        return true;
    }

    /// @notice Get the GOT price in WEI during the auction, at the time of
    /// calling this function. Returns 0 if auction has ended.
    /// Returns "price_start" before auction has started.
    /// @dev Calculates the current GOT token price in WEI.
    /// @return Returns WEI per indivisible GOT (token_multiplier * GOT).
    function price() public constant returns (uint256) {
        if (stage == Stages.AuctionEnded ||
            stage == Stages.TokensDistributed) {
            return 0;
        }
        return calcTokenPrice();
    }

    /// @notice Get the remaining funds needed to end the auction, calculated at
    /// the current GOT price in WEI.
    /// @dev The remaining funds necessary to end the auction at the current GOT price in WEI.
    /// @return Returns the remaining funds to end the auction in WEI.
    function remainingFundsToEndAuction() constant public returns (uint256) {

        // num_tokens_auctioned = total number of indivisible GOT (GOT * token_multiplier) that is auctioned
        uint256 required_wei_at_price = num_tokens_auctioned * price() / token_multiplier;
        if (required_wei_at_price <= received_wei) {
            return 0;
        }

        return required_wei_at_price - received_wei;
    }

/*
---------------------------------------------------------------------------------------------
    Private function for calcuclating current token price
---------------------------------------------------------------------------------------------
*/

    // @dev Calculates the token price (WEI / GOT) at the current timestamp
    // during the auction; elapsed time = 0 before auction starts.
    // This is a composite exponentially decaying curve (two curves combined).
    // The curve 1 is for the first 8 days and the curve 2 is for the remaining days.
    // They are of the form:
    //         current_price  = price_start * (1 + elapsed) / (1 + elapsed + decay_rate);
    //          where, decay_rate = elapsed ** price_exponent / price_constant;
    // Based on the provided parameters, the price does not change in the first
    // price_constant^(1/price_exponent) seconds due to rounding.
    // Rounding in `decay_rate` also produces values that increase instead of decrease
    // in the beginning of the auction; these spikes decrease over time and are noticeable
    // only in first hours. This should be calculated before usage.
    // @return Returns the token price - WEI per GOT.

    function calcTokenPrice() constant private returns (uint256) {
        uint256 elapsed;
        uint256 decay_rate1;
        uint256 decay_rate2;
        if (stage == Stages.AuctionDeployed || stage == Stages.AuctionSetUp){
          return price_start;
        }
        if (stage == Stages.AuctionStarted) {
            elapsed = now - auction_start_time;
            // The first eight days auction price curve
            if (now >= auction_start_time && now < auction_start_time + CURVE_CUTOFF_DURATION){
              decay_rate1 = elapsed ** price_exponent1 / price_constant1;
              return price_start * (1 + elapsed) / (1 + elapsed + decay_rate1);
            }
            // The remaining days auction price curve
            else if (now >= auction_start_time && now >= auction_start_time + CURVE_CUTOFF_DURATION){
              decay_rate2 = elapsed ** price_exponent2 / price_constant2;
              return price_start * (1 + elapsed) / (1 + elapsed + decay_rate2);
            }
            else {
              return price_start;
            }

        }
    }

}
/// @title GoToken Uniform Price Dutch auction contract - distribution of a fixed
/// number of tokens using second price auction, where everybody gets the same final
/// price when the auction ends i.e. the ending bid becomes the finalized
/// price per token for all participants.
function addToPrivateSaleWhitelist(address[] _bidder_addresses) public isOwner {
    for (uint32 i = 0; i < _bidder_addresses.length; i++) {
          privatesalewhitelist[_bidder_addresses[i]] = true;
				PrivateSaleWhitelisted(_bidder_addresses[i]);
      }
  }

// Private sale contributors whitelist. Only Admin can add or remove
// @notice Adds presale account addresses to privatesalewhitelist.
// @ Admin Adds presale account addresses to privatesalewhitelist.
// @param _bidder_addresses Array of addresses.