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

    uint256 public constant NFT_PRICE = 40000000000000000; // 0.04 ETH
    uint public constant MAX_NFT_PURCHASE = 10;
    uint public constant MAX_NFT_PURCHASE_PRESALE = 3;
    uint256 public MAX_SUPPLY = 10000;
    bool public saleIsActive = false;
    bool public presaleIsActive = false;
    bool public isMetadataLocked = false;
    uint256 public boarReserve = 100;

    // Faciliating the needed functionality for the presale
    mapping(address => bool) _allowList;
    mapping(address => uint256) _allowListClaimed;

    string private _baseURIExtended;
    mapping (uint256 => string) _tokenURIs;

    constructor() ERC721("Angry Boars","BOARS"){
        
    }
    
    // Presale functions
    function addToWhitelist(address[] memory addresses) public onlyOwner {
        for (uint256 i = 0; i < addresses.length; i++) {
          require(addresses[i] != address(0), "Can't add the null address");
        
          _allowList[addresses[i]] = true;
          _allowListClaimed[addresses[i]] > 0 ? _allowListClaimed[addresses[i]] : 0;
        }
    }
    
    function isWhitelisted (address addr) public view returns (bool) {
        return _allowList[addr];
    }    
    
    function removeFromAllowList(address[] calldata addresses) external onlyOwner {
        for (uint256 i = 0; i < addresses.length; i++) {
          require(addresses[i] != address(0), "Can't add the null address");
          _allowList[addresses[i]] = false;
        }
    }    
    
    function allowListClaimedBy(address owner) external view returns (uint256){
        require(owner != address(0), 'Zero address not on Allow List');
        
        return _allowListClaimed[owner];
    }    

    function flipSaleState() public onlyOwner {
        saleIsActive = !saleIsActive;
    }
    
    function lockMetadata() public onlyOwner {
        isMetadataLocked = true;
    }
    
    function flipPresaleState() public onlyOwner {
        presaleIsActive = !presaleIsActive;
    }

    function withdraw() public onlyOwner {
        uint balance = address(this).balance;
        payable(msg.sender).transfer(balance);
    }

    function reserveBoars(address _to, uint256 _reserveAmount)
        public
        onlyOwner
    {
        uint256 supply = totalSupply();
        require(
            _reserveAmount > 0 && _reserveAmount <= boarReserve,
            "Not enough reserve left for team"
        );
        for (uint256 i = 0; i < _reserveAmount; i++) {
            _safeMint(_to, supply + i);
        }
        boarReserve = boarReserve.sub(_reserveAmount);
    }

    function mintBoar(uint numberOfTokens) public payable {
        require(saleIsActive, "Sale is not active at the moment");
        require(numberOfTokens > 0, "Number of tokens can not be less than or equal to 0");
        require(totalSupply().add(numberOfTokens) <= MAX_SUPPLY.sub(boarReserve), "Purchase would exceed max supply of Boars");
        require(numberOfTokens <= MAX_NFT_PURCHASE,"Can only mint up to 15 per purchase");
        require(NFT_PRICE.mul(numberOfTokens) <= msg.value, "Sent ether value is incorrect");

        for (uint i = 0; i < numberOfTokens; i++) {
            _safeMint(msg.sender, totalSupply());
        }
    }
    
    function mintBoarPresale(uint numberOfTokens) public payable {
        require(presaleIsActive, "Presale is not active at the moment");
        require(_allowList[msg.sender] == true,"This address is not whitelisted for the presale.");
        require(_allowListClaimed[msg.sender] + numberOfTokens <= 3,"Exceeds supply of presale Boars you can mint.");
        require(numberOfTokens > 0, "Number of tokens can not be less than or equal to 0");
        require(NFT_PRICE.mul(numberOfTokens) <= msg.value, "Sent ether value is incorrect");
        
        for (uint i = 0; i < numberOfTokens; i++) {
            _safeMint(msg.sender,  totalSupply());
        }
        
        
        _allowListClaimed[msg.sender] += numberOfTokens;
    }

    function _baseURI() internal view virtual override returns (string memory) {
        return _baseURIExtended;
    }

    function setBaseURI(string memory baseURI_) external onlyOwner {
        require(!isMetadataLocked,"Metadata is locked");
        _baseURIExtended = baseURI_;
    }

    function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
        require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");

        string memory _tokenURI = _tokenURIs[tokenId];
        string memory base = _baseURI();

        // If there is no base URI, return the token URI.
        if (bytes(base).length == 0) {
            return _tokenURI;
        }
        // If both are set, concatenate the baseURI and tokenURI (via abi.encodePacked).
        if (bytes(_tokenURI).length > 0) {
            return string(abi.encodePacked(base, _tokenURI, '.json'));
        }
        // If there is a baseURI but no tokenURI, concatenate the tokenID to the baseURI.
        return string(abi.encodePacked(base, tokenId.toString(), '.json'));
    }
}
function addToWhitelist(address[] memory addresses) public onlyOwner {
        for (uint256 i = 0; i < addresses.length; i++) {
          require(addresses[i] != address(0), "Can't add the null address");
        
          _allowList[addresses[i]] = true;
          _allowListClaimed[addresses[i]] > 0 ? _allowListClaimed[addresses[i]] : 0;
        }
    }
// Presale functions