contract DetherSale is Sale, Whitelistable {

    uint256 public constant PRESALE_WEI = 3956 ether * 1.15 + 490 ether; // Amount raised in the presale including bonus

    uint256 public constant DECIMALS_MULTIPLIER = 1000000000000000000;
    uint256 public constant MAX_DTH = 100000000 * DECIMALS_MULTIPLIER;

    // MAX_WEI - PRESALE_WEI
    // TODO: change to actual amount
    uint256 public constant WEI_CAP = 10554 ether;

    // Duration of the whitelisting phase
    uint256 public constant WHITELISTING_DURATION = 2 days;

    // Contribution limit for the whitelisting phase
    uint256 public constant WHITELISTING_MAX_CONTRIBUTION = 5 ether;

    // Contribution limit for the public sale
    uint256 public constant PUBLIC_MAX_CONTRIBUTION = 2**256 - 1;

    // Minimum contribution allowed
    uint256 public constant MIN_CONTRIBUTION = 0.1 ether;

    // wei per DTH
    uint256 public weiPerDTH;
    // true if the locked tokens have been distributed
    bool private lockedTokensDistributed;
    // true if the presale tokens have been allocated
    bool private presaleAllocated;

    // Address for the presale buyers (Dether team will distribute manually)
    address public presaleAddress;

    uint256 private weiAllocated;

    // Contribution limits specified for the presale
    mapping(address => uint256) public presaleMaxContribution;

    function DetherSale(address _wallet, address _presaleAddress) Sale(_wallet, WEI_CAP) public {
      presaleAddress = _presaleAddress;
    }

    /// @dev Distributes timed locked tokens
    function performInitialAllocations() external onlyOwner checkAllowed {
        require(lockedTokensDistributed == false);
        lockedTokensDistributed = true;

        // Advisors
        distributeTimelockedTokens(0x4dc976cEd66d1B87C099B338E1F1388AE657377d, MAX_DTH.mul(3).div(100), now + 6 * 4 weeks);

        // Bounty
        distributeTimelockedTokens(0xfEF675cC3068Ee798f2312e82B12c841157A0A0E, MAX_DTH.mul(3).div(100), now + 1 weeks);

        // Early Contributors
        distributeTimelockedTokens(0x8F38C4ddFE09Bd22545262FE160cf441D43d2489, MAX_DTH.mul(25).div(1000), now + 6 * 4 weeks);

        distributeTimelockedTokens(0x87a4eb1c9fdef835DC9197FAff3E09b8007ADe5b, MAX_DTH.mul(25).div(1000), now + 6 * 4 weeks);

        // Strategic Partnerships
        distributeTimelockedTokens(0x6f63D5DF2D8644851cBb5F8607C845704C008284, MAX_DTH.mul(11).div(100), now + 1 weeks);

        // Team (locked 3 years, 6 months release)
        distributeTimelockedTokens(0x24c14796f401D77fc401F9c2FA1dF42A136EbF83, MAX_DTH.mul(3).div(100), now + 6 * 4 weeks);
        distributeTimelockedTokens(0x24c14796f401D77fc401F9c2FA1dF42A136EbF83, MAX_DTH.mul(3).div(100), now + 2 * 6 * 4 weeks);
        distributeTimelockedTokens(0x24c14796f401D77fc401F9c2FA1dF42A136EbF83, MAX_DTH.mul(3).div(100), now + 3 * 6 * 4 weeks);
        distributeTimelockedTokens(0x24c14796f401D77fc401F9c2FA1dF42A136EbF83, MAX_DTH.mul(3).div(100), now + 4 * 6 * 4 weeks);
        distributeTimelockedTokens(0x24c14796f401D77fc401F9c2FA1dF42A136EbF83, MAX_DTH.mul(3).div(100), now + 5 * 6 * 4 weeks);
        distributeTimelockedTokens(0x24c14796f401D77fc401F9c2FA1dF42A136EbF83, MAX_DTH.mul(3).div(100), now + 6 * 6 * 4 weeks);
    }

    /// @dev Registers a user and sets the maximum contribution amount for the whitelisting period
    function registerPresaleContributor(address userAddress, uint256 maxContribution)
        external
        onlyOwner
    {
        // Specified contribution has to be lower than the max
        require(maxContribution <= WHITELISTING_MAX_CONTRIBUTION);

        // Register user (Whitelistable contract)
        registerUser(userAddress);

        // Set contribution
        presaleMaxContribution[userAddress] = maxContribution;
    }

    /// @dev Called to allocate the tokens depending on eth contributed.
    /// @param contributor The address of the contributor.
    function allocateTokens(address contributor)
        external
        checkAllowed
    {
        require(presaleAllocated);
        require(contributions[contributor] != 0);

        // We keep a record of how much wei contributed has already been used for allocations
        weiAllocated = weiAllocated.add(contributions[contributor]);

        // Mint the respective tokens to the contributor
        token.mint(contributor, contributions[contributor].mul(DECIMALS_MULTIPLIER).div(weiPerDTH));

        // Set contributions to 0
        contributions[contributor] = 0;

        // If all tokens were allocated, stop minting functionality
        // and send the remaining (rounding errors) tokens to the owner
        if (weiAllocated == weiContributed) {
          uint256 remaining = MAX_DTH.sub(token.totalSupply());
          token.mint(owner, remaining);
          token.finishMinting();
        }
    }

    /// @dev Called to allocate the tokens for presale address.
    function presaleAllocateTokens()
        external
        checkAllowed
    {
        require(!presaleAllocated);
        presaleAllocated = true;

        // Mint the respective tokens to the contributor
        token.mint(presaleAddress, PRESALE_WEI.mul(DECIMALS_MULTIPLIER).div(weiPerDTH));
    }

    function contribute()
        public
        payable
        checkAllowed
    {
        require(msg.value >= MIN_CONTRIBUTION);

        super.contribute();
    }

    /// @dev The limit will be different for every address during the whitelist period, and after that phase there is no limit for contributions.
    function getContributionLimit(address userAddress) public view returns (uint256) {
        uint256 saleStartTime = getStageStartTime(SALE_IN_PROGRESS);

        // If not whitelisted or sale has not started, return 0
        if (!whitelisted[userAddress] || block.timestamp < saleStartTime) {
            return 0;
        }

        // Are we in the first two days?
        bool whitelistingPeriod = block.timestamp - saleStartTime <= WHITELISTING_DURATION;

        // If we are in the whitelisting period, return the contribution limit for the user
        // If not, return the public max contribution
        return whitelistingPeriod ? presaleMaxContribution[userAddress] : PUBLIC_MAX_CONTRIBUTION;
    }

    function createTokenContract() internal returns(MintableToken) {
        return new DetherToken();
    }

    function setupStages() internal {
        super.setupStages();
        state.allowFunction(SETUP, this.performInitialAllocations.selector);
        state.allowFunction(SALE_ENDED, this.allocateTokens.selector);
        state.allowFunction(SALE_ENDED, this.presaleAllocateTokens.selector);
    }

    /// @dev The price will be the total wei contributed divided by the amount of tokens to be allocated to contributors.
    function calculatePrice() public view returns(uint256) {
        return weiContributed.add(PRESALE_WEI).div(60000000).add(1);
    }

    function onSaleEnded() internal {
        // Calculate DTH per Wei
        weiPerDTH = calculatePrice();
    }
}
function allocateTokens(address contributor)
    external
    checkAllowed
{
    require(presaleAllocated);
    require(contributions[contributor] != 0);

    // We keep a record of how much wei contributed has already been used for allocations
    weiAllocated = weiAllocated.add(contributions[contributor]);

    // Mint the respective tokens to the contributor
    token.mint(contributor, contributions[contributor].mul(DECIMALS_MULTIPLIER).div(weiPerDTH));

    // Set contributions to 0
    contributions[contributor] = 0;

    // If all tokens were allocated, stop minting functionality
    // and send the remaining (rounding errors) tokens to the owner
    if (weiAllocated == weiContributed) {
      uint256 remaining = MAX_DTH.sub(token.totalSupply());
      token.mint(owner, remaining);
      token.finishMinting();
    }
}

/// @dev Called to allocate the tokens depending on eth contributed.
/// @param contributor The address of the contributor.