contract ABTF is Ownable, ERC721Enumerable, ReentrancyGuard {
    using SafeMath for uint256;
    string public ABTF_PROVENANCE = "";
    string public BASE_URI = "";

    uint256[] public prices = [0.035 ether, 0.035 ether, 0.035 ether, 0.035 ether];
    uint256[] public reserveNumbers = [166, 100, 100, 100];
    uint256[] public maxTokens = [626, 500, 500, 500];
    uint256[] public purchaseLimit = [1000, 1000, 1000, 1000];
    uint256[] public totalReserved = [0, 0, 0, 0];
    uint256[] public totalMinted = [0, 0, 0, 0];
    bool[] public saleActive = [false, false, false, false];

    constructor() ERC721("A Bug Travels Far", "ABTF") {}

    function withdraw() public onlyOwner {
        address payable to = payable(msg.sender);
        (bool success, ) = to.call{value: address(this).balance}("");
        require(success, "Transfer failed.");
    }

    function flipSaleState(uint256 bookNum) public onlyOwner {
        saleActive[bookNum] = !saleActive[bookNum];
    }

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

    function setBaseURI(string memory baseURI_) public onlyOwner {
        BASE_URI = baseURI_;
    }

    function setProvenanceHash(string memory provenanceHash) public onlyOwner {
        ABTF_PROVENANCE = provenanceHash;
    }

    // ------------------------

    function setPrice(uint256 _bookNum, uint256 _price) public onlyOwner {
        prices[_bookNum] = _price;
    }

    function setReserveNumber(uint256 _bookNum, uint256 _reserveNumber) public onlyOwner {
        reserveNumbers[_bookNum] = _reserveNumber;
    }

    function setMaxTokens(uint256 _bookNum, uint256 _maxTokens) public onlyOwner {
        maxTokens[_bookNum] = _maxTokens;
    }

    function setPurchaseLimit(uint256 _bookNum, uint256 _purchaseLimit) public onlyOwner {
        purchaseLimit[_bookNum] = _purchaseLimit;
    }

    // ------------------------

    function purchase(uint256 numberOfTokens, uint256 _bookNum) public payable nonReentrant {
        require(_bookNum >= 0 && _bookNum <= 3, "Must enter a book number");
        require(saleActive[_bookNum], "Sale for this book is not active");
        require(totalMinted[_bookNum] <= maxTokens[_bookNum], "All tokens have been minted for this book");
        require(totalMinted[_bookNum].add(numberOfTokens) <= maxTokens[_bookNum], "Would exceed total supply for this book");
        require(numberOfTokens > 0 && numberOfTokens <= purchaseLimit[_bookNum],"Would exceed purchase limit for this book");
        require(msg.value / numberOfTokens == prices[_bookNum], "Eth value is not good");

        for (uint256 i = 0; i < numberOfTokens; i++) {
            if (totalMinted[_bookNum] < maxTokens[_bookNum]) {
                uint256 tokenId = 0;
                if(_bookNum == 0) {
                    tokenId = reserveNumbers[_bookNum] + totalMinted[_bookNum];
                } else {
                    for (uint256 j = 0; j < _bookNum; j++) {
                        tokenId += maxTokens[j];
                    }
                    tokenId += reserveNumbers[_bookNum] + totalMinted[_bookNum];
                }
                // uint256 tokenId = totalMinted[_bookNum];
                totalMinted[_bookNum] += 1;
                _safeMint(msg.sender, tokenId);
            }
        }
    }

    function reserve(address _to, uint256 _reserveAmount, uint256 _bookNum) public onlyOwner {
        require(_reserveAmount > 0, "Must enter a number to reserve");
        require(_bookNum >= 0 && _bookNum <= 3, "Must enter a book number");
        require(totalReserved[_bookNum].add(_reserveAmount) <= reserveNumbers[_bookNum], "Not enough reserve left for this book");
        require(totalMinted[_bookNum].add(totalReserved[_bookNum]).add(_reserveAmount) <= maxTokens[_bookNum], "All tokens for this book have been minted");

        for (uint256 i = 0; i < _reserveAmount; i++) {
            uint256 tokenId = 0;
            if(_bookNum == 0) {
                tokenId = totalReserved[_bookNum];
            } else {
                for (uint256 j = 0; j < _bookNum; j++) {
                    tokenId += maxTokens[j];
                }
                tokenId += totalReserved[_bookNum];
            }
            totalReserved[_bookNum] += 1;
            _safeMint(_to, tokenId);
        }

        // reserveNumbers[_bookNum] = reserveNumbers[_bookNum].sub(_reserveAmount);
    }

    function tokensOfOwner(address _owner) external view returns (uint256[] memory)
    {
        uint256 tokenCount = balanceOf(_owner);
        if (tokenCount == 0) {
            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;
        }
    }
}
function purchase(uint256 numberOfTokens, uint256 _bookNum) public payable nonReentrant {
    require(_bookNum >= 0 && _bookNum <= 3, "Must enter a book number");
    require(saleActive[_bookNum], "Sale for this book is not active");
    require(totalMinted[_bookNum] <= maxTokens[_bookNum], "All tokens have been minted for this book");
    require(totalMinted[_bookNum].add(numberOfTokens) <= maxTokens[_bookNum], "Would exceed total supply for this book");
    require(numberOfTokens > 0 && numberOfTokens <= purchaseLimit[_bookNum],"Would exceed purchase limit for this book");
    require(msg.value / numberOfTokens == prices[_bookNum], "Eth value is not good");

    for (uint256 i = 0; i < numberOfTokens; i++) {
        if (totalMinted[_bookNum] < maxTokens[_bookNum]) {
            uint256 tokenId = 0;
            if(_bookNum == 0) {
                tokenId = reserveNumbers[_bookNum] + totalMinted[_bookNum];
            } else {
                for (uint256 j = 0; j < _bookNum; j++) {
                    tokenId += maxTokens[j];
                }
                tokenId += reserveNumbers[_bookNum] + totalMinted[_bookNum];
            }
            // uint256 tokenId = totalMinted[_bookNum];
            totalMinted[_bookNum] += 1;
            _safeMint(msg.sender, tokenId);
        }
    }
}

// ------------------------