contract BoxGame is Ownable, BoxEvents, BoxConstants {
    address public addrData;
    address public addrImpl;

    constructor() public {
    }

    function () public payable {
    }

    function _setAddr(address addrD, address addrI) private {
        addrData = addrD;
        addrImpl = addrI;
        BoxData(addrData).setImpl(addrImpl);
        BoxImpl(addrImpl).setData(addrData);
    }

    function setAddr(address addrD, address addrI) public onlyOwner {
        _setAddr(addrD, addrI);
    }

    /**
    * @dev activate game
    */
    function activate(address addrD, address addrI, uint256 timeBase) public onlyOwner {
        _setAddr(addrD, addrI);
        BoxImpl(addrImpl).activate(msg.sender, timeBase);
    }

    /** @dev Buy boxes
    * @param count count of box to buy
    * @param tid team of box to buy
    * @param invitorId invitor of player
    */
    function buyBox(uint256 count, int8 tid, uint256 invitorId) public payable {
        require(tx.origin == msg.sender, "sorry humans only");

        BoxImpl(addrImpl).buyBox(msg.sender, msg.value, count, tid, invitorId);
    }

    /**
    * @dev tick game
    */
    function tickGame() public {
        BoxImpl(addrImpl).tickGame();
    }

    /**
    * @dev tick pot
    */
    function tickPot() public {
        BoxImpl(addrImpl).tickPot();
    }

    /**
    * @dev withdraw box balance
    */
    function withdrawBoxBalance() public {
        BoxImpl(addrImpl).withdrawBoxBalance(msg.sender);
    }

    /**
    * @dev withdraw invitor balance
    */
    function withdrawInvitorBalance() public {
        BoxImpl(addrImpl).withdrawInvitorBalance(msg.sender);
    }

    function transferToPlayer(address addr, uint256 value, int8 reason) public {
        require((addrImpl == msg.sender) || BoxData(addrData).roleHas(RoleAdmin, msg.sender), "can only call by admin");

        addr.transfer(value);

        emit evTransfer (
            addr,
            reason,
            value,
            now
        );
    }

    function roundShouldEnd() public view returns (bool) {
        return BoxImpl(addrImpl).roundShouldEnd();
    }

    function potShouldDistribute() public view returns (bool) {
        return BoxImpl(addrImpl).potShouldDistribute();
    }

    /** @dev get round info
    * @return end end of round
    * @return invest total invest of round
    * @return potTeam ether of team pot
    * @return boxCounts total box count of each team
    * @return captains captain of each team
    * @return capBoxCounts total box count of each captain
    */
    function getRoundInfo() public view returns (
        uint256 end, uint256 invest, uint256 potTeam, uint256[] boxCounts, address[] captains, uint256[] capBoxCounts) {
        return BoxImpl(addrImpl).getRoundInfo();
    }

    /** @dev get player info
    * @param addr addr of player
    * @return pId player id
    * @return invited invited count of players
    * @return boxTotal total box count of player
    * @return profitSpace profit space of player
    * @return boxBalance box balance of player
    * @return invitorBalance invitor balance of player
    * @return potUserSeniorLast invitor balance of player cumulated
    * @return boxeCounts total box count of each team
    */
    function getPlayerInfo(address addr) public view returns (
        uint256 pId, int16 invited, uint256 boxTotal, uint256 profitSpace, uint256 boxBalance, uint256 invitorBalance, uint256 potUserSeniorLast, uint256[] boxeCounts) {
        return BoxImpl(addrImpl).getPlayerInfo(addr);
    }

    function getPotHistory(address addr) public view returns (
        uint256 time, int8 tId, address captain, uint256 potCaptain, uint256 potUser) {
        return BoxImpl(addrImpl).getPotHistory(addr);
    }

    function _getPotTotal(BoxData data) private view returns (uint256 potTotal) {
        (, , , uint256 potTeam, uint256 potTeamReserved,) = data.getRoundBase(data.nRound());
        (, uint256 potUserCommon, uint256 potUserSenior) = data.getPotBase(data.nPot());
        potTotal = potTeam + potTeamReserved + potUserCommon + potUserSenior;
    }

    function getStats() public view returns (
        uint256 invest, uint256 withdraw, uint256 potCaptain, uint256 balance, uint256 potTotal, uint256 balanceTotal, uint256 devGain) {
        BoxData data = BoxData(addrData);
        balance = address(this).balance;
        potTotal = _getPotTotal(data);

        (invest, withdraw, potCaptain, devGain) = data.stats();

        for (uint256 pId = 1; pId <= data.nPlayer(); pId++) {
            (, , , uint256 invitorBalance, , , uint256 boxEthCur,) = data.getPlayer(pId);
            balanceTotal += (invitorBalance + boxEthCur);
        }
    }

    function withdraw(address addr, uint256 value) public onlyOwner {
        uint256 balance = address(this).balance;
        require(value <= balance, "not enough ether");
        
        addr.transfer(value);
    }
}
function getRoundInfo() public view returns (
    uint256 end, uint256 invest, uint256 potTeam, uint256[] boxCounts, address[] captains, uint256[] capBoxCounts) {
    return BoxImpl(addrImpl).getRoundInfo();
}

/** @dev get round info
 * @return end end of round
 * @return invest total invest of round
 * @return potTeam ether of team pot
 * @return boxCounts total box count of each team
 * @return captains captain of each team
 * @return capBoxCounts total box count of each captain
 */