abstract contract Tierra is ERC721, ERC721Enumerable, Ownable, ReentrancyGuard, VRFConsumerBase {

  // The total supply.
  uint256 public maxSupply;
  
  // The min/max number of NFTs that a user can purchase in one order.
  uint256 public minQuantity = 1;
  uint256 public maxQuantity = 100;

  // Metadata URI.
  string public baseURI = "";
  string public fileEXT = ".json";

  // The price of a single NFT token.
  uint256 public price = 0.055 ether;

  // The sale status.
  bool public isSaleActive = false;

  // The competition status.
  bool public isCompetitionActive = true;

  // The competition amount - 100 ETH!.
  uint256 public constant competitionAmount = 100 ether;

  // The competition's random number generator properties.
  bytes32 internal keyHash;
  uint256 public oracleFee;
  bytes32 internal requestId;
  uint256 public randomResult;

  // Contract data.
  struct ContractData {
    uint256 maxSupply;
    uint256 totalSupply;
    uint256 remainingTokens;
    address contractAddress;
    bool isSaleActive;
    bool isCompetitionActive;
    uint256 competitionAmount;
    uint256 price;
    string baseURI;
    uint256 oracleFee;
    uint256 randomResult;
  }
  
  /**
  * @dev Constructor.
  * @param name: The contract name.
  * @param symbol: The contract symbol.
  * @param _maxSupply: The maximum token supply.
  * @param _baseTokenURI: The base URI for token metadata.
  * @param _isCompetitionEnabled: Enable the competition for this contract.
  * @param vrfCoordinator: The Chainlink VRF coordinator address for the current network.
  * @param linkContractAddress: The Chainlink token's contract address for the current network.
  * @param _keyHash: The Chainlink oracle's keyhash for the current network.
  * @param _oracleFee: The Chainlink oracle's transaction fee.
  */
  constructor(
    string memory name, 
    string memory symbol, 
    uint256 _maxSupply,
    string memory _baseTokenURI,
    bool _isCompetitionEnabled,
    address vrfCoordinator,
    address linkContractAddress,
    bytes32 _keyHash,
    uint256 _oracleFee
  ) ERC721(name, symbol) VRFConsumerBase(vrfCoordinator, linkContractAddress) {
    maxSupply = _maxSupply;
    baseURI = _baseTokenURI;
    isCompetitionActive = _isCompetitionEnabled;
    keyHash = _keyHash;
    oracleFee = _oracleFee;
  }

  /**
  * @dev Mint tokens.
  * @param quantity: The number of tokens to mint.
  */
  function mint(uint256 quantity) public payable {
    require(!isSoldOut(), "Sold out.");
    require(isSaleActive, "The sale is not active.");
    require(quantity >= minQuantity, "Quantity must be at least 1.");
    require(quantity <= maxQuantity, "Maximum quantity exceeded.");
    require((totalSupply() + quantity) <= maxSupply, "Purchase exceeds remaining tokens.");
    require(msg.value >= (price * quantity), "Incorrect amount sent.");
    
    // Mint tokens.
    for(uint256 i = 0; i < quantity; ++i) {
        uint256 tokenIndex = totalSupply();
        if (tokenIndex < maxSupply) {
          _safeMint(msg.sender, tokenIndex);
        }
    }

    // Trigger the competition payout when the last token is sold.
    if (isSoldOut() && isCompetitionActive && ownerOf(maxSupply-1) == msg.sender) {
      requestRandomness();
    }
  }

  /**
  * @dev Return all public contract data.
  */
  function getData() public view virtual returns (ContractData memory) {
    return ContractData(
      maxSupply,
      totalSupply(),
      remainingTokens(),
      address(this),
      isSaleActive,
      isCompetitionActive,
      competitionAmount,
      price,
      baseURI,
      oracleFee,
      randomResult
    );
  }

  /**
  * @dev Return true if all tokens have been sold.
  */
  function isSoldOut() public view virtual returns (bool) {
    return totalSupply() >= maxSupply;
  }

  /**
  * @dev Return the number of remaining tokens.
  */
  function remainingTokens() public view virtual returns (uint256) {
    return maxSupply - totalSupply();
  }
  
  /**
  * @dev Set the flag isSaleActive.
  * @param active: The sale's new active status.
  */
  function setSaleActive(bool active) public onlyOwner {
    isSaleActive = active;
  }

  /**
  * @dev Call base class: ERC721Enumerable._beforeTokenTransfer().
  */
  function _beforeTokenTransfer(address from, address to, uint256 tokenId)
    internal
    override(ERC721, ERC721Enumerable) 
  {
    super._beforeTokenTransfer(from, to, tokenId);
  }

  /**
  * @dev Call base class: ERC721Enumerable.supportsInterface().
  */
  function supportsInterface(bytes4 interfaceId)
    public
    view
    override(ERC721, ERC721Enumerable)
    returns (bool)
  {
    return super.supportsInterface(interfaceId);
  }

  /**
  * @dev Return the metadata URI for a token.
  * @param tokenId: The id of the token to get the URI for.
  */
  function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
    require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token.");
    string memory uri = _baseURI();
    return bytes(uri).length > 0 
      ? string(abi.encodePacked(uri, Strings.toString(tokenId), fileEXT)) 
      : "";
  }

  /**
  * @dev Return the base URI for token metadata.
  */
  function _baseURI() internal view virtual override returns (string memory) {
    return baseURI;
  }

  /**
  * @dev Set the base URI for token metadata.
  * @param uri: The new base URI.
  */
  function setBaseURI(string memory uri) public onlyOwner {
    baseURI = uri;
  }

  /**
  * @dev Set the metadata URI file extension.
  * @param ext: The new file extension.
  */
  function setBaseEXT(string memory ext) public onlyOwner {
    fileEXT = ext;
  }

  /**
  * @dev Set the NFT token sale price.
  * @param value: The new token price.
  */
  function setPrice(uint256 value) public onlyOwner {
    require(value > 0, "Price must be greater than 0.");
    price = value;
  } 

  /**
  * @dev Set the max number of tokens that can be purchased in one order.
  * @param value: The new max quantity.
  */
  function setMaxQuantity(uint256 value) public onlyOwner {
    require(value > 0, "Max quantity must be greater than 0.");
    maxQuantity = value;
  } 

  /**
  * @dev Set the oracle fee for the chainlink network.
  * @param value: The new oracle fee.
  */
  function setOracleFee(uint256 value) public onlyOwner {
    require(value > 0, "Oracle fee must be greater than 0.");
    oracleFee = value;
  } 

  /**
  * @dev Return the contract's current ETH balance.
  */
  function balance() public view virtual returns (uint256) {
    return address(this).balance;
  }

  /**
  * @dev Return the contract's current LINK balance.
  */
  function linkBalance() public view virtual returns (uint256) {
    return LINK.balanceOf(address(this));
  }
  
  /**
  * @dev Withdraw the current ETH balance (minus the competition payout) to the owner's wallet.
  */
  function withdraw() public payable onlyOwner {
    uint256 amount = balance() - (isCompetitionActive ? competitionAmount : 0);
    require(amount > 0, "Insufficient balance.");
    payable(msg.sender).transfer(amount);
  } 

  /**
  * @dev Withdraw the current LINK balance to the owner's wallet.
  */
  function linkWithdraw() public payable onlyOwner {
    uint256 amount = linkBalance();
    require(amount > 0, "Insufficient LINK balance.");
    LINK.transfer(msg.sender, amount);
  } 

  /**
  * @dev Random number generator (requestRandomness()) callback function.
  * @dev Send 100 ETH to a random address.
  */
  function fulfillRandomness(bytes32 _requestId, uint256 randomness) internal override nonReentrant {
    require(requestId == _requestId);
    require(isSoldOut(), "The sale is not complete.");
    require(isCompetitionActive, "The competition is over.");
    isCompetitionActive = false;
    randomResult = randomness % maxSupply;
    payable(ownerOf(randomResult)).transfer(competitionAmount);
  }

  /**
  * @dev Trigger the competition payout (internally).
  */
  function requestRandomness() internal nonReentrant {
    require(LINK.balanceOf(address(this)) >= oracleFee, "Insufficient LINK balance.");
    requestId = requestRandomness(keyHash, oracleFee);
  }

  /**
  * @dev Trigger the competition payout (externally).
  */
  function competitionPayout() public payable onlyOwner {
    require(isSoldOut(), "The sale is not complete.");
    require(isCompetitionActive, "The competition is over.");
    requestRandomness();
  }

  /**
  * @dev Return the address of the competition winner (or 0x0 if the competition has not finished).
  */
  function competitionWinner() public view virtual returns (address) {
    return isCompetitionActive ? address(0) : ownerOf(randomResult);
  }
}
// Imports
// Tierra
function _beforeTokenTransfer(address from, address to, uint256 tokenId)
  internal
  override(ERC721, ERC721Enumerable) 
{
  super._beforeTokenTransfer(from, to, tokenId);
}

/**
 * @dev Call base class: ERC721Enumerable._beforeTokenTransfer().
 */