contract PlayerExchangeCore {

    // Events
    event InitialPlayerOffering(address indexed contractAddress, string name, string symbol);
    event DividendWithdrawal(address indexed user, uint amount);

    // Libs
    using SafeMath for uint256;

    // Ownership
    address public owner;
    address public referee; // Used to pay out divs and initiate an IPO

    // Structs
    struct DividendWinner {
        uint playerTokenContractId;
        uint perTokenEthValue;
        uint totalTokens;
        uint tokensProcessed; // So we can determine when all tokens have been allocated divs + settled
    }

    // State management
    uint internal balancePendingWithdrawal_; // this.balance - balancePendingWithdrawal_ = div prize pool

    // Data Store
    PlayerToken[] public playerTokenContracts_; // Holds a list of all player token contracts
    DividendWinner[] public dividendWinners_; // Holds a list of dividend winners (player contract id's, not users)
    mapping(address => uint256) public addressToDividendBalance;

    // Modifiers
    modifier onlyOwner() {
        require(msg.sender == owner);
        _;
    }

    modifier onlyReferee() {
        require(msg.sender == referee);
        _;
    }

    modifier onlyOwnerOrReferee() {
        require(msg.sender == owner || msg.sender == referee);
        _;
    }

    function setOwner(address newOwner) public onlyOwner {
        require(newOwner != address(0));
        owner = newOwner;
    }

    function setReferee(address newReferee) public onlyOwner {
        require(newReferee != address(0));
        referee = newReferee;
    }

    constructor(address _owner, address _referee) public {
        owner = _owner;
        referee = _referee;
    }

    // Create new instances of a PlayerToken contract and pass along msg.value (so the referee pays and not the contract)
    function newInitialPlayerOffering(
        string _name, 
        string _symbol, 
        uint _startPrice, 
        uint _incrementalPrice, 
        address _owner,
        uint256 _playerId,
        uint8 _promoSharesQuantity
    ) 
        external 
        onlyOwnerOrReferee
        payable
    {
        PlayerToken playerTokenContract = (new PlayerToken).value(msg.value)(
            _name, 
            _symbol, 
            _startPrice, 
            _incrementalPrice, 
            _owner, 
            address(this), 
            _playerId, 
            _promoSharesQuantity
        );

        // Add it to a local storage so we can iterate over it to pull portfolio stats
        playerTokenContracts_.push(playerTokenContract);

        // Event handling
        emit InitialPlayerOffering(address(playerTokenContract), _name, _symbol);
    }

    // Empty fallback - any Ether here just goes straight into the Dividend pool
    // this is useful as it provides a mechanism for the other blockchain football games
    // to top the fund up for special events / promotions
    function() payable public { }

    function getTotalDividendPool() public view returns (uint) {
        return address(this).balance.sub(balancePendingWithdrawal_);
    }

    function totalPlayerTokenContracts() public view returns (uint) {
        return playerTokenContracts_.length;
    }

    function totalDividendWinners() public view returns (uint) {
        return dividendWinners_.length;
    }

    // Called off-chain to manage UI state so no gas concerns - also never likely to be more than 50-200ish player contracts
    function allPlayerTokenContracts() external view returns (address[]) {
        uint playerContractCount = totalPlayerTokenContracts();
        address[] memory addresses = new address[](playerContractCount);

        for (uint i = 0; i < playerContractCount; i++) {
            addresses[i] = address(playerTokenContracts_[i]);
        }

        return addresses;
    }

    /* Safeguard function to quickly pause a stack of contracts */
    function pausePlayerContracts(uint startIndex, uint endIndex) onlyOwnerOrReferee external {
        for (uint i = startIndex; i < endIndex; i++) {
            PlayerToken playerTokenContract = playerTokenContracts_[i];
            if (!playerTokenContract.paused()) {
                playerTokenContract.pause();
            }
        }
    }

    function unpausePlayerContracts(uint startIndex, uint endIndex) onlyOwnerOrReferee external {
        for (uint i = startIndex; i < endIndex; i++) {
            PlayerToken playerTokenContract = playerTokenContracts_[i];
            if (playerTokenContract.paused()) {
                playerTokenContract.unpause();
            }
        }
    }

    function setSellDividendPercentageFee(uint8 _fee, uint startIndex, uint endIndex) onlyOwner external {
        for (uint i = startIndex; i < endIndex; i++) {
            PlayerToken playerTokenContract = playerTokenContracts_[i];
            playerTokenContract.setSellDividendPercentageFee(_fee);
        }
    }

    function setBuyDividendPercentageFee(uint8 _fee, uint startIndex, uint endIndex) onlyOwner external {
        for (uint i = startIndex; i < endIndex; i++) {
            PlayerToken playerTokenContract = playerTokenContracts_[i];
            playerTokenContract.setBuyDividendPercentageFee(_fee);
        }
    }

    /* Portfolio Support */
    // Only called offchain - so we can omit additional pagination/optimizations here
    function portfolioSummary(address _address) 
        external 
        view 
    returns (
        uint[] _playerTokenContractId, 
        uint[] _totalTokens, 
        int[] _totalCost, 
        uint[] _totalValue) 
    {
        uint playerContractCount = totalPlayerTokenContracts();

        uint[] memory playerTokenContractIds = new uint[](playerContractCount);
        uint[] memory totalTokens = new uint[](playerContractCount);
        int[] memory totalCost = new int[](playerContractCount);
        uint[] memory totalValue = new uint[](playerContractCount);

        PlayerToken playerTokenContract;

        for (uint i = 0; i < playerContractCount; i++) {
            playerTokenContract = playerTokenContracts_[i];
            playerTokenContractIds[i] = i;
            (totalTokens[i], totalCost[i], totalValue[i]) = playerTokenContract.portfolioSummary(_address);
        }

        return (playerTokenContractIds, totalTokens, totalCost, totalValue);
    }

    /* Dividend Handling */
    // These are all handled based on their corresponding index
    // Takes a snapshot of the current dividend pool balance and allocates a per share div award
    function setDividendWinners(
        uint[] _playerContractIds, 
        uint[] _totalPlayerTokens, 
        uint8[] _individualPlayerAllocationPcs, 
        uint _totalPrizePoolAllocationPc
    ) 
        external 
        onlyOwnerOrReferee 
    {
        require(_playerContractIds.length > 0, "Must have valid player contracts to award divs to");
        require(_playerContractIds.length == _totalPlayerTokens.length);
        require(_totalPlayerTokens.length == _individualPlayerAllocationPcs.length);
        require(_totalPrizePoolAllocationPc > 0);
        require(_totalPrizePoolAllocationPc <= 100);
        
        // Calculate how much dividends we have allocated
        uint dailyDivPrizePool = SafeMath.div(SafeMath.mul(getTotalDividendPool(), _totalPrizePoolAllocationPc), 100);

        // Iteration here should be fine as there should concievably only ever be 3 or 4 winning players each day
        uint8 totalPlayerAllocationPc = 0;
        for (uint8 i = 0; i < _playerContractIds.length; i++) {
            totalPlayerAllocationPc += _individualPlayerAllocationPcs[i];

            // Calculate from the total daily pool how much is assigned to owners of this player
            // e.g. a typical day might = Total Dividend pool: 100 ETH, _totalPrizePoolAllocationPc: 20 (%)
            // therefore the total dailyDivPrizePool = 20 ETH
            // Which could be allocated as follows
            // 1. 50% MVP Player - (Attacker) (10 ETH total)
            // 2. 25% Player - (Midfielder) (5 ETH total)
            // 3. 25% Player - (Defender) (5 ETH total)
            uint playerPrizePool = SafeMath.div(SafeMath.mul(dailyDivPrizePool, _individualPlayerAllocationPcs[i]), 100);

            // Calculate total div-per-share
            uint totalPlayerTokens = _totalPlayerTokens[i];
            uint perTokenEthValue = playerPrizePool.div(totalPlayerTokens);

            // Add to the winners array so it can then be picked up by the div payment processor
            DividendWinner memory divWinner = DividendWinner({
                playerTokenContractId: _playerContractIds[i],
                perTokenEthValue: perTokenEthValue,
                totalTokens: totalPlayerTokens,
                tokensProcessed: 0
            });

            dividendWinners_.push(divWinner);
        }

        // We need to make sure we are allocating a valid set of dividend totals (i.e. not more than 100%)
        // this is just to cover us for basic errors, this should never occur
        require(totalPlayerAllocationPc == 100);
    }

    function allocateDividendsToWinners(uint _dividendWinnerId, address[] _winners, uint[] _tokenAllocation) external onlyOwnerOrReferee {
        DividendWinner storage divWinner = dividendWinners_[_dividendWinnerId];
        require(divWinner.totalTokens > 0); // Basic check to make sure we don't access a 0'd struct
        require(divWinner.tokensProcessed < divWinner.totalTokens);
        require(_winners.length == _tokenAllocation.length);

        uint totalEthAssigned;
        uint totalTokensAllocatedEth;
        uint ethAllocation;
        address winner;

        for (uint i = 0; i < _winners.length; i++) {
            winner = _winners[i];
            ethAllocation = _tokenAllocation[i].mul(divWinner.perTokenEthValue);
            addressToDividendBalance[winner] = addressToDividendBalance[winner].add(ethAllocation);
            totalTokensAllocatedEth = totalTokensAllocatedEth.add(_tokenAllocation[i]);
            totalEthAssigned = totalEthAssigned.add(ethAllocation);
        }

        // Update balancePendingWithdrawal_ - this allows us to get an accurate reflection of the div pool
        balancePendingWithdrawal_ = balancePendingWithdrawal_.add(totalEthAssigned);

        // As we will likely cause this function in batches this allows us to make sure we don't oversettle (failsafe)
        divWinner.tokensProcessed = divWinner.tokensProcessed.add(totalTokensAllocatedEth);

        // This should never occur, but a failsafe for when automated div payments are rolled out
        require(divWinner.tokensProcessed <= divWinner.totalTokens);
    }

    function withdrawDividends() external {
        require(addressToDividendBalance[msg.sender] > 0, "Must have a valid dividend balance");
        uint senderBalance = addressToDividendBalance[msg.sender];
        addressToDividendBalance[msg.sender] = 0;
        balancePendingWithdrawal_ = balancePendingWithdrawal_.sub(senderBalance);
        msg.sender.transfer(senderBalance);
        emit DividendWithdrawal(msg.sender, senderBalance);
    }
}
function newInitialPlayerOffering(
    string _name, 
    string _symbol, 
    uint _startPrice, 
    uint _incrementalPrice, 
    address _owner,
    uint256 _playerId,
    uint8 _promoSharesQuantity
) 
    external 
    onlyOwnerOrReferee
    payable
{
    PlayerToken playerTokenContract = (new PlayerToken).value(msg.value)(
        _name, 
        _symbol, 
        _startPrice, 
        _incrementalPrice, 
        _owner, 
        address(this), 
        _playerId, 
        _promoSharesQuantity
    );

    // Add it to a local storage so we can iterate over it to pull portfolio stats
    playerTokenContracts_.push(playerTokenContract);

    // Event handling
    emit InitialPlayerOffering(address(playerTokenContract), _name, _symbol);
}

// Create new instances of a PlayerToken contract and pass along msg.value (so the referee pays and not the contract)