contract TheGraveyardSale is ERC721, Ownable, RBAC {
  using SafeMath for uint256;

  bool public canSetURI = true; // one way toggle that allows use for hardSetTokenURI
  // use ONLY in the case that community asks for this functionality to be removed
  // this cannot be undone.

  bool public saleIsActive = false; // current status of sale, default is inactive
  bool public premintIsActive = false; // curent status of premint, default is inactive
  string public NFT_PROVENANCE = ''; // hash representing ownership history of the NFT
  uint256 public NFTPrice = 42000000000000000; // 0.042 ETH
  uint256 public NFTReserve = 50; // Reserve amount of NFTs [COLIN]
  uint256 public constant maxNFTPurchase = 15; // max no. of tokens that can be bought at a time
  uint256 public constant WALLET_CAP = 30; // max no. of tokens a wallet can own before losing the ability to mint
  uint256 public constant maxPremintPurchase = 3; // max no. of tokens that can be bought or owned during premint
  uint256 public constant MAX_NFTS = 4200; // max total supply of NFTs 4200
  uint256 public constant PREMINT_SUPPLY = 420; // 420
  uint256 public premintReserve = 420; //420
  // address[420] private premintList;
  constructor() ERC721('The Graveyard Sale', 'SKULL') {}

  // sets canSetURI to false. USE ONLY IF COMMUNITY ASKS FOR FUNCTIONALITY TO BE REMOVED
  function cannotSetURI() public onlyOwner
  {
    canSetURI = false;
  }

  // set token URI in the event webhost fails: use with ipfs json
  function hardSetTokenURI(uint256 tokenId, string memory tokenURI) public onlyOwner
  {
    require(canSetURI == true, "This function is no longer available");
    _setTokenURI(tokenId, tokenURI);
  }

  // Withdraws entire balance of contract (ETH) to owner account (owner-only)
  function withdraw() public onlyOwner {
    uint256 balance = address(this).balance;
    msg.sender.transfer(balance);
  }

  // Withdraws amount specified (ETH) from balance of contract to owner account (owner-only)
  function withdrawPartial(address payable _recipient, uint256 _amount)
    public
    onlyOwner
  {
    // Check that amount is not more than total contract balance
    require(
      _amount > 0 && _amount <= address(this).balance,
      'Withdraw amount must be positive and not exceed total contract balance'
    );
    _recipient.transfer(_amount);
  }

  // Sets a new price for the NFT (owner-only)
  function setNFTPrice(uint256 newPrice) public onlyOwner {
    NFTPrice = newPrice;
  }

  /**
   * Mints specified amount of new tokens for the recipient from the reserve
   * reduces the reserve by this amount (owner-only)
   */
  function reserveNFTs(address _to, string memory tokenURI) public onlyOwner {
    uint256 supply = totalSupply();
    // Checks that there is sufficient reserve supply of tokens for the transaction
    require(
      NFTReserve > 0,
      'Not enough reserve left'
    );
    _safeMint(_to, supply);
    _setTokenURI(supply, tokenURI);


    NFTReserve = NFTReserve.sub(1);
  }

  // Sets the provenance (ownership history) of the NFT (owner-only). The information is encoded by its hash value.
  function setProvenanceHash(string memory provenanceHash) public onlyOwner {
    NFT_PROVENANCE = provenanceHash;
  }

  /**
   * Sets the base URI for all token IDs. It is automatically added as a prefix to the value returned in {tokenURI},
   * or to the token ID if {tokenURI} is empty. Identical to internal function _setBaseURI, but with public
   * visibility, and can be called by owner only.
   */
  function setBaseURI(string memory baseURI) public onlyOwner {
    _setBaseURI(baseURI);
  }

  // Toggle the current status of the sale between active/inactive (owner-only)
  function flipSaleState() public onlyOwner {
    saleIsActive = !saleIsActive;
  }

  function flipPremintState() public onlyOwner {
    premintIsActive = !premintIsActive;
  }

  // Returns an array of all tokens belonging to an owner (if any), indexed by their token ID
  function tokensOfOwner(address _owner)
    external
    view
    returns (uint256[] memory)
  {
    uint256 tokenCount = balanceOf(_owner);
    if (tokenCount == 0) {
      // Return an empty array
      return new uint256[](0);
    } else {
      uint256[] memory result = new uint256[](tokenCount);
      uint256 index;
      for (index = 0; index < tokenCount; index++) {
        result[index] = tokenOfOwnerByIndex(_owner, index);
      }
      return result;
    }
  }

  // Mints specified number of NFTs for the receiver and accepts payment (ETH) in return
  function mintNFT(uint256 numberOfTokens) public payable {
    // Checks that sale is currently active
    require(saleIsActive, 'Sale must be active to mint NFT');
    // Checks that requested number of tokens is positive and not more than the max purchase limit
    require(
      numberOfTokens > 0 && numberOfTokens <= maxNFTPurchase,
      'Exceeded max token purchase'
    );
    // Checks that final total supply of tokens (if sale occurs) would not exceed the max supply limit
    require(
      totalSupply().add(numberOfTokens) <= MAX_NFTS,
      'Purchase would exceed max supply of NFTs'
    );

    require(balanceOf(msg.sender).add(numberOfTokens) <= WALLET_CAP);
    /**
         Checks that the buyer sends at least the total price of the transaction (ETH), calculated as number
        * of tokens times the token price
        */
    require(
      msg.value >= NFTPrice.mul(numberOfTokens),
      'ETH value sent is not correct'
    );

    for (uint256 i = 0; i < numberOfTokens; i++) {
      uint256 mintIndex = totalSupply();
      if (totalSupply() < MAX_NFTS) {
        _safeMint(msg.sender, mintIndex);
      }
    }
  }

  function premint(uint256 _toPremint) external onlyPreminter() payable
  {

    uint256 supply = totalSupply();
    // Checks that there is sufficient reserve supply of tokens for the transaction
    require(
      _toPremint > 0 && _toPremint <= premintReserve,
      'Not enough reserve left'
    );
    require((balanceOf(msg.sender) + _toPremint) <= maxPremintPurchase, "Limit of 3 NFTs during presale");
    require(premintIsActive, 'Premint must be active to mint NFT');
    // Checks that requested number of tokens is positive and not more than the max purchase limit
    require(
      _toPremint > 0 && _toPremint <= maxPremintPurchase,
      'Exceeded max token purchase'
    );
    // Checks that final total supply of tokens (if sale occurs) would not exceed the max supply limit
    require(
      totalSupply().add(_toPremint) <= PREMINT_SUPPLY,
      'Purchase would exceed max supply of NFTs'
    );
    /**
         Checks that the buyer sends at least the total price of the transaction (ETH), calculated as number
        * of tokens times the token price
        */
    require(
      msg.value >= NFTPrice.mul(_toPremint),
      'ETH value sent is not correct'
    );

    for (uint256 i = 0; i < _toPremint; i++) {
      uint256 mintIndex = totalSupply();
      if (totalSupply() < MAX_NFTS) {
        _safeMint(msg.sender, mintIndex);
      }
    }

    premintReserve = premintReserve.sub(_toPremint);
  }
}
/**
 * @title Skulls contract
 * @dev Extends ERC721 Non-Fungible Token Standard basic implementation
 */
function withdrawPartial(address payable _recipient, uint256 _amount)
  public
  onlyOwner
{
  // Check that amount is not more than total contract balance
  require(
    _amount > 0 && _amount <= address(this).balance,
    'Withdraw amount must be positive and not exceed total contract balance'
  );
  _recipient.transfer(_amount);
}

// Withdraws amount specified (ETH) from balance of contract to owner account (owner-only)