contract GodPowerCoin is ERC721 {
    
    function GodPowerCoin() public {
        balances[msg.sender] = _totalSupply;
        Transfer(address(0), msg.sender, _totalSupply);
    }
    
    uint public divCutPool = 0;
    uint public divCutMaster = 10; // to master card
    uint public divCutAdmin = 30;
    
    uint public divPoolAmt = 0;
    uint public divMasterAmt = 0;
    
    mapping(address => uint) public dividend;
    
    function withdrawDividend() public {
        require(dividend[msg.sender] > 0);
        msg.sender.transfer(dividend[msg.sender]);
        dividend[msg.sender] = 0;
    }
    
    function setCut(uint admin_, uint pool_, uint master_) public onlyCLevel {
        divCutAdmin = admin_;
        divCutPool = pool_;
        divCutMaster = master_;
    }
    
    function purchase(uint _tokenId, uint _newPrice) public payable {
        address oldOwner = token[_tokenId].owner;
        uint sellingPrice = token[_tokenId].price;
        
        require(oldOwner != msg.sender);
        require(msg.sender != address(0));

        require(sellingPrice > 0); // can't purchase unreleased token

        require(msg.value >= sellingPrice);
        uint purchaseExcess = msg.value.sub(sellingPrice);

        payoutMining(_tokenId); // must happen before owner change!!

        uint payment = sellingPrice.mul(1000 - divCutPool - divCutAdmin - divCutMaster) / 1000;
        if (divCutPool > 0)
            divPoolAmt = divPoolAmt.add(sellingPrice.mul(divCutPool) / 1000);
        
        divMasterAmt = divMasterAmt.add(sellingPrice.mul(divCutMaster) / 1000);
        
        token[_tokenId].lastPrice = sellingPrice;

        uint maxPrice = getMaxPrice(_tokenId);
        if ((_newPrice > maxPrice) || (_newPrice == 0))
            _newPrice = maxPrice;
            
        token[_tokenId].price = _newPrice;

        _transferToken(oldOwner, msg.sender, _tokenId);
        
        if (_tokenId % 10000 > 0) {
            address MASTER = token[(_tokenId / 10000).mul(10000)].owner;
            dividend[MASTER] = dividend[MASTER].add(sellingPrice.mul(divCutMaster) / 1000);
        }
        
        oldOwner.transfer(payment);

        if (purchaseExcess > 0)
            msg.sender.transfer(purchaseExcess);

        TokenSold(_tokenId, sellingPrice, oldOwner, msg.sender);
    }
    
    function _createToken(uint tokenId, uint _price, address _owner, uint _mBaseSpeed) internal {
        
        token[tokenId].owner = _owner;
        token[tokenId].price = _price;
        token[tokenId].lastPrice = _price;
        
        token[tokenId].mSpeed = _mBaseSpeed;

        token[tokenId].mLastPayoutBlock = block.number;
        
        mSumRawSpeed = mSumRawSpeed.add(getMiningRawSpeed(tokenId));
        
        Birth(tokenId, _price);
        tokenList.push(tokenId);
    }
    function createToken(uint tokenId, uint _price, address _owner, uint _mBaseSpeed) public onlyCLevel {
        require(_price != 0);
        if (_owner == address(0))
            _owner = ceoAddress;

        require(token[tokenId].price == 0);
        _createToken(tokenId, _price, _owner, _mBaseSpeed);
        TransferToken(0, _owner, tokenId);
    }
    function createSimilarTokens(uint[] tokenId, uint _price, address _owner, uint _mBaseSpeed) public onlyCLevel {
        require(_price != 0);
        if (_owner == address(0))
            _owner = ceoAddress;

        for (uint i = 0; i < tokenId.length; i++) {
            require(token[tokenId[i]].price == 0);
            _createToken(tokenId[i], _price, _owner, _mBaseSpeed);
            TransferToken(0, _owner, tokenId[i]);
        }
    }
    function createMultipleTokens(uint[] tokenId, uint[] _price, address _owner, uint[] _mBaseSpeed) public onlyCLevel {
        if (_owner == address(0))
            _owner = ceoAddress;

        for (uint i = 0; i < tokenId.length; i++) {
            require(_price[i] != 0);
            require(token[tokenId[i]].price == 0);
            _createToken(tokenId[i], _price[i], _owner, _mBaseSpeed[i]);
            TransferToken(0, _owner, tokenId[i]);
        }
    }
    
    event MiningUpgrade(address indexed sender, uint indexed token, uint newLevelSpeed);

    // ETH: 6000 blocks per day, 5 ETH per block
    
    uint public mSumRawSpeed = 0;

    uint public mCoinPerBlock = 50;
    
    uint public mUpgradeCostFactor = mCoinPerBlock * 6000 * WAD;
    uint public mUpgradeSpeedup = 1040; // = * 1.04
    
    function adminSetMining(uint mCoinPerBlock_, uint mUpgradeCostFactor_, uint mUpgradeSpeedup_) public onlyCLevel {
        mCoinPerBlock = mCoinPerBlock_;
        mUpgradeCostFactor = mUpgradeCostFactor_;
        mUpgradeSpeedup = mUpgradeSpeedup_;
    }
    
    function getMiningRawSpeed(uint id) public view returns (uint) {
        return token[id].mSpeed;
    }
    function getMiningRealSpeed(uint id) public view returns (uint) {
        return getMiningRawSpeed(id).mul(mCoinPerBlock) / mSumRawSpeed;
    }
    function getMiningUpgradeCost(uint id) public view returns (uint) {
        return getMiningRawSpeed(id).mul(mUpgradeCostFactor) / mSumRawSpeed;
    }
    function upgradeMining(uint id) public {
        uint cost = getMiningUpgradeCost(id);
        balances[msg.sender] = balances[msg.sender].sub(cost);
        _totalSupply = _totalSupply.sub(cost);
        
        mSumRawSpeed = mSumRawSpeed.sub(getMiningRawSpeed(id));
        token[id].mSpeed = token[id].mSpeed.mul(mUpgradeSpeedup) / 1000;
        mSumRawSpeed = mSumRawSpeed.add(getMiningRawSpeed(id));
        
        MiningUpgrade(msg.sender, id, token[id].mSpeed);
    }
    function upgradeMiningMultipleTimes(uint id, uint n) public {
        for (uint i = 0; i < n; i++) {
            uint cost = getMiningUpgradeCost(id);
            balances[msg.sender] = balances[msg.sender].sub(cost);
            _totalSupply = _totalSupply.sub(cost);
        
            mSumRawSpeed = mSumRawSpeed.sub(getMiningRawSpeed(id));
            token[id].mSpeed = token[id].mSpeed.mul(mUpgradeSpeedup) / 1000;
            mSumRawSpeed = mSumRawSpeed.add(getMiningRawSpeed(id));
        }
        MiningUpgrade(msg.sender, id, token[id].mSpeed);
    }
    function payoutMiningAll(address owner, uint[] list) public {
        uint sum = 0;
        for (uint i = 0; i < list.length; i++) {
            uint id = list[i];
            require(token[id].owner == owner);
            uint blocks = block.number.sub(token[id].mLastPayoutBlock);
            token[id].mLastPayoutBlock = block.number;
            sum = sum.add(getMiningRawSpeed(id).mul(mCoinPerBlock).mul(blocks).mul(WAD) / mSumRawSpeed); // mul WAD !
        }
        balances[owner] = balances[owner].add(sum);
        _totalSupply = _totalSupply.add(sum);
    }
    function payoutMining(uint id) public {
        require(token[id].mLastPayoutBlock > 0);
        uint blocks = block.number.sub(token[id].mLastPayoutBlock);
        token[id].mLastPayoutBlock = block.number;
        address owner = token[id].owner;
        uint coinsMined = getMiningRawSpeed(id).mul(mCoinPerBlock).mul(blocks).mul(WAD) / mSumRawSpeed; // mul WAD !
        
        balances[owner] = balances[owner].add(coinsMined);
        _totalSupply = _totalSupply.add(coinsMined);
    }
}
function adminSetMining(uint mCoinPerBlock_, uint mUpgradeCostFactor_, uint mUpgradeSpeedup_) public onlyCLevel {
    mCoinPerBlock = mCoinPerBlock_;
    mUpgradeCostFactor = mUpgradeCostFactor_;
    mUpgradeSpeedup = mUpgradeSpeedup_;
}

// = * 1.04