contract BoxData is AccessControl, BoxConstants {
    using SafeMath for uint256;

    address public addrGame;
    address public addrImpl;

    //TODO 金钱统计

    //game
    bool public activated = false;

    constructor(address addr) public {
        roleAdd(RoleAdmin, msg.sender);
        _setGame(addr);
    }

    //game

    /** @dev set activated
    * @param status activated status
    */
    function setActivated(bool status) public onlyAdmin {
        activated = status;
    }
 
    function setImpl(address addr) public onlyAdmin {
        addrImpl = addr;
    }

    function _setGame(address addr) private {
        addrGame = addr;
        addrImpl = BoxGame(addrGame).addrImpl();
    }

    function setGame(address addr) public onlyAdmin {
        _setGame(addr);
    }

    //round
    uint256 public nRound = 0;
    mapping (uint256 => BoxTypes.Round) public rounds;          //(rID => Round)

    /** @dev new round
    * @return rId Id of round
    */
    function newRound(uint256 end, uint256 potTeam) public onlyAdmin returns (uint256 rId) {
        nRound++;
        rId = nRound;
        BoxTypes.Round storage round = rounds[rId];
        round.end = end;
        round.potTeam = potTeam;
    }

    /** @dev get round base info
    * @param rId activated status
    * @return end end of round
    */
    function getRoundBase(uint256 rId) public view returns (
        uint256 end, uint256 boxTotal, uint256 potCaptain, uint256 potTeam, uint256 potTeamReserved, int8 luckyTeam) {
        BoxTypes.Round storage round = rounds[rId];

        end = round.end;
        boxTotal = round.boxTotal;
        potCaptain = round.potCaptain;
        potTeam = round.potTeam;
        potTeamReserved = round.potTeamReserved;
        luckyTeam = round.luckyTeam;
    }

    function setRoundTime(uint256 time) public onlyAdmin {
        rounds[nRound].end = time;
    }

    function setRoundPotInfo(uint256 rId, uint256 potCaptain, int8 luckyTeam) public onlyAdmin {
        BoxTypes.Round storage round = rounds[rId];

        round.potCaptain = potCaptain;
        round.luckyTeam = luckyTeam;
    }

    function getRoundTeam(uint256 rId, int8 tId) public view returns (uint256 captain, uint256[] players, uint256[] boxCounts, uint256 boxTotal) {
        BoxTypes.Round storage round = rounds[rId];

        if (0 == round.end) {
            return;
        }

        captain = round.teamCaptain[tId];
        uint256[] storage teamPlayers = round.teamPlayers[tId];
        uint256 count = teamPlayers.length;
        if (0 == count) {
            return;
        }

        players = new uint256[](count);
        boxCounts = new uint256[](count);
        for (uint256 index = 0; index < count; index++) {
            uint256 pId = teamPlayers[index];
            uint256 boxCount = round.teamPlayerBoxCount[tId][pId];
            players[index] = pId;
            boxCounts[index] = boxCount;
            boxTotal += boxCount;
        }
    }

    function getRoundPlayerBoxCount(uint256 rId, uint256 pId, int8 tId) public view returns (uint256) {
        return rounds[rId].teamPlayerBoxCount[tId][pId];
    }

    function getRoundPotPlayer(uint256 rId, uint256 pId) public view returns (uint256) {
        return rounds[rId].potPlayer[pId];
    }
    
    //pot
    uint256 public nPot = 0;
    mapping (uint256 => BoxTypes.Pot) public pots;              //(potID => Pot)

    function newPot(uint256 end) public onlyAdmin returns (uint256 potId) {
        nPot++;
        potId = nPot;
        BoxTypes.Pot storage pot = pots[potId];
        pot.end = end;
    }

    function getPotBase(uint256 potId) public view returns (
        uint256 end, uint256 potUserCommon, uint256 potUserSenior) {
        BoxTypes.Pot storage pot = pots[potId];

        end = pot.end;
        potUserCommon = pot.potUserCommon;
        potUserSenior = pot.potUserSenior;
    }

    function incPot(uint256 potUserCommon, uint256 potUserSenior, uint256 potTeam, uint256 potTeamReserved) public onlyAdmin {
        BoxTypes.Pot storage pot = pots[nPot];
        pot.potUserCommon = pot.potUserCommon.add(potUserCommon);
        pot.potUserSenior = pot.potUserSenior.add(potUserSenior);

        BoxTypes.Round storage round = rounds[nRound];
        round.potTeam = round.potTeam.add(potTeam);
        round.potTeamReserved = round.potTeamReserved.add(potTeamReserved);
    }

    function getPotSenior(uint256 potId, uint256 pId) public view returns (uint256) {
        return pots[potId].potSenior[pId];
    }

    //player
    uint256 public nPlayer = 0;
    mapping (address => uint256) public pAddrTopId;             //(address => pId)
    mapping (uint256 => BoxTypes.Player) public players;        //(pId => Player)

    /** @dev new player
    * @param addr Address of player
    * @param invitor invitor of player
    * @return pId Id of player
    */
    function newPlayer(address addr, uint256 invitor) public onlyAdmin returns (uint256 pId) {
        nPlayer++;
        pId = nPlayer;

        BoxTypes.Player storage player = players[pId];
        pAddrTopId[addr] = pId;
        player.invitor = invitor;
        player.addr = addr;
    }

    function getPlayer(uint256 pId) public view returns (
        address addr, uint256 invitor, int16 invited, uint256 invitorBalance, uint256 boxCount, uint256 boxTotal, uint256 boxEthCur, uint256 boxEthCum) {
        BoxTypes.Player storage player = players[pId];
        if (address(0) == player.addr) {
            return;
        }

        addr = player.addr;
        invitor = player.invitor;
        invited = player.invited;
        invitorBalance = player.invitorBalance;
        boxCount = player.boxCount;
        boxTotal = player.boxTotal;
        boxEthCur = player.boxEthCur;
        boxEthCum = player.boxEthCum;
    }

    function getPlayerAddress(uint256 pId) public view returns (address addr) {
        return players[pId].addr;
    }

    function getProfitablePlayers(uint256 ethMaxPerBox) public view returns (uint256[] profitablePlayers, uint256[] boxCounts, uint256 boxTotal, int16[] inviteds) {
        uint256 count;
        for (uint256 pId = 1; pId <= nPlayer; pId++) {
            BoxTypes.Player storage player = players[pId];
            uint256 ethMax = ethMaxPerBox.mul(player.boxCount);
            if (player.boxEthCum < ethMax) {
                count++;
            }
        }
        profitablePlayers = new uint256[](count); 
        boxCounts = new uint256[](count);
        inviteds = new int16[](count);
        uint256 pos;
        for (pId = 1; pId <= nPlayer; pId++) {
            player = players[pId];
            ethMax = ethMaxPerBox.mul(player.boxCount);
            if (player.boxEthCum < ethMax) {
                profitablePlayers[pos] = pId;
                boxCounts[pos] = player.boxCount;
                inviteds[pos] = player.invited;
                boxTotal += player.boxCount;
                pos++;
            }
        }
    }

    function setPlayerInvitor(uint256 pId, uint256 invitorId) public onlyAdmin {
        players[pId].invitor = invitorId;
    }

    function incPlayerInvited(uint256 pId) public onlyAdmin returns (int16) {
        players[pId].invited++;
        return players[pId].invited;
    }

    function newBox(uint256 pId, uint256 count, int8 tId) public onlyAdmin {
        BoxTypes.Player storage player = players[pId];
        player.boxCount += count;
        player.boxTotal += count;

        BoxTypes.Round storage round = rounds[nRound];
        round.boxTotal += count;

        uint256 boxCountOld = round.teamPlayerBoxCount[tId][pId];
        if (0 == boxCountOld) {
            round.teamPlayers[tId].push(pId);
        }
        uint256 boxCountNew = boxCountOld + count;
        round.teamPlayerBoxCount[tId][pId] = boxCountNew;
        uint256 captainId = round.teamCaptain[tId];
        uint256 captainBoxCount = round.teamPlayerBoxCount[tId][captainId];
        if (boxCountNew > captainBoxCount) {
            round.teamCaptain[tId] = pId;
        }
    }

    function incPlayerInvitorBalance(uint256 pId, uint256 val) public onlyAdmin {
        players[pId].invitorBalance = players[pId].invitorBalance.add(val);
    }

    function _addPlayerBoxEth(uint256 pId, uint256 val, uint256 ethMaxPerBox) private returns (uint256 change) {
        BoxTypes.Player storage player = players[pId];

        uint256 ethMax = ethMaxPerBox.mul(player.boxCount);
        if (ethMax <= player.boxEthCum) {
            return 0;
        }

        uint256 changeMax = ethMax - player.boxEthCum;
        if (val >= changeMax) {
            change = changeMax;
            player.boxEthCur += change;
            player.boxEthCum = 0;
            player.boxCount = 0;
        } else {
            change = val;
            player.boxEthCur += change;
            player.boxEthCum += change;
        }
    }

    function addPlayerPotTeam(uint256 rId, uint256[] pIds, uint256[] vals, uint256 ethMaxPerBox) public onlyAdmin returns (uint256 distributed) {
        uint256 count = pIds.length;
        if(count <= 0) {
            return;
        }
        for (uint256 index = 0; index < count; index++) {
            uint256 pId = pIds[index];
            uint256 val = vals[index];
            distributed += _addPlayerBoxEth(pId, val, ethMaxPerBox);
            rounds[rId].potPlayer[pId] += val;
        }
    }

    function addPlayerPotUser(uint256 potId, uint256[] pIds, uint256[] valCommons, uint256[] valSeniors, uint256 ethMaxPerBox) public onlyAdmin returns (uint256 distributed) {
        uint256 count = pIds.length;
        if(count <= 0) {
            return;
        }
        for (uint256 index = 0; index < count; index++) {
            uint256 pId = pIds[index];
            uint256 valCommon = valCommons[index];
            uint256 valSenior = valSeniors[index];
            uint256 val = valCommon.add(valSenior);
            distributed += _addPlayerBoxEth(pId, val, ethMaxPerBox);
            pots[potId].potSenior[pId] += valSenior;
        }
    }

    function resetPlayerBoxEth(uint256 pId,  uint256 ethMaxPerBox) public onlyAdmin returns (uint256 eth) {
        BoxTypes.Player storage player = players[pId];

        eth = player.boxEthCur;
        player.boxEthCur = 0;
        uint256 ethMax = ethMaxPerBox.mul(player.boxCount);
        if (ethMax <= player.boxEthCum) {
            player.boxCount = 0;
            player.boxEthCum = 0;
        }
    }

    function resetPlayerInvitorBalance(uint256 pId) public onlyAdmin returns (uint256 eth) {
        BoxTypes.Player storage player = players[pId];
        eth = player.invitorBalance;
        player.invitorBalance = 0;
    }

    //stats
    BoxTypes.Statistics public stats;

    function addStatsInvest(uint256 val) public onlyAdmin {
        stats.totalInvest = stats.totalInvest.add(val);
    }

    function addStatsWithdraw(uint256 val) public onlyAdmin {
        stats.totalWithdraw = stats.totalWithdraw.add(val);
    }

    function addStatsPotCaptain(uint256 val) public onlyAdmin {
        stats.totalPotCaptain = stats.totalPotCaptain.add(val);
    }

    function addStatsDevGain(uint256 val) public onlyAdmin {
        stats.devGain = stats.devGain.add(val);
    }

    /**
     * @dev call only by impl or admin
     */
    modifier onlyAdmin() {
        require((msg.sender == addrImpl) || (msg.sender == addrGame) || roleHas(RoleAdmin, msg.sender), "can only call by admin");
        _;
    }
}
function newPot(uint256 end) public onlyAdmin returns (uint256 potId) {
    nPot++;
    potId = nPot;
    BoxTypes.Pot storage pot = pots[potId];
    pot.end = end;
}

//(potID => Pot)