contract RepuX is StandardToken, Ownable {
    string public constant name = "RepuX";
    string public constant symbol = "RPX";
    uint256 public constant decimals = 18;
    address public multisig; //multisig wallet, to which all contributions will be sent

    uint256 public phase1StartBlock; //Crowdsale start block
    uint256 public phase1EndBlock; // Day 7 (estimate)
    uint256 public phase2EndBlock; // Day 13 (estimate)
    uint256 public phase3EndBlock; // Day 19 (estimate)
    uint256 public phase4EndBlock; // Day 25 (estimate)
    uint256 public phase5EndBlock; // Day 31 (estimate)
    uint256 public endBlock; //whole crowdsale end block

    uint256 public basePrice = 1226 * (10**12); // ICO token base price: ~$0.35 (estimate assuming $290 per Eth)

    uint256 public totalSupply = 500000000 * (10**decimals); //Token total supply: 500000000 RPX
    uint256 public presaleTokenSupply = totalSupply.mul(10).div(100); //Amount of tokens available during presale (10%)
    uint256 public crowdsaleTokenSupply = totalSupply.mul(50).div(100); //Amount of tokens available during crowdsale (50%)
    uint256 public rewardsTokenSupply = totalSupply.mul(15).div(100); //Rewards pool (VIP etc, 10%), ambassador share(3%) & ICO bounties(2%)
    uint256 public teamTokenSupply = totalSupply.mul(12).div(100); //Tokens distributed to team (12% in total, 4% vested for 12, 24 & 36 months)
    uint256 public ICOReserveSupply = totalSupply.mul(13).div(100); //Token reserve for 2nd ICO (after 2 years min, 13%)
    uint256 public presaleTokenSold = 0; //Records the amount of tokens sold during presale
    uint256 public crowdsaleTokenSold = 0; //Records the amount of tokens sold during the crowdsale

    uint256 public phase1Cap = 125000000 * (10**decimals);
    uint256 public phase2Cap = phase1Cap.add(50000000 * (10**decimals));
    uint256 public phase3Cap = phase2Cap.add(37500000 * (10**decimals));
    uint256 public phase4Cap = phase3Cap.add(25000000 * (10**decimals));

    uint256 public transferLockup = 5760; //Lock up token transfer until ~2 days after crowdsale concludes
    uint256 public teamLockUp; 
    uint256 public ICOReserveLockUp;
    uint256 private teamWithdrawlCount = 0;
    uint256 public averageBlockTime = 30; //Average block time in seconds

    bool public presaleStarted = false;
    bool public presaleConcluded = false;
    bool public crowdsaleStarted = false;
    bool public crowdsaleConcluded = false;
    bool public ICOReserveWithdrawn = false;
    bool public halted = false; //Halt crowdsale in emergency

    uint256 contributionCount = 0;
    bytes32[] public contributionHashes;
    mapping (bytes32 => Contribution) private contributions;

    event Halt(); //Halt event
    event Unhalt(); //Unhalt event
    event Burn(address burner, uint256 amount);

    struct Contribution {
        address contributor;
        address recipient;
        uint256 ethWei;
        uint256 tokens;
        bool resolved;
        bool success;
        uint8 stage;
    }

    event ContributionReceived(bytes32 contributionHash, address contributor, address recipient,
        uint256 ethWei, uint256 pendingTokens);

    event ContributionResolved(bytes32 contributionHash, bool pass, address contributor, 
        address recipient, uint256 ethWei, uint256 tokens);


    // lockup during and after 48h of end of crowdsale
    modifier crowdsaleTransferLock() {
        require(crowdsaleConcluded && block.number >= endBlock.add(transferLockup));
        _;
    }

    modifier whenNotHalted() {
        require(!halted);
        _;
    }

    //Constructor: set owner (team) address & crowdsale recipient multisig wallet address
    //Allocate reward tokens to the team wallet
  	function RepuX(address _multisig) {
        owner = msg.sender;
        multisig = _multisig;
        balances[owner] = rewardsTokenSupply;
  	}

    //Fallback function when receiving Ether. Contributors can directly send Ether to the token address during crowdsale.
    function() payable {
        buy();
    }


    //Halt ICO in case of emergency.
    function halt() public onlyOwner {
        halted = true;
        Halt();
    }

    function unhalt() public onlyOwner {
        halted = false;
        Unhalt();
    }

    function startPresale() public onlyOwner {
        require(!presaleStarted);
        presaleStarted = true;
    }

    function concludePresale() public onlyOwner {
        require(presaleStarted && !presaleConcluded);
        presaleConcluded = true;
        //Unsold tokens in the presale are made available in the crowdsale.
        crowdsaleTokenSupply = crowdsaleTokenSupply.add(presaleTokenSupply.sub(presaleTokenSold)); 
    }

    //Can only be called after presale is concluded.
    function startCrowdsale() public onlyOwner {
        require(presaleConcluded && !crowdsaleStarted);
        crowdsaleStarted = true;
        phase1StartBlock = block.number;
        phase1EndBlock = phase1StartBlock.add(dayToBlockNumber(7));
        phase2EndBlock = phase1EndBlock.add(dayToBlockNumber(6));
        phase3EndBlock = phase2EndBlock.add(dayToBlockNumber(6));
        phase4EndBlock = phase3EndBlock.add(dayToBlockNumber(6));
        phase5EndBlock = phase4EndBlock.add(dayToBlockNumber(6));
        endBlock = phase5EndBlock;
    }

    //Can only be called either after crowdsale time period ends, or after tokens have sold out
    function concludeCrowdsale() public onlyOwner {
        require(crowdsaleStarted && !crowdsaleOn() && !crowdsaleConcluded);
        crowdsaleConcluded = true;
        endBlock = block.number;
        uint256 unsold = crowdsaleTokenSupply.sub(crowdsaleTokenSold);
        if (unsold > 0) {
            //Burn unsold tokens
            totalSupply = totalSupply.sub(unsold);
            Burn(this, unsold);
            Transfer(this, address(0), unsold);
        }
        teamLockUp = dayToBlockNumber(365); //12-month lock-up period
        ICOReserveLockUp = dayToBlockNumber(365 * 2); //2 years lock up period
    }

    function withdrawTeamToken() public onlyOwner {
        require(teamWithdrawlCount < 3);
        require(crowdsaleConcluded);
        if (teamWithdrawlCount == 0) {
            require(block.number >= endBlock.add(teamLockUp)); //12-month lock-up
        } else if (teamWithdrawlCount == 1) {
            require(block.number >= endBlock.add(teamLockUp.mul(2))); //24-month lock-up
        } else {
            require(block.number >= endBlock.add(teamLockUp.mul(3))); //36-month lock-up
        }
        teamWithdrawlCount++;
        uint256 tokens = teamTokenSupply.div(3);
        balances[owner] = balances[owner].add(tokens);
        Transfer(this, owner, tokens);
    }

    function withdrawICOReserve() public onlyOwner {
        require(!ICOReserveWithdrawn);
        require(crowdsaleConcluded);
        require(block.number >= endBlock.add(ICOReserveLockUp));
        ICOReserveWithdrawn = true;
        balances[owner] = balances[owner].add(ICOReserveSupply);
        Transfer(this, owner, ICOReserveSupply);
    }

    function buy() payable {
        buyRecipient(msg.sender);
    }


    //Allow addresses to buy token for another account
    function buyRecipient(address recipient) public payable whenNotHalted {
        require(msg.value > 0);
        require(presaleOn()||crowdsaleOn()); //Contribution only allowed during presale/crowdsale
        uint256 tokens = msg.value.div(tokenPrice()); 
        uint8 stage = 0;

        if(presaleOn()) {
            require(presaleTokenSold.add(tokens) <= presaleTokenSupply);
            presaleTokenSold = presaleTokenSold.add(tokens);
        } else {
            require(crowdsaleTokenSold.add(tokens) <= crowdsaleTokenSupply);
            crowdsaleTokenSold = crowdsaleTokenSold.add(tokens);
            stage = 1;
        }
        contributionCount = contributionCount.add(1);
        bytes32 transactionHash = keccak256(contributionCount, msg.sender, msg.value, msg.data,
            msg.gas, block.number, tx.gasprice);
        contributions[transactionHash] = Contribution(msg.sender, recipient, msg.value, 
            tokens, false, false, stage);
        contributionHashes.push(transactionHash);
        ContributionReceived(transactionHash, msg.sender, recipient, msg.value, tokens);
    }

    //Accept a contribution if KYC passed.
    function acceptContribution(bytes32 transactionHash) public onlyOwner {
        Contribution storage c = contributions[transactionHash];
        require(!c.resolved);
        c.resolved = true;
        c.success = true;
        balances[c.recipient] = balances[c.recipient].add(c.tokens);
        assert(multisig.send(c.ethWei));
        Transfer(this, c.recipient, c.tokens);
        ContributionResolved(transactionHash, true, msg.sender, c.recipient, c.ethWei, 
            c.tokens);
    }

    //Reject a contribution if KYC failed.
    function rejectContribution(bytes32 transactionHash) public onlyOwner {
        Contribution storage c = contributions[transactionHash];
        require(!c.resolved);
        c.resolved = true;
        c.success = false;
        if (c.stage == 0) {
            presaleTokenSold = presaleTokenSold.sub(c.tokens);
        } else {
            crowdsaleTokenSold = crowdsaleTokenSold.sub(c.tokens);
        }
        assert(c.contributor.send(c.ethWei));
        ContributionResolved(transactionHash, false, msg.sender, c.recipient, c.ethWei, 
            c.tokens);
    }


    //Burns the specified amount of tokens from the team wallet address
    function burn(uint256 _value) public onlyOwner returns (bool) {
        balances[msg.sender] = balances[msg.sender].sub(_value);
        totalSupply = totalSupply.sub(_value);
        Transfer(msg.sender, address(0), _value);
        Burn(msg.sender, _value);
        return true;
    }

    //Allow team to change the recipient multisig address
    function setMultisig(address addr) public onlyOwner {
      	require(addr != address(0));
      	multisig = addr;
    }

    //Allows Team to adjust average blocktime according to network status, 
    //in order to provide more precise timing for ICO phases & lock-up periods
    function setAverageBlockTime(uint256 newBlockTime) public onlyOwner {
        require(newBlockTime > 0);
        averageBlockTime = newBlockTime;
    }

    function transfer(address _to, uint256 _value) public crowdsaleTransferLock 
    returns(bool) {
        return super.transfer(_to, _value);
    }

    function transferFrom(address _from, address _to, uint256 _value) public 
    crowdsaleTransferLock returns(bool) {
        return super.transferFrom(_from, _to, _value);
    }

    //Price of token in terms of ether.
    function tokenPrice() public constant returns(uint256) {
        uint8 p = phase();
        if (p == 0) return basePrice.mul(40).div(100); //Presale: 60% discount
        if (p == 1) return basePrice.mul(50).div(100); //ICO phase 1: 50% discount
        if (p == 2) return basePrice.mul(60).div(100); //Phase 2 :40% discount
        if (p == 3) return basePrice.mul(70).div(100); //Phase 3: 30% discount
        if (p == 4) return basePrice.mul(80).div(100); //Phase 4: 20% discount
        if (p == 5) return basePrice.mul(90).div(100); //Phase 5: 10% discount
        return basePrice;
    }

    function phase() public constant returns (uint8) {
        if (presaleOn()) return 0;
        if (crowdsaleTokenSold <= phase1Cap && block.number <= phase1EndBlock) return 1;
        if (crowdsaleTokenSold <= phase2Cap && block.number <= phase2EndBlock) return 2;
        if (crowdsaleTokenSold <= phase3Cap && block.number <= phase3EndBlock) return 3;
        if (crowdsaleTokenSold <= phase4Cap && block.number <= phase4EndBlock) return 4;
        if (crowdsaleTokenSold <= crowdsaleTokenSupply && block.number <= phase5EndBlock) return 5;
        return 6;
    }

    function presaleOn() public constant returns (bool) {
        return (presaleStarted && !presaleConcluded && presaleTokenSold < presaleTokenSupply);
    }

    function crowdsaleOn() public constant returns (bool) {
        return (crowdsaleStarted && block.number <= endBlock && crowdsaleTokenSold < crowdsaleTokenSupply);
    }

    function dayToBlockNumber(uint256 dayNum) public constant returns(uint256) {
        return dayNum.mul(86400).div(averageBlockTime); //86400 = 24*60*60 = number of seconds in a day
    }

    function getContributionFromHash(bytes32 contributionHash) public constant returns (
            address contributor,
            address recipient,
            uint256 ethWei,
            uint256 tokens,
            bool resolved,
            bool success
        ) {
        Contribution c = contributions[contributionHash];
        contributor = c.contributor;
        recipient = c.recipient;
        ethWei = c.ethWei;
        tokens = c.tokens;
        resolved = c.resolved;
        success = c.success;
    }

    function getContributionHashes() public constant returns (bytes32[]) {
        return contributionHashes;
    }

}
// Based in part on code by Open-Zeppelin: https://github.com/OpenZeppelin/zeppelin-solidity.git
// Based in part on code by FirstBlood: https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol
// Smart contract for the RepuX token & the first crowdsale
function setAverageBlockTime(uint256 newBlockTime) public onlyOwner {
    require(newBlockTime > 0);
    averageBlockTime = newBlockTime;
}

//Allows Team to adjust average blocktime according to network status, 
//in order to provide more precise timing for ICO phases & lock-up periods