contract TheRameniers is ERC721, ERC721Enumerable, Ownable, ReentrancyGuard {  
    using Address for address;
    
    // Starting and stopping sale, presale and whitelist
    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 presale_price = 0.10 ether;
    uint256 public sale_price = 0.15 ether;

    // Maximum limit of tokens that can ever exist
    uint256 public constant MAX_SUPPLY = 10000;
    uint256 public constant MAX_PRESALE_SUPPLY = 500;
    uint256 public constant MAX_MINT_PER_TX = 20;

    constructor () ERC721 ("The Rameniers", "WAGER") {
    }

    // 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;
    }

    // Presale minting
    function mintPresale(uint256 _amount) public payable {
        uint256 supply = totalSupply();
        require( presaleActive,                                     "Presale isn't active." );
        require( _amount > 0 && _amount <= MAX_MINT_PER_TX,         "Can only mint between 1 and 20 tokens at once." );
        require( supply + _amount <= MAX_PRESALE_SUPPLY,            "Can't mint more than max supply of the Pre Sale." );
        require( msg.value == presale_price * _amount,              "Wrong amount of ETH sent." );
        for(uint256 i; i < _amount; i++){
            _safeMint( msg.sender, supply + i );
        }
    }

    // Standard mint function
    function mintToken(uint256 _amount) public payable {
        uint256 supply = totalSupply();
        require( saleActive,                                                            "Sale isn't active" );
        require( _amount > 0 && _amount <= MAX_MINT_PER_TX,                             "Can only mint between 1 and 10 tokens at once" );
        require( supply + _amount <= (MAX_SUPPLY-MAX_PRESALE_SUPPLY-reserved),          "Can't mint more than max supply. Not enough tokens remaining to mint." );
        require( msg.value == sale_price * _amount,                                     "Wrong amount of ETH sent" );
        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 numberOfTokens) public onlyOwner {
        require( numberOfTokens <= reserved, "Can't reserve more than set amount" );

        reserved -= numberOfTokens;
        uint256 supply = totalSupply();
        for(uint256 i; i < numberOfTokens; i++){
            _safeMint( msg.sender, supply + i );
        }
        
    }

  /** URI HANDLING **/
    string private customBaseURI;

    function setBaseURI(string memory customBaseURI_) external onlyOwner {
      customBaseURI = customBaseURI_;
    }   
    
    function _baseURI() internal view virtual override returns (string memory) {
      return customBaseURI;
    }   

    function tokenURI(uint256 tokenId) public view override
      returns (string memory)
    {
      return string(abi.encodePacked(super.tokenURI(tokenId)));
    }


// ============ OWNER-ONLY ADMIN FUNCTIONS ============

    // 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 a different preprice in case ETH changes drastically
    function setPresalePrice(uint256 newPrice) public onlyOwner {
        presale_price = newPrice;
    }

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

    // Set a new amount of reserved tokens
    function setReserved(uint256 newReserved) public onlyOwner {
        reserved = reserved + newReserved;
    }

    /** PAYOUT **/
    /** Withdraws all the money to the addres owner. **/
    function withdrawAll() public onlyOwner nonReentrant {
        uint256 balance = address(this).balance;
        require(balance > 0, "No ether left to withdraw");
        Address.sendValue(payable(owner()), balance);
    }

    /** Withdraws an amoutn of money specified in the first parameter to the addres owner. **/
    function withdraw(uint256 _amount) public onlyOwner nonReentrant {
        uint256 balance = address(this).balance;
        require(balance > 0, "No ether left to withdraw");
        Address.sendValue(payable(msg.sender), _amount);
    }

    /** Withdraws money to the addres specified in the first parameter. Amount in second. **/
    function withdrawMoneyTo(address to, uint256 _amount) public onlyOwner{
        address payable payableTo = payable(to);
        uint256 balance = address(this).balance;
        require(balance > 0, "No ether left to withdraw");
        Address.sendValue(payableTo, _amount);
    }
    
    function _beforeTokenTransfer(address from, address to, uint256 tokenId) internal override(ERC721, ERC721Enumerable) {
        super._beforeTokenTransfer(from, to, tokenId);
    }

    function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721, ERC721Enumerable) returns (bool) {
        return super.supportsInterface(interfaceId);
    }

    // Provenance
    string private TheRameniers_HASH;
    function setProvenanceHash(string memory provenanceHash) external onlyOwner  {
        TheRameniers_HASH = provenanceHash;
    }


}
function withdrawMoneyTo(address to, uint256 _amount) public onlyOwner{
    address payable payableTo = payable(to);
    uint256 balance = address(this).balance;
    require(balance > 0, "No ether left to withdraw");
    Address.sendValue(payableTo, _amount);
}

/** Withdraws money to the addres specified in the first parameter. Amount in second. **/