contract Hut34ICO is
    ReentryProtected,
    ERC20Token,
    Hut34ICOAbstract,
    Hut34Config
{
    using SafeMath for uint;

//
// Constants
//

    // Token fixed point for decimal places
    uint constant TOKEN = uint(10)**decimals;

    // Calculate vested tokens
    uint public constant VESTED_TOKENS =
            TOTAL_TOKENS * TOKEN * VESTED_PERCENT / 100;

    // Hut34 retains 50% of tokens (70% - 20% vested tokens)
    uint public constant RETAINED_TOKENS = TOKEN * TOTAL_TOKENS / 2;

    // Calculate end date
    uint public constant END_DATE = START_DATE + 35 days;

    // Divides `etherRaised` to calculate commision
    // etherRaised/6.66... == etherRaised * 1.5% / 100
    uint public constant COMMISSION_DIV = 67;

    // Developer commission wallet
    address public constant COMMISSION_WALLET =
        0x0065D506E475B5DBD76480bAFa57fe7C41c783af;

//
// Functions
//

    function Hut34ICO()
        public
    {
        // Run sanity checks
        require(TOTAL_TOKENS != 0);
        require(OWNER != 0x0);
        require(HUT34_RETAIN != 0x0);
        require(HUT34_WALLET != 0x0);
        require(PRESOLD_TOKENS <= WHOLESALE_TOKENS);
        require(PRESOLD_TOKENS == 0 || PRESOLD_ADDRESS != 0x0);
        require(MIN_CAP != 0);
        require(START_DATE >= now);
        require(bytes(name).length != 0);
        require(bytes(symbol).length != 0);
        require(KYC_THRESHOLD != 0);
        require(RATE_DAY_0 >= RATE_DAY_1);
        require(RATE_DAY_1 >= RATE_DAY_7);
        require(RATE_DAY_7 >= RATE_DAY_14);
        require(RATE_DAY_14 >= RATE_DAY_21);
        require(RATE_DAY_21 >= RATE_DAY_28);

        owner = OWNER;
        totalSupply = TOTAL_TOKENS.mul(TOKEN);
        wholesaleLeft = WHOLESALE_TOKENS.mul(TOKEN);
        uint presold = PRESOLD_TOKENS.mul(TOKEN);
        wholesaleLeft = wholesaleLeft.sub(presold);

        // Presale raise is appoximate given it was conducted in Fiat.
        etherRaised = PRESALE_ETH_RAISE;

        // Mint the total supply into Hut34 token holding address
        balances[HUT34_RETAIN] = totalSupply;
        Transfer(0x0, HUT34_RETAIN, totalSupply);

        // Transfer vested tokens from holding wallet to vesting pseudo-address
        balances[HUT34_RETAIN] = balances[HUT34_RETAIN].sub(VESTED_TOKENS);
        balances[HUT34_VEST_ADDR] = balances[HUT34_VEST_ADDR].add(VESTED_TOKENS);
        Transfer(HUT34_RETAIN, HUT34_VEST_ADDR, VESTED_TOKENS);

        // Transfer presold tokens to holding address;
        balances[HUT34_RETAIN] = balances[HUT34_RETAIN].sub(presold);
        balances[PRESOLD_ADDRESS] = balances[PRESOLD_ADDRESS].add(presold);
        Transfer(HUT34_RETAIN, PRESOLD_ADDRESS, presold);
    }

    // Default function. Accepts payments during funding period
    function ()
        public
        payable
    {
        // Pass through to purchasing function. Will throw on failed or
        // successful ICO
        proxyPurchase(msg.sender);
    }

//
// Getters
//

    // ICO fails if aborted or minimum funds are not raised by the end date
    function fundFailed() public view returns (bool)
    {
        return !__abortFuse
            || (now > END_DATE && etherRaised < MIN_CAP);
    }

    // Funding succeeds if not aborted, minimum funds are raised before end date
    function fundRaised() public view returns (bool)
    {
        return !fundFailed()
            && etherRaised >= MIN_CAP
            && now > START_DATE;
    }

    // Returns wholesale value in wei
    function wholeSaleValueLeft() public view returns (uint)
    {
        return wholesaleLeft / RATE_WHOLESALE;
    }

    function currentRate()
        public
        view
        returns (uint)
    {
        return
            fundFailed() ? 0 :
            icoSucceeded ? 0 :
            now < START_DATE ? 0 :
            now < START_DATE + 1 days ? RATE_DAY_0 :
            now < START_DATE + 7 days ? RATE_DAY_1 :
            now < START_DATE + 14 days ? RATE_DAY_7 :
            now < START_DATE + 21 days ? RATE_DAY_14 :
            now < START_DATE + 28 days ? RATE_DAY_21 :
            now < END_DATE ? RATE_DAY_28 :
            0;
    }

    // Calculates the sale and wholesale portion of tokens for a given value
    // of wei at the time of calling.
    function ethToTokens(uint _wei)
        public
        view
        returns (uint allTokens_, uint wholesaleTokens_)
    {
        // Get wholesale portion of ether and tokens
        uint wsValueLeft = wholeSaleValueLeft();
        uint wholesaleSpend =
                fundFailed() ? 0 :
                icoSucceeded ? 0 :
                now < START_DATE ? 0 :
                now > END_DATE ? 0 :
                // No wholesale purchse
                _wei < WHOLESALE_THRESHOLD ? 0 :
                // Total wholesale purchase
                _wei < wsValueLeft ?  _wei :
                // over funded for remaining wholesale tokens
                wsValueLeft;

        wholesaleTokens_ = wholesaleSpend
                .mul(RATE_WHOLESALE)
                .mul(TOKEN)
                .div(1 ether);

        // Remaining wei used to purchase retail tokens
        _wei = _wei.sub(wholesaleSpend);

        // Get retail rate
        uint saleRate = currentRate();

        allTokens_ = _wei
                .mul(saleRate)
                .mul(TOKEN)
                .div(1 ether)
                .add(wholesaleTokens_);
    }

//
// ICO functions
//

    // The fundraising can be aborted any time before `finaliseICO()` is called.
    // This will force a fail state and allow refunds to be collected.
    // The owner can abort or anyone else if a successful fund has not been
    // finalised before 7 days after the end date.
    function abort()
        public
        noReentry
        returns (bool)
    {
        require(!icoSucceeded);
        require(msg.sender == owner || now > END_DATE  + 14 days);
        delete __abortFuse;
        Aborted();
        return true;
    }

    // General addresses can purchase tokens during funding
    function proxyPurchase(address _addr)
        public
        payable
        noReentry
        returns (bool)
    {
        require(!fundFailed());
        require(!icoSucceeded);
        require(now > START_DATE);
        require(now <= END_DATE);
        require(msg.value > 0);

        // Log ether deposit
        Deposit (_addr, msg.value);

        // Get ether to token conversion
        uint tokens;
        // Portion of tokens sold at wholesale rate
        uint wholesaleTokens;

        (tokens, wholesaleTokens) = ethToTokens(msg.value);

        // Block any failed token creation
        require(tokens > 0);

        // Prevent over subscribing
        require(balances[HUT34_RETAIN] - tokens >= RETAINED_TOKENS);

        // Adjust wholesale tokens left for sale
        if (wholesaleTokens != 0) {
            wholesaleLeft = wholesaleLeft.sub(wholesaleTokens);
        }

        // transfer tokens from fund wallet
        balances[HUT34_RETAIN] = balances[HUT34_RETAIN].sub(tokens);
        balances[_addr] = balances[_addr].add(tokens);
        Transfer(HUT34_RETAIN, _addr, tokens);

        // Update funds raised
        etherRaised = etherRaised.add(msg.value);

        // Update holder payments
        etherContributed[_addr] = etherContributed[_addr].add(msg.value);

        // Check KYC requirement
        if(etherContributed[_addr] >= KYC_THRESHOLD && !mustKyc[_addr]) {
            mustKyc[_addr] = true;
            Kyc(_addr, true);
        }

        return true;
    }

    // Owner can sweep a successful funding to the fundWallet.
    // Can be called repeatedly to recover errant ether which may have been
    // `selfdestructed` to the contract
    // Contract can be aborted up until this returns `true`
    function finalizeICO()
        public
        onlyOwner
        preventReentry()
        returns (bool)
    {
        // Must have reached minimum cap
        require(fundRaised());

        // Set first vesting date (only once as this function can be called again)
        if(!icoSucceeded) {
            nextReleaseDate = now + VESTING_PERIOD;
        }

        // Set success flag;
        icoSucceeded = true;

        // Transfer % Developer commission
        uint devCommission = calcCommission();
        Withdrawal(this, COMMISSION_WALLET, devCommission);
        COMMISSION_WALLET.transfer(devCommission);

        // Remaining % to the fund wallet
        Withdrawal(this, HUT34_WALLET, this.balance);
        HUT34_WALLET.transfer(this.balance);
        return true;
    }

    function clearKyc(address[] _addrs)
        public
        noReentry
        onlyOwner
        returns (bool)
    {
        uint len = _addrs.length;
        for(uint i; i < len; i++) {
            delete mustKyc[_addrs[i]];
            Kyc(_addrs[i], false);
        }
        return true;
    }

    // Releases vested tokens back to Hut34 wallet
    function releaseVested()
        public
        returns (bool)
    {
        require(now > nextReleaseDate);
        VestingReleased(nextReleaseDate);
        nextReleaseDate = nextReleaseDate.add(VESTING_PERIOD);
        return xfer(HUT34_VEST_ADDR, HUT34_RETAIN, VESTED_TOKENS / 4);
    }

    // Direct refund to caller
    function refund()
        public
        returns (bool)
    {
        address[] memory addrs = new address[](1);
        addrs[0] = msg.sender;
        return refundFor(addrs);
    }

    // Bulk refunds can be pushed from a failed ICO
    function refundFor(address[] _addrs)
        public
        preventReentry()
        returns (bool)
    {
        require(fundFailed());
        uint i;
        uint len = _addrs.length;
        uint value;
        uint tokens;
        address addr;

        for (i; i < len; i++) {
            addr = _addrs[i];
            value = etherContributed[addr];
            tokens = balances[addr];
            if (tokens > 0) {
                // Return tokens
                // transfer tokens from fund wallet
                balances[HUT34_RETAIN] = balances[HUT34_RETAIN].add(tokens);
                delete balances[addr];
                Transfer(addr, HUT34_RETAIN, tokens);
            }

            if (value > 0) {
                // Refund ether contribution
                delete etherContributed[addr];
                delete mustKyc[addr];
                refunded = refunded.add(value);
                Withdrawal(this, addr, value);
                addr.transfer(value);
            }
        }
        return true;
    }

//
// ERC20 additional and overloaded functions
//

    // Allows a sender to transfer tokens to an array of recipients
    function transferToMany(address[] _addrs, uint[] _amounts)
        public
        noReentry
        returns (bool)
    {
        require(_addrs.length == _amounts.length);
        uint len = _addrs.length;
        for(uint i = 0; i < len; i++) {
            xfer(msg.sender, _addrs[i], _amounts[i]);
        }
        return true;
    }

    // Overload to check ICO success and KYC flags.
    function xfer(address _from, address _to, uint _amount)
        internal
        noReentry
        returns (bool)
    {
        require(icoSucceeded);
        require(!mustKyc[_from]);
        super.xfer(_from, _to, _amount);
        return true;
    }

    // Overload to require ICO success
    function approve(address _spender, uint _amount)
        public
        noReentry
        returns (bool)
    {
        // ICO must be successful
        require(icoSucceeded);
        super.approve(_spender, _amount);
        return true;
    }

//
// Contract management functions
//

    // Initiate a change of owner to `_owner`
    function changeOwner(address _owner)
        public
        onlyOwner
        returns (bool)
    {
        ChangeOwnerTo(_owner);
        newOwner = _owner;
        return true;
    }

    // Finalise change of ownership to newOwner
    function acceptOwnership()
        public
        returns (bool)
    {
        require(msg.sender == newOwner);
        ChangedOwner(owner, msg.sender);
        owner = newOwner;
        delete newOwner;
        return true;
    }

    // This will selfdestruct the contract on the condittion all have been
    // processed.
    function destroy()
        public
        noReentry
        onlyOwner
    {
        require(!__abortFuse);
        require(refunded == (etherRaised - PRESALE_ETH_RAISE));
        // Log burned tokens for complete ledger accounting on archival nodes
        Transfer(HUT34_RETAIN, 0x0, balances[HUT34_RETAIN]);
        Transfer(HUT34_VEST_ADDR, 0x0, VESTED_TOKENS);
        Transfer(PRESOLD_ADDRESS, 0x0, PRESOLD_TOKENS);
        // Garbage collect mapped state
        delete balances[HUT34_RETAIN];
        delete balances[PRESOLD_ADDRESS];
        selfdestruct(owner);
    }

    // Owner can salvage ERC20 tokens that may have been sent to the account
    function transferExternalToken(address _kAddr, address _to, uint _amount)
        public
        onlyOwner
        preventReentry
        returns (bool)
    {
        require(ERC20Token(_kAddr).transfer(_to, _amount));
        return true;
    }

    // Calculate commission on prefunded and raised ether.
    function calcCommission()
        internal
        view
        returns(uint)
    {
        uint commission = (this.balance + PRESALE_ETH_RAISE) / COMMISSION_DIV;
        // Edge case that prefund causes commission to be greater than balance
        return commission <= this.balance ? commission : this.balance;
    }
}
/*-----------------------------------------------------------------------------\
 
 Hut34ICO implimentation
 
 \*----------------------------------------------------------------------------*/
function releaseVested()
    public
    returns (bool)
{
    require(now > nextReleaseDate);
    VestingReleased(nextReleaseDate);
    nextReleaseDate = nextReleaseDate.add(VESTING_PERIOD);
    return xfer(HUT34_VEST_ADDR, HUT34_RETAIN, VESTED_TOKENS / 4);
}

// Releases vested tokens back to Hut34 wallet