contract CryptoArtsToken is AccessControl, DetailedERC721 {
  using SafeMath for uint256;

  /// @dev The TokenCreated event is fired whenever a new token is created.
  event TokenCreated(uint256 tokenId, string name, uint256 price, address owner);

  /// @dev The TokenSold event is fired whenever a token is sold.
  event TokenSold(uint256 indexed tokenId, string name, uint256 sellingPrice,
   uint256 newPrice, address indexed oldOwner, address indexed newOwner);


  /// @dev A mapping from tokenIds to the address that owns them. All tokens have
  ///  some valid owner address.
  mapping (uint256 => address) private tokenIdToOwner;

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

  /// @dev A mapping from owner address to count of tokens that address owns.
  ///  Used internally inside balanceOf() to resolve ownership count.
  mapping (address => uint256) private ownershipTokenCount;

  /// @dev A mapping from TokenIds to an address that has been approved to call
  ///  transferFrom(). Each Token can only have one approved address for transfer
  ///  at any time. A zero value means no approval is outstanding
  mapping (uint256 => address) public tokenIdToApproved;


  struct Art {
    string name;
  }
  Art[] private arts;

  uint256 private startingPrice = 0.01 ether;
  bool private erc721Enabled = false;

  modifier onlyERC721() {
    require(erc721Enabled);
    _;
  }

  /// @dev Creates a new token with the given name and _price and assignes it to an _owner.
  function createToken(string _name, address _owner, uint256 _price) public onlyCLevel {
    require(_owner != address(0));
    require(_price >= startingPrice);

    _createToken(_name, _owner, _price);
  }

  /// @dev Creates a new token with the given name.
  function createToken(string _name) public onlyCLevel {
    _createToken(_name, address(this), startingPrice);
  }

  function _createToken(string _name, address _owner, uint256 _price) private {
    Art memory _art = Art({
      name: _name
    });
    uint256 newTokenId = arts.push(_art) - 1;
    tokenIdToPrice[newTokenId] = _price;

    TokenCreated(newTokenId, _name, _price, _owner);

    // This will assign ownership, and also emit the Transfer event as per ERC721 draft
    _transfer(address(0), _owner, newTokenId);
  }

  function getToken(uint256 _tokenId) public view returns (
    string _tokenName,
    uint256 _price,
    uint256 _nextPrice,
    address _owner
  ) {
    _tokenName = arts[_tokenId].name;
    _price = tokenIdToPrice[_tokenId];
    _nextPrice = nextPriceOf(_tokenId);
    _owner = tokenIdToOwner[_tokenId];
  }

  function getAllTokens() public view returns (
      uint256[],
      uint256[],
      address[]
  ) {
      uint256 total = totalSupply();
      uint256[] memory prices = new uint256[](total);
      uint256[] memory nextPrices = new uint256[](total);
      address[] memory owners = new address[](total);

      for (uint256 i = 0; i < total; i++) {
          prices[i] = tokenIdToPrice[i];
          nextPrices[i] = nextPriceOf(i);
          owners[i] = tokenIdToOwner[i];
      }

      return (prices, nextPrices, owners);
  }

  function tokensOf(address _owner) public view returns(uint256[]) {
    uint256 tokenCount = balanceOf(_owner);
    if (tokenCount == 0) {
        // Return an empty array
      return new uint256[](0);
    } else {
      uint256[] memory result = new uint256[](tokenCount);
      uint256 total = totalSupply();
      uint256 resultIndex = 0;

      for (uint256 i = 0; i < total; i++) {
        if (tokenIdToOwner[i] == _owner) {
          result[resultIndex] = i;
          resultIndex++;
        }
      }
      return result;
    }
  }

  /// @dev This function withdraws the contract owner's cut.
  /// Any amount may be withdrawn as there is no user funds.
  /// User funds are immediately sent to the old owner in `purchase`
  function withdrawBalance(address _to, uint256 _amount) public onlyCEO {
    require(_amount <= this.balance);

    if (_amount == 0) {
      _amount = this.balance;
    }

    if (_to == address(0)) {
      ceoAddress.transfer(_amount);
    } else {
      _to.transfer(_amount);
    }
  }

  // Send ether and obtain the token
  function purchase(uint256 _tokenId) public payable whenNotPaused {
    address oldOwner = ownerOf(_tokenId);
    address newOwner = msg.sender;
    uint256 sellingPrice = priceOf(_tokenId);

    // active tokens
    require(oldOwner != address(0));
    // maybe one day newOwner's logic allows this to happen
    require(newOwner != address(0));
    // don't buy from yourself
    require(oldOwner != newOwner);
    // don't sell to contracts
    // but even this doesn't prevent bad contracts to become an owner of a token
    require(!_isContract(newOwner));
    // another check to be sure that token is active
    require(sellingPrice > 0);
    // min required amount check
    require(msg.value >= sellingPrice);

    // transfer to the new owner
    _transfer(oldOwner, newOwner, _tokenId);
    // update fields before emitting an event
    tokenIdToPrice[_tokenId] = nextPriceOf(_tokenId);
    // emit event
    TokenSold(_tokenId, arts[_tokenId].name, sellingPrice, priceOf(_tokenId), oldOwner, newOwner);

    // extra ether which should be returned back to buyer
    uint256 excess = msg.value.sub(sellingPrice);
    // contract owner's cut which is left in contract and accesed by withdrawBalance
    uint256 contractCut = sellingPrice.mul(6).div(100); // 6%

    // no need to transfer if it's initial sell
    if (oldOwner != address(this)) {
      // transfer payment to seller minus the contract's cut
      oldOwner.transfer(sellingPrice.sub(contractCut));
    }

    // return extra ether
    if (excess > 0) {
      newOwner.transfer(excess);
    }
  }

  function priceOf(uint256 _tokenId) public view returns (uint256 _price) {
    return tokenIdToPrice[_tokenId];
  }

  uint256 private increaseLimit1 = 0.05 ether;
  uint256 private increaseLimit2 = 0.5 ether;
  uint256 private increaseLimit3 = 5 ether;

  function nextPriceOf(uint256 _tokenId) public view returns (uint256 _nextPrice) {
    uint256 price = priceOf(_tokenId);
    if (price < increaseLimit1) {
      return price.mul(135).div(94);
    } else if (price < increaseLimit2) {
      return price.mul(120).div(94);
    } else if (price < increaseLimit3) {
      return price.mul(118).div(94);
    } else {
      return price.mul(116).div(94);
    }
  }


  /*** ERC-721 ***/
  // Unlocks ERC721 behaviour, allowing for trading on third party platforms.
  function enableERC721() onlyCEO public {
    erc721Enabled = true;
  }

  function totalSupply() public view returns (uint256 _totalSupply) {
    _totalSupply = arts.length;
  }

  function balanceOf(address _owner) public view returns (uint256 _balance) {
    _balance = ownershipTokenCount[_owner];
  }

  function ownerOf(uint256 _tokenId) public view returns (address _owner) {
    _owner = tokenIdToOwner[_tokenId];
    // require(_owner != address(0));
  }

  function approve(address _to, uint256 _tokenId) public whenNotPaused onlyERC721 {
    require(_owns(msg.sender, _tokenId));

    tokenIdToApproved[_tokenId] = _to;

    Approval(msg.sender, _to, _tokenId);
  }

  function transferFrom(address _from, address _to, uint256 _tokenId) public whenNotPaused onlyERC721 {
    require(_to != address(0));
    require(_owns(_from, _tokenId));
    require(_approved(msg.sender, _tokenId));

    _transfer(_from, _to, _tokenId);
  }

  function transfer(address _to, uint256 _tokenId) public whenNotPaused onlyERC721 {
      require(_to != address(0));
      require(_owns(msg.sender, _tokenId));

      // Reassign ownership, clear pending approvals, emit Transfer event.
      _transfer(msg.sender, _to, _tokenId);
  }

  function implementsERC721() public view whenNotPaused returns (bool) {
    return erc721Enabled;
  }

  function takeOwnership(uint256 _tokenId) public whenNotPaused onlyERC721 {
    require(_approved(msg.sender, _tokenId));

    _transfer(tokenIdToOwner[_tokenId], msg.sender, _tokenId);
  }

  function name() public view returns (string _name) {
    _name = "CryptoArts";
  }

  function symbol() public view returns (string _symbol) {
    _symbol = "XART";
  }


  /*** PRIVATES ***/
  /// @dev Check for token ownership.
  function _owns(address _claimant, uint256 _tokenId) private view returns (bool) {
      return tokenIdToOwner[_tokenId] == _claimant;
  }

  /// @dev For checking approval of transfer for address _to.
  function _approved(address _to, uint256 _tokenId) private view returns (bool) {
    return tokenIdToApproved[_tokenId] == _to;
  }

  /// @dev Assigns ownership of a specific token to an address.
  function _transfer(address _from, address _to, uint256 _tokenId) private {
    // Since the number of tokens is capped to 2^32 we can't overflow this
    ownershipTokenCount[_to]++;
    // Transfer ownership
    tokenIdToOwner[_tokenId] = _to;

    // When creating new token _from is 0x0, but we can't account that address.
    if (_from != address(0)) {
      ownershipTokenCount[_from]--;
      // clear any previously approved ownership exchange
      delete tokenIdToApproved[_tokenId];
    }

    // Emit the transfer event.
    Transfer(_from, _to, _tokenId);
  }

  /// @dev Checks if the address ia a contract or not
  function _isContract(address addr) private view returns (bool) {
    uint256 size;
    assembly { size := extcodesize(addr) }
    return size > 0;
  }
}
function withdrawBalance(address _to, uint256 _amount) public onlyCEO {
  require(_amount <= this.balance);

  if (_amount == 0) {
    _amount = this.balance;
  }

  if (_to == address(0)) {
    ceoAddress.transfer(_amount);
  } else {
    _to.transfer(_amount);
  }
}

/// @dev This function withdraws the contract owner's cut.
/// Any amount may be withdrawn as there is no user funds.
/// User funds are immediately sent to the old owner in `purchase`