contract MooMooCollectible is ERC721Enumerable, Ownable {
  using Strings for uint256;
  using Address for address;

  // Starting and stopping sale and presale
  bool public saleActive = false;
  bool public presaleActive = false;

  // Reserved for the team, customs, giveaways, collabs and so on.
  uint256 public reserved = 100;

  // Price of each token
  uint256 public price = 0.04 ether;

  // Maximum limit of tokens that can ever exist
  uint256 constant MAX_SUPPLY = 5000;

  // The base link that leads to the image / video of the token
  string public baseTokenURI;

  // List of addresses that have a number of reserved tokens for presale
  mapping(address => uint256) public whitelist;

  constructor() ERC721("Moo Moo", "MOO") {
  }

  // Override so the openzeppelin tokenURI() method will use this method to create the full tokenURI instead
  function _baseURI() internal view virtual override returns (string memory) {
    return baseTokenURI;
  }

  /**
   * @dev See {IERC721Metadata-tokenURI}.
   */
  function tokenURI(uint256 tokenId)
    public
    view
    virtual
    override
    returns (string memory)
  {
    require(
      _exists(tokenId),
      "ERC721Metadata: URI query for nonexistent token"
    );

    string memory baseURI = _baseURI();
    return
      bytes(baseURI).length > 0
        ? string(abi.encodePacked(baseURI, tokenId.toString()))
        : "";
  }


  // See which address owns which tokens
  function tokensOfOwner(address addr) public view returns (uint256[] memory) {
    uint256 tokenCount = balanceOf(addr);
    uint256[] memory tokensId = new uint256[](tokenCount);
    for (uint256 i; i < tokenCount; i++) {
      tokensId[i] = tokenOfOwnerByIndex(addr, i);
      }
    return tokensId;
  }

  // Whitelist Presale Minting Function
  function mintPresale(uint256 _amount) public payable {
    uint256 supply = totalSupply() + 1;
    uint256 reservedAmt = whitelist[msg.sender];
    require(presaleActive, "Presale isn't active");
    require(
      _amount > 0 && _amount <= 10,
      "Can only mint between 1 and 10 tokens at once"
    );
    require(_amount <= reservedAmt, "Can't mint more than reserved");
    require(supply + _amount <= MAX_SUPPLY, "Can't mint more than max supply");
    require(msg.value >= price * _amount, "Not enough ETH sent");
    whitelist[msg.sender] = reservedAmt - _amount;
    for (uint256 i; i < _amount; i++) {
      _safeMint(msg.sender, supply + i);
    }
  }

  // mint function
  function mint(uint256 _amount) public payable {
    uint256 supply = totalSupply() + 1;
    require(saleActive, "Sale isn't active");
    require(
      _amount > 0 && _amount <= 10,
      "Can only mint between 1 and 10 tokens at once"
    );
    require(supply + _amount <= MAX_SUPPLY, "Can't mint more than max supply");
    require(msg.value >= price * _amount, "Wrong amount of ETH sent"); // TODO: make price anything above the value 
    for (uint256 i; i < _amount; i++) {
      _safeMint(msg.sender, supply + i);
    }
  }

  // Admin minting function to reserve tokens for the team, collabs, customs and giveaways
  function mintReserved(uint256 _amount) public onlyOwner {
    // Limited to a publicly set amount
    require(_amount <= reserved, "Can't reserve more than set amount");
    reserved -= _amount;
    uint256 supply = totalSupply() + 1;
    for (uint256 i; i < _amount; i++) {
      _safeMint(msg.sender, supply + i);
    }
  }

  // edit whitelist
  function editWhiteList(address[] memory _a) public onlyOwner {
    for (uint256 i; i < _a.length; i++) {
      // gives each whitelisted address 10 mints
      whitelist[_a[i]] = 10;
    }
  }

  // Start and stop presale
  function setPresaleActive(bool val) public onlyOwner {
    presaleActive = val;
  }

  // Start and stop sale
  function setSaleActive(bool val) public onlyOwner {
    saleActive = val;
  }

  // Set new baseURI
  function setBaseURI(string memory baseURI) public onlyOwner {
    baseTokenURI = baseURI;
  }

  // Set a different price in case ETH changes drastically
  function setPrice(uint256 newPrice) public onlyOwner {
    price = newPrice;
  }

  // Withdraw funds from contract to the team wallet
  function withdraw(uint256 amount) public payable onlyOwner {
    require(payable(msg.sender).send(amount));
  }

}
function setPresaleActive(bool val) public onlyOwner {
  presaleActive = val;
}

// Start and stop presale