library SafeMath {
    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        if (a == 0) {
            return 0;
        }
        uint256 c = a * b;
        assert(c / a == b);
        return c;
    }
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a / b;
        return c;
    }
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        assert(b <= a);
        return a - b;
    }
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        assert(c>= a);
        return c;
    }
}
contract ERC721 {
    function approve( address _to, uint256 _tokenId) public;
    function balanceOf(address _owner) public view returns (uint256 balance);
    function implementsERC721() public pure returns (bool);
    function ownerOf(uint256 _tokenId) public view returns (address addr);
    function takeOwnership(uint256 _tokenId) public;
    function totalSupply() public view returns (uint256 supply);
    function transferFrom(address _from, address _to, uint256 _tokenId) public;
    function transfer(address _to, uint256 _tokenId) public;

    event Transfer(address indexed from, address indexed to, uint256 tokenId);
    event Approval(address indexed owner, address indexed approved, uint256 tokenId);
}

contract AthleteToken is ERC721 {
    string public constant NAME = "CryptoFantasy";
    string public constant SYMBOL = "Athlete";
    uint256 private constant initPrice = 0.001 ether;
    event Birth(uint256 tokenId, address owner);
    event TokenSold(uint256 tokenId, uint256 sellPrice, address sellOwner, address buyOwner, string athleteId);
    event Transfer(address from, address to, uint256 tokenId);
    mapping (uint256 => address) public athleteIndexToOwner;

    mapping (address => uint256) private ownershipTokenCount;

    
    mapping (uint256 => address) public athleteIndexToApproved;
    mapping (uint256 => uint256) private athleteIndexToPrice;
    mapping (uint256 => uint256) private athleteIndexToActualFee;
    mapping (uint256 => uint256) private athleteIndexToSiteFee;
    mapping (uint256 => address) private athleteIndexToActualWalletId;
    mapping (uint256 => string) private athleteIndexToAthleteID;
    mapping (uint256 => bool) private athleteIndexToAthleteVerificationState;
    address public ceoAddress;
    address public cooAddress;
    uint256 public promoCreatedCount;

    struct Athlete {
        string  athleteId;
        address actualAddress;
        uint256 actualFee;
        uint256 siteFee;
        uint256 sellPrice;
        bool    isVerified;
    }
    Athlete[] private athletes;
    mapping (uint256 => Athlete) private athleteIndexToAthlete;
    modifier onlyCEO() {
        require(msg.sender == ceoAddress);
        _;
    }
    modifier onlyCOO() {
        require(msg.sender == cooAddress);
        _;
    }
    modifier onlyCLevel() {
        require(msg.sender == ceoAddress || msg.sender == cooAddress);
        _;
    }

    function AthleteToken() public {
        ceoAddress = msg.sender;
        cooAddress = msg.sender;
    }

    function approve( address _to, uint256 _tokenId ) public {
        require(_owns(msg.sender, _tokenId));
        athleteIndexToApproved[_tokenId] = _to;
        Approval(msg.sender, _to, _tokenId);
    }

    function balanceOf(address _owner) public view returns (uint256 balance) {
        return ownershipTokenCount[_owner];
    }

    function createOfAthleteCard(string _athleteId, address _actualAddress, uint256 _actualFee, uint256 _siteFee, uint256 _sellPrice) public onlyCOO returns (uint256 _newAthleteId) {
        
        address _athleteOwner = address(this);
        bool _verified = true;
        if ( _sellPrice <= 0 ) {
            _sellPrice = initPrice;
        }
        if ( _actualAddress == address(0) ){
            _actualAddress = ceoAddress;
            _verified = false;
        }
        
        Athlete memory _athlete = Athlete({ athleteId: _athleteId, actualAddress: _actualAddress, actualFee: _actualFee,  siteFee: _siteFee, sellPrice: _sellPrice, isVerified: _verified });
        uint256 newAthleteId = athletes.push(_athlete) - 1;
        
        require(newAthleteId == uint256(uint32(newAthleteId)));
        Birth(newAthleteId, _athleteOwner);
        
        athleteIndexToPrice[newAthleteId] = _sellPrice;
        athleteIndexToActualFee[newAthleteId] = _actualFee;
        athleteIndexToSiteFee[newAthleteId] = _siteFee;
        athleteIndexToActualWalletId[newAthleteId] = _actualAddress;
        athleteIndexToAthleteID[newAthleteId] = _athleteId;
        athleteIndexToAthlete[newAthleteId] = _athlete;
        athleteIndexToAthleteVerificationState[newAthleteId] = _verified;
        
        _transfer(address(0), _athleteOwner, newAthleteId);
        return newAthleteId;
    }
    
    function changeOriginWalletIdForAthlete( uint256 _tokenId, address _oringinWalletId ) public onlyCOO returns( string athleteId, address actualAddress, uint256 actualFee, uint256 siteFee, uint256 sellPrice, address owner) {
        athleteIndexToActualWalletId[_tokenId] = _oringinWalletId;
        Athlete storage athlete = athletes[_tokenId];
        athlete.actualAddress = _oringinWalletId;
        athleteId     = athlete.athleteId;
        actualAddress = athlete.actualAddress;
        actualFee     = athlete.actualFee;
        siteFee       = athlete.siteFee;
        sellPrice     = priceOf(_tokenId);
        owner         = ownerOf(_tokenId);
    }
    
    function changeSellPriceForAthlete( uint256 _tokenId, uint256 _newSellPrice ) public onlyCOO returns( string athleteId, address actualAddress, uint256 actualFee, uint256 siteFee, uint256 sellPrice, address owner) {
        athleteIndexToPrice[_tokenId] = _newSellPrice;
        Athlete storage athlete = athletes[_tokenId];
        athlete.sellPrice = _newSellPrice;
        athleteId     = athlete.athleteId;
        actualAddress = athlete.actualAddress;
        actualFee     = athlete.actualFee;
        siteFee       = athlete.siteFee;
        sellPrice     = athlete.sellPrice;
        owner         = ownerOf(_tokenId);
    }
    
    function createContractOfAthlete(string _athleteId, address _actualAddress, uint256 _actualFee, uint256 _siteFee, uint256 _sellPrice) public onlyCOO{
        _createOfAthlete(address(this), _athleteId, _actualAddress, _actualFee, _siteFee, _sellPrice);
    }

    function getAthlete(uint256 _tokenId) public view returns ( string athleteId, address actualAddress, uint256 actualFee, uint256 siteFee, uint256 sellPrice, address owner) {
        Athlete storage athlete = athletes[_tokenId];
        athleteId     = athlete.athleteId;
        actualAddress = athlete.actualAddress;
        actualFee     = athlete.actualFee;
        siteFee       = athlete.siteFee;
        sellPrice     = priceOf(_tokenId);
        owner         = ownerOf(_tokenId);
    }

    function implementsERC721() public pure returns (bool) {
        return true;
    }
    function name() public pure returns (string) {
        return NAME;
    }
    function ownerOf(uint256 _tokenId) public view returns (address owner) {
        owner = athleteIndexToOwner[_tokenId];
        require(owner != address(0));
    }
    function payout(address _to) public onlyCLevel {
        _payout(_to);
    }
    function purchase(uint256 _tokenId) public payable {
        address sellOwner = athleteIndexToOwner[_tokenId];
        address buyOwner = msg.sender;
        uint256 sellPrice = msg.value;

        //make sure token owner is not sending to self
        require(sellOwner != buyOwner);
        //safely check to prevent against an unexpected 0x0 default
        require(_addressNotNull(buyOwner));
        //make sure sent amount is greater than or equal to the sellPrice
        require(msg.value >= sellPrice);
        uint256 actualFee = uint256(SafeMath.div(SafeMath.mul(sellPrice, athleteIndexToActualFee[_tokenId]), 100)); // calculate actual fee
        uint256 siteFee   = uint256(SafeMath.div(SafeMath.mul(sellPrice, athleteIndexToSiteFee[_tokenId]), 100));   // calculate site fee
        uint256 payment   = uint256(SafeMath.sub(sellPrice, SafeMath.add(actualFee, siteFee)));   //payment for seller

        _transfer(sellOwner, buyOwner, _tokenId);
        //Pay previous tokenOwner if owner is not contract
        if ( sellOwner != address(this) ) {
            sellOwner.transfer(payment); // (1-(actual_fee+site_fee))*sellPrice
        }
        TokenSold(_tokenId, sellPrice, sellOwner, buyOwner, athletes[_tokenId].athleteId);
        address actualWallet = athleteIndexToActualWalletId[_tokenId];
        actualWallet.transfer(actualFee);
            
        ceoAddress.transfer(siteFee);

    }

    function priceOf(uint256 _tokenId) public view returns (uint256 price) {
        return athleteIndexToPrice[_tokenId];
    }
    function setCEO(address _newCEO) public onlyCEO {
        require(_newCEO != address(0));
        ceoAddress = _newCEO;
    }
    function setCOO(address _newCOO) public onlyCEO {
        require(_newCOO != address(0));
        cooAddress = _newCOO;
    }
    function symbol() public pure returns (string) {
        return SYMBOL;
    }
    function takeOwnership(uint256 _tokenId) public {
        address newOwner = msg.sender;
        address oldOwner = athleteIndexToOwner[_tokenId];
        
        require(_addressNotNull(newOwner));
        require(_approved(newOwner, _tokenId));
        _transfer(oldOwner, newOwner, _tokenId);
    }

    function tokenOfOwner(address _owner) public view returns(uint256[] ownerTokens) {
        uint256 tokenCount = balanceOf(_owner);
        if ( tokenCount == 0 ) {
            return new uint256[](0);
        }
        else {
            uint256[] memory result = new uint256[](tokenCount);
            uint256 totalAthletes = totalSupply();
            uint256 resultIndex = 0;
            uint256 athleteId;

            for(athleteId = 0; athleteId <= totalAthletes; athleteId++) {
                if (athleteIndexToOwner[athleteId] == _owner) {
                    result[resultIndex] = athleteId;
                    resultIndex++;
                }
            }
            return result;
        }
    }

    function totalSupply() public view returns (uint256 total) {
        return athletes.length;
    }

    function transfer( address _to, uint256 _tokenId ) public {
        require(_owns(msg.sender, _tokenId));
        require(_addressNotNull(_to));

        _transfer(msg.sender, _to, _tokenId);
    }

    function transferFrom( address _from, address _to, uint256 _tokenId ) public {
        require(_owns(_from, _tokenId));
        require(_approved(_to, _tokenId));
        require(_addressNotNull(_to));

        _transfer(_from, _to, _tokenId);
    }

    function _addressNotNull(address _to) private pure returns (bool) {
        return _to != address(0);
    }
    function _approved(address _to, uint256 _tokenId) private view returns (bool) {
        return athleteIndexToApproved[_tokenId] == _to;
    }

    function _createOfAthlete(address _athleteOwner, string _athleteId, address _actualAddress, uint256 _actualFee, uint256 _siteFee, uint256 _sellPrice) private {
        
        bool _verified = true;
        // Check sell price and origin wallet id
        if ( _sellPrice <= 0 ) {
            _sellPrice = initPrice;
        }
        if ( _actualAddress == address(0) ){
            _actualAddress = ceoAddress;
            _verified = false;
        }
        
        Athlete memory _athlete = Athlete({ athleteId: _athleteId, actualAddress: _actualAddress, actualFee: _actualFee,  siteFee: _siteFee, sellPrice: _sellPrice, isVerified: _verified });
        uint256 newAthleteId = athletes.push(_athlete) - 1;
        
        require(newAthleteId == uint256(uint32(newAthleteId)));
        Birth(newAthleteId, _athleteOwner);
        
        athleteIndexToPrice[newAthleteId] = _sellPrice;
        athleteIndexToActualFee[newAthleteId] = _actualFee;
        athleteIndexToSiteFee[newAthleteId] = _siteFee;
        athleteIndexToActualWalletId[newAthleteId] = _actualAddress;
        athleteIndexToAthleteID[newAthleteId] = _athleteId;
        athleteIndexToAthlete[newAthleteId] = _athlete;
        athleteIndexToAthleteVerificationState[newAthleteId] = _verified;
        
        _transfer(address(0), _athleteOwner, newAthleteId);

    }

    function _owns(address claimant, uint256 _tokenId) private view returns (bool) {
        return claimant == athleteIndexToOwner[_tokenId];
    }
    function _payout(address _to) private {
        if (_to == address(0)) {
            ceoAddress.transfer(this.balance);
        }
        else {
            _to.transfer(this.balance);
        }
    }
    function _transfer(address _from, address _to, uint256 _tokenId) private {
        ownershipTokenCount[_to]++;
        athleteIndexToOwner[_tokenId] = _to;
        if (_from != address(0)) {
            ownershipTokenCount[_from]--;
            delete athleteIndexToApproved[_tokenId];
        }
        Transfer(_from, _to, _tokenId);
    }

}