contract EtherPornStars is Ownable, SupportsInterfaceWithLookup, ERC721BasicToken, ERC721 {

  struct StarData {
      uint16 fieldA;
      uint16 fieldB;
      uint32 fieldC;
      uint32 fieldD;
      uint32 fieldE;
      uint64 fieldF;
      uint64 fieldG;
  }

  address public logicContractAddress;
  address public starCoinAddress;

  // Ether Porn Star data
  mapping(uint256 => StarData) public starData;
  mapping(uint256 => bool) public starPower;
  mapping(uint256 => uint256) public starStudio;
  // Active Ether Porn Star
  mapping(address => uint256) public activeStar;
  // Mapping to studios
  mapping(uint8 => address) public studios;
  event ActiveStarChanged(address indexed _from, uint256 _tokenId);
  // Token name
  string internal name_;
  // Token symbol
  string internal symbol_;
  // Genomes
  mapping(uint256 => uint256) public genome;
  // Mapping from owner to list of owned token IDs
  mapping(address => uint256[]) internal ownedTokens;
  // Mapping from token ID to index of the owner tokens list
  mapping(uint256 => uint256) internal ownedTokensIndex;
  // Array with all token ids, used for enumeration
  uint256[] internal allTokens;
  // Mapping from token id to position in the allTokens array
  mapping(uint256 => uint256) internal allTokensIndex;
  // Optional mapping for token URIs
  mapping(uint256 => string) internal tokenURIs;
   // Mapping for multi-level network rewards
  mapping (uint256 => uint256) inviter;
  // Emitted when a user buys a star
  event BoughtStar(address indexed buyer, uint256 _tokenId, uint8 _studioId );
  /**
   * @dev Constructor function
   */
  modifier onlyLogicContract {
    require(msg.sender == logicContractAddress || msg.sender == owner);
    _;
  }
  constructor(string _name, string _symbol, address _starCoinAddress) public {
    name_ = _name;
    symbol_ = _symbol;
    starCoinAddress = _starCoinAddress;

    // register the supported interfaces to conform to ERC721 via ERC165
    _registerInterface(InterfaceId_ERC721Enumerable);
    _registerInterface(InterfaceId_ERC721Metadata);
  }

  /**
   * @dev Gets the token name
   * @return string representing the token name
   */


    /**
    * @dev Sets the token's interchangeable logic contract
    */
  function setLogicContract(address _logicContractAddress) external onlyOwner {
    logicContractAddress = _logicContractAddress;
  }

  function addStudio(uint8 _studioId, address _studioAddress) external onlyOwner {
    studios[_studioId] = _studioAddress;
}
  function name() external view returns (string) {
    return name_;
  }

  /**
   * @dev Gets the token symbol
   * @return string representing the token symbol
   */
  function symbol() external view returns (string) {
    return symbol_;
  }

  /**
   * @dev Returns an URI for a given token ID
   * Throws if the token ID does not exist. May return an empty string.
   * @param _tokenId uint256 ID of the token to query
   */
  function tokenURI(uint256 _tokenId) public view returns (string) {
    require(_exists(_tokenId));
    return tokenURIs[_tokenId];
  }

  /**
   * @dev Gets the token ID at a given index of the tokens list of the requested owner
   * @param _owner address owning the tokens list to be accessed
   * @param _index uint256 representing the index to be accessed of the requested tokens list
   * @return uint256 token ID at the given index of the tokens list owned by the requested address
   */
  function tokenOfOwnerByIndex(
    address _owner,
    uint256 _index
  )
    public
    view
    returns (uint256)
  {
    require(_index < balanceOf(_owner));
    return ownedTokens[_owner][_index];
  }

  /**
   * @dev Gets the total amount of tokens stored by the contract
   * @return uint256 representing the total amount of tokens
   */
  function totalSupply() public view returns (uint256) {
    return allTokens.length;
  }

  /**
   * @dev Gets the token ID at a given index of all the tokens in this contract
   * Reverts if the index is greater or equal to the total number of tokens
   * @param _index uint256 representing the index to be accessed of the tokens list
   * @return uint256 token ID at the given index of the tokens list
   */
  function tokenByIndex(uint256 _index) public view returns (uint256) {
    require(_index < totalSupply());
    return allTokens[_index];
  }

  /**
   * @dev Internal function to set the token URI for a given token
   * Reverts if the token ID does not exist
   * @param _tokenId uint256 ID of the token to set its URI
   * @param _uri string URI to assign
   */
  function _setTokenURI(uint256 _tokenId, string _uri) internal {
    require(_exists(_tokenId));
    tokenURIs[_tokenId] = _uri;
  }

  /**
   * @dev Internal function to add a token ID to the list of a given address
   * @param _to address representing the new owner of the given token ID
   * @param _tokenId uint256 ID of the token to be added to the tokens list of the given address
   */
  function addTokenTo(address _to, uint256 _tokenId) internal {
    super.addTokenTo(_to, _tokenId);
    uint256 length = ownedTokens[_to].length;
    ownedTokens[_to].push(_tokenId);
    ownedTokensIndex[_tokenId] = length;

    if (activeStar[_to] == 0) {
      activeStar[_to] = _tokenId;
      emit ActiveStarChanged(_to, _tokenId);
    }
  }

  /**
   * @dev Internal function to remove a token ID from the list of a given address
   * @param _from address representing the previous owner of the given token ID
   * @param _tokenId uint256 ID of the token to be removed from the tokens list of the given address
   */
  function removeTokenFrom(address _from, uint256 _tokenId) internal {
    super.removeTokenFrom(_from, _tokenId);
    // To prevent a gap in the array, we store the last token in the index of the token to delete, and
    // then delete the last slot.
    uint256 tokenIndex = ownedTokensIndex[_tokenId];
    uint256 lastTokenIndex = ownedTokens[_from].length.sub(1);
    uint256 lastToken = ownedTokens[_from][lastTokenIndex];
    ownedTokens[_from][tokenIndex] = lastToken;
    // This also deletes the contents at the last position of the array
    ownedTokens[_from].length--;
    // Note that this will handle single-element arrays. In that case, both tokenIndex and lastTokenIndex are going to
    // be zero. Then we can make sure that we will remove _tokenId from the ownedTokens list since we are first swapping
    // the lastToken to the first position, and then dropping the element placed in the last position of the list
    ownedTokensIndex[_tokenId] = 0;
    ownedTokensIndex[lastToken] = tokenIndex;
  }

  /**
   * @dev Internal function to mint a new token
   * Reverts if the given token ID already exists
   * @param _to address the beneficiary that will own the minted token
   * @param _tokenId uint256 ID of the token to be minted by the msg.sender
   */
  function _mint(address _to, uint256 _tokenId) internal {
    super._mint(_to, _tokenId);
    allTokensIndex[_tokenId] = allTokens.length;
    allTokens.push(_tokenId);
  }

  function mint(address _to, uint256 _tokenId) external onlyLogicContract {
    _mint(_to, _tokenId);
  }
  /**
   * @dev Internal function to burn a specific token
   * Reverts if the token does not exist
   * @param _owner owner of the token to burn
   * @param _tokenId uint256 ID of the token being burned by the msg.sender
   */
  function _burn(address _owner, uint256 _tokenId) internal {
    super._burn(_owner, _tokenId);
    // Clear metadata (if any)
    if (bytes(tokenURIs[_tokenId]).length != 0) {
      delete tokenURIs[_tokenId];
    }
    // Reorg all tokens array
    uint256 tokenIndex = allTokensIndex[_tokenId];
    uint256 lastTokenIndex = allTokens.length.sub(1);
    uint256 lastToken = allTokens[lastTokenIndex];

    allTokens[tokenIndex] = lastToken;
    allTokens[lastTokenIndex] = 0;

    allTokens.length--;
    allTokensIndex[_tokenId] = 0;
    allTokensIndex[lastToken] = tokenIndex;
  }

  function burn(address _owner, uint256 _tokenId) external onlyLogicContract {
    _burn(_owner, _tokenId);
}

/**
    * @dev Allows setting star data for a star
    * @param _tokenId star to set data for
    */
  function setStarData(
      uint256 _tokenId,
      uint16 _fieldA,
      uint16 _fieldB,
      uint32 _fieldC,
      uint32 _fieldD,
      uint32 _fieldE,
      uint64 _fieldF,
      uint64 _fieldG
  ) external onlyLogicContract {
      starData[_tokenId] = StarData(
          _fieldA,
          _fieldB,
          _fieldC,
          _fieldD,
          _fieldE,
          _fieldF,
          _fieldG
      );
  }
    /**
    * @dev Allow setting star genome
    * @param _tokenId token to set data for
    * @param _genome genome data to set
    */
  function setGenome(uint256 _tokenId, uint256 _genome) external onlyLogicContract {
    genome[_tokenId] = _genome;
  }

  function activeStarGenome(address _owner) public view returns (uint256) {
    uint256 tokenId = activeStar[_owner];
    if (tokenId == 0) {
        return 0;
    }
    return genome[tokenId];
    }

  function setActiveStar(uint256 _tokenId) external {
    require(msg.sender == ownerOf(_tokenId));
    activeStar[msg.sender] = _tokenId;
    emit ActiveStarChanged(msg.sender, _tokenId);
    }

  function forceTransfer(address _from, address _to, uint256 _tokenId) external onlyLogicContract {
      require(_from != address(0));
      require(_to != address(0));
      removeTokenFrom(_from, _tokenId);
      addTokenTo(_to, _tokenId);
      emit Transfer(_from, _to, _tokenId);
  }
  function transfer(address _to, uint256 _tokenId) external {
    require(msg.sender == ownerOf(_tokenId));
    require(_to != address(0));
    removeTokenFrom(msg.sender, _tokenId);
    addTokenTo(_to, _tokenId);
    emit Transfer(msg.sender, _to, _tokenId);
    }
  function addrecruit(uint256 _recId, uint256 _inviterId) private {
    inviter[_recId] = _inviterId;
}
  function buyStar(uint256 _tokenId, uint8 _studioId, uint256 _inviterId) external payable {
      require(msg.value >= 0.1 ether);
      _mint(msg.sender, _tokenId);
      emit BoughtStar(msg.sender, _tokenId, _studioId);
      uint amount = msg.value;
      starCoinAddress.transfer(msg.value);
      addrecruit(_tokenId, _inviterId);
      starStudio[_tokenId] = _studioId;
      StarCoin instanceStarCoin = StarCoin(starCoinAddress);
      instanceStarCoin.rewardTokens(msg.sender, amount);
        if (_inviterId != 0) {
          recReward(amount, _inviterId);
      }
      if(_studioId == 1) {
          starPower[_tokenId] = true;
      }
    }
  function recReward(uint amount, uint256 _inviterId) private {
    StarCoin instanceStarCoin = StarCoin(starCoinAddress);
    uint i=0;
    owner = ownerOf(_inviterId);
    amount = amount/2;
    instanceStarCoin.rewardTokens(owner, amount);
    while (i < 4) {
      amount = amount/2;
      owner = ownerOf(inviter[_inviterId]);
      if(owner==address(0)){
        break;
      }
      instanceStarCoin.rewardTokens(owner, amount);
      _inviterId = inviter[_inviterId];
      i++;
    }
  }

  function myTokens()
    external
    view
    returns (
      uint256[]
    )
  {
    return ownedTokens[msg.sender];
  }
}
/**
 * @title SupportsInterfaceWithLookup
 * @author Matt Condon (@shrugs)
 * @dev Implements ERC165 using a lookup table.
 */
function setLogicContract(address _logicContractAddress) external onlyOwner {
  logicContractAddress = _logicContractAddress;
}

/**
 * @dev Sets the token's interchangeable logic contract
 */