contract SportStarMaster {

    // ***** EVENTS ***/

    // @dev The Birth event is fired whenever a new token comes into existence.
    event Birth(uint256 tokenId, string name, address owner);

    // @dev The TokenSold event is fired whenever a token is sold.
    event TokenSold(uint256 tokenId, uint256 oldPrice, uint256 newPrice, address prevOwner, address winner);

    // @dev Transfer event as defined in current draft of ERC721.
    //  ownership is assigned, including births.
    event Transfer(address from, address to, uint256 tokenId);



    // ***** CONSTANTS ***/

    uint256 private startingPrice = 0.001 ether;
    uint256 private constant PROMO_CREATION_LIMIT = 5000;
    uint256 private firstStepLimit = 0.053613 ether;
    uint256 private secondStepLimit = 0.564957 ether;



    // ***** STORAGE ***/

    // @dev A mapping from TokenIDs to the price of the token.
    mapping(uint256 => uint256) private tokenIndexToPrice;

    // The addresses of the accounts (or contracts) that can execute actions within each roles.
    address public ceoAddress;
    address public cooAddress;

    // The address of tokens contract
    SportStarToken public tokensContract;

    uint256 public promoCreatedCount;


    uint256 private increaseLimit1 = 0.05 ether;
    uint256 private increaseLimit2 = 0.5 ether;
    uint256 private increaseLimit3 = 2.0 ether;
    uint256 private increaseLimit4 = 5.0 ether;



    // ***** ACCESS MODIFIERS ***/

    // @dev Access modifier for CEO-only functionality
    modifier onlyCEO() {
        require(msg.sender == ceoAddress);
        _;
    }

    // @dev Access modifier for COO-only functionality
    modifier onlyCOO() {
        require(msg.sender == cooAddress);
        _;
    }

    // Access modifier for contract owner only functionality
    modifier onlyCLevel() {
        require(
            msg.sender == ceoAddress ||
            msg.sender == cooAddress
        );
        _;
    }



    // ***** CONSTRUCTOR ***/

    function SportStarMaster() public {
        ceoAddress = msg.sender;
        cooAddress = msg.sender;
    }


    function setTokensContract(address _newTokensContract) public onlyCEO {
        require(_newTokensContract != address(0));

        tokensContract = SportStarToken(_newTokensContract);
    }



    // ***** PRIVILEGES SETTING FUNCTIONS

    function setCEO(address _newCEO) public onlyCEO {
        require(_newCEO != address(0));

        ceoAddress = _newCEO;
    }

    function setCOO(address _newCOO) public onlyCEO {
        require(_newCOO != address(0));

        cooAddress = _newCOO;
    }



    // ***** PUBLIC FUNCTIONS ***/

    function getTokenInfo(uint256 _tokenId) public view returns (
        address owner,
        uint256 price
    ) {
        owner = tokensContract.ownerOf(_tokenId);
        price = tokenIndexToPrice[_tokenId];
    }

    // @dev Creates a new promo Token with the given name, with given _price and assignes it to an address.
    function createPromoToken(address _owner, string _name, uint256 _price) public onlyCOO {
        require(promoCreatedCount < PROMO_CREATION_LIMIT);

        address tokenOwner = _owner;
        if (tokenOwner == address(0)) {
            tokenOwner = cooAddress;
        }

        if (_price <= 0) {
            _price = startingPrice;
        }

        promoCreatedCount++;
        uint256 newTokenId = tokensContract.createToken(_name, tokenOwner);
        tokenIndexToPrice[newTokenId] = startingPrice;

        Birth(newTokenId, _name, _owner);
    }

    // @dev Creates a new Token with the given name.
    function createContractToken(string _name) public onlyCOO {
        uint256 newTokenId = tokensContract.createToken(_name, address(this));
        tokenIndexToPrice[newTokenId] = startingPrice;

        Birth(newTokenId, _name, address(this));
    }

    // Allows someone to send ether and obtain the token
    function purchase(uint256 _tokenId) public payable {
        address oldOwner = tokensContract.ownerOf(_tokenId);
        address newOwner = msg.sender;

        uint256 sellingPrice = tokenIndexToPrice[_tokenId];

        // Making sure token owner is not sending to self
        require(oldOwner != newOwner);

        // Safety check to prevent against an unexpected 0x0 default.
        require(_addressNotNull(newOwner));

        // Making sure sent amount is greater than or equal to the sellingPrice
        require(msg.value >= sellingPrice);

        uint256 devCut = calculateDevCut(sellingPrice);
        uint256 payment = SafeMath.sub(sellingPrice, devCut);
        uint256 purchaseExcess = SafeMath.sub(msg.value, sellingPrice);

        tokenIndexToPrice[_tokenId] = calculateNextPrice(sellingPrice);

        tokensContract.updateOwner(oldOwner, newOwner, _tokenId);

        // Pay previous tokenOwner if owner is not contract
        if (oldOwner != address(this)) {
            oldOwner.transfer(payment);
        }

        TokenSold(_tokenId, sellingPrice, tokenIndexToPrice[_tokenId], oldOwner, newOwner);

        msg.sender.transfer(purchaseExcess);
    }

    function priceOf(uint256 _tokenId) public view returns (uint256 price) {
        return tokenIndexToPrice[_tokenId];
    }

    function calculateDevCut (uint256 _price) public view returns (uint256 _devCut) {
        if (_price < increaseLimit1) {
            return SafeMath.div(SafeMath.mul(_price, 3), 100); // 3%
        } else if (_price < increaseLimit2) {
            return SafeMath.div(SafeMath.mul(_price, 3), 100); // 3%
        } else if (_price < increaseLimit3) {
            return SafeMath.div(SafeMath.mul(_price, 3), 100); // 3%
        } else if (_price < increaseLimit4) {
            return SafeMath.div(SafeMath.mul(_price, 3), 100); // 3%
        } else {
            return SafeMath.div(SafeMath.mul(_price, 2), 100); // 2%
        }
    }

    function calculateNextPrice (uint256 _price) public view returns (uint256 _nextPrice) {
        if (_price < increaseLimit1) {
            return SafeMath.div(SafeMath.mul(_price, 200), 97);
        } else if (_price < increaseLimit2) {
            return SafeMath.div(SafeMath.mul(_price, 133), 97);
        } else if (_price < increaseLimit3) {
            return SafeMath.div(SafeMath.mul(_price, 125), 97);
        } else if (_price < increaseLimit4) {
            return SafeMath.div(SafeMath.mul(_price, 115), 97);
        } else {
            return SafeMath.div(SafeMath.mul(_price, 113), 98);
        }
    }



    // PRIVATE FUNCTIONS

    // Safety check on _to address to prevent against an unexpected 0x0 default.
    function _addressNotNull(address _to) private pure returns (bool) {
        return _to != address(0);
    }
}
function setCEO(address _newCEO) public onlyCEO {
    require(_newCEO != address(0));

    ceoAddress = _newCEO;
}

// ***** PRIVILEGES SETTING FUNCTIONS