// Contract: SamuraiCats
// Target function: function tokenURI(uint256 tokenId)
 memory reason) {
                if (reason.length == 0) {
                    revert("ERC721: transfer to non ERC721Receiver implementer");
                } else {
                    assembly {
                        revert(add(32, reason), mload(reason))
                    }
                }
            }
        } else {
            return true;
        }
    }

    /**
     * @dev Hook that is called before any token transfer. This includes minting
     * and burning.
     *
     * Calling conditions:
     *
     * - When `from` and `to` are both non-zero, ``from``'s `tokenId` will be
     * transferred to `to`.
     * - When `from` is zero, `tokenId` will be minted for `to`.
     * - When `to` is zero, ``from``'s `tokenId` will be burned.
     * - `from` and `to` are never both zero.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 tokenId
    ) internal virtual {}
}

////// lib/openzeppelin-contracts/contracts/token/ERC721/extensions/IERC721Enumerable.sol

/* pragma solidity ^0.8.0; */

/* import "../IERC721.sol"; */

/**
 * @title ERC-721 Non-Fungible Token Standard, optional enumeration extension
 * @dev See https://eips.ethereum.org/EIPS/eip-721
 */
interface IERC721Enumerable is IERC721 {
    /**
     * @dev Returns the total amount of tokens stored by the contract.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns a token ID owned by `owner` at a given `index` of its token list.
     * Use along with {balanceOf} to enumerate all of ``owner``'s tokens.
     */
    function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256 tokenId);

    /**
     * @dev Returns a token ID at a given `index` of all the tokens stored by the contract.
     * Use along with {totalSupply} to enumerate all tokens.
     */
    function tokenByIndex(uint256 index) external view returns (uint256);
}

////// lib/openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721Enumerable.sol

/* pragma solidity ^0.8.0; */

/* import "../ERC721.sol"; */
/* import "./IERC721Enumerable.sol"; */

/**
 * @dev This implements an optional extension of {ERC721} defined in the EIP that adds
 * enumerability of all the token ids in the contract as well as all token ids owned by each
 * account.
 */
abstract contract ERC721Enumerable is ERC721, IERC721Enumerable {
    // Mapping from owner to list of owned token IDs
    mapping(address => mapping(uint256 => uint256)) private _ownedTokens;

    // Mapping from token ID to index of the owner tokens list
    mapping(uint256 => uint256) private _ownedTokensIndex;

    // Array with all token ids, used for enumeration
    uint256[] private _allTokens;

    // Mapping from token id to position in the allTokens array
    mapping(uint256 => uint256) private _allTokensIndex;

    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC721) returns (bool) {
        return interfaceId == type(IERC721Enumerable).interfaceId || super.supportsInterface(interfaceId);
    }

    /**
     * @dev See {IERC721Enumerable-tokenOfOwnerByIndex}.
     */
    function tokenOfOwnerByIndex(address owner, uint256 index) public view virtual override returns (uint256) {
        require(index < ERC721.balanceOf(owner), "ERC721Enumerable: owner index out of bounds");
        return _ownedTokens[owner][index];
    }

    /**
     * @dev See {IERC721Enumerable-totalSupply}.
     */
    function totalSupply() public view virtual override returns (uint256) {
        return _allTokens.length;
    }

    /**
     * @dev See {IERC721Enumerable-tokenByIndex}.
     */
    function tokenByIndex(uint256 index) public view virtual override returns (uint256) {
        require(index < ERC721Enumerable.totalSupply(), "ERC721Enumerable: global index out of bounds");
        return _allTokens[index];
    }

    /**
     * @dev Hook that is called before any token transfer. This includes minting
     * and burning.
     *
     * Calling conditions:
     *
     * - When `from` and `to` are both non-zero, ``from``'s `tokenId` will be
     * transferred to `to`.
     * - When `from` is zero, `tokenId` will be minted for `to`.
     * - When `to` is zero, ``from``'s `tokenId` will be burned.
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 tokenId
    ) internal virtual override {
        super._beforeTokenTransfer(from, to, tokenId);

        if (from == address(0)) {
            _addTokenToAllTokensEnumeration(tokenId);
        } else if (from != to) {
            _removeTokenFromOwnerEnumeration(from, tokenId);
        }
        if (to == address(0)) {
            _removeTokenFromAllTokensEnumeration(tokenId);
        } else if (to != from) {
            _addTokenToOwnerEnumeration(to, tokenId);
        }
    }

    /**
     * @dev Private function to add a token to this extension's ownership-tracking data structures.
     * @param to address representing the new owner of the given token ID
     * @param tokenId uint256 ID of the token to be added to the tokens list of the given address
     */
    function _addTokenToOwnerEnumeration(address to, uint256 tokenId) private {
        uint256 length = ERC721.balanceOf(to);
        _ownedTokens[to][length] = tokenId;
        _ownedTokensIndex[tokenId] = length;
    }

    /**
     * @dev Private function to add a token to this extension's token tracking data structures.
     * @param tokenId uint256 ID of the token to be added to the tokens list
     */
    function _addTokenToAllTokensEnumeration(uint256 tokenId) private {
        _allTokensIndex[tokenId] = _allTokens.length;
        _allTokens.push(tokenId);
    }

    /**
     * @dev Private function to remove a token from this extension's ownership-tracking data structures. Note that
     * while the token is not assigned a new owner, the `_ownedTokensIndex` mapping is _not_ updated: this allows for
     * gas optimizations e.g. when performing a transfer operation (avoiding double writes).
     * This has O(1) time complexity, but alters the order of the _ownedTokens array.
     * @param from address representing the previous owner of the given token ID
     * @param tokenId uint256 ID of the token to be removed from the tokens list of the given address
     */
    function _removeTokenFromOwnerEnumeration(address from, uint256 tokenId) private {
        // To prevent a gap in from's tokens array, we store the last token in the index of the token to delete, and
        // then delete the last slot (swap and pop).

        uint256 lastTokenIndex = ERC721.balanceOf(from) - 1;
        uint256 tokenIndex = _ownedTokensIndex[tokenId];

        // When the token to delete is the last token, the swap operation is unnecessary
        if (tokenIndex != lastTokenIndex) {
            uint256 lastTokenId = _ownedTokens[from][lastTokenIndex];

            _ownedTokens[from][tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token
            _ownedTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index
        }

        // This also deletes the contents at the last position of the array
        delete _ownedTokensIndex[tokenId];
        delete _ownedTokens[from][lastTokenIndex];
    }

    /**
     * @dev Private function to remove a token from this extension's token tracking data structures.
     * This has O(1) time complexity, but alters the order of the _allTokens array.
     * @param tokenId uint256 ID of the token to be removed from the tokens list
     */
    function _removeTokenFromAllTokensEnumeration(uint256 tokenId) private {
        // To prevent a gap in the tokens array, we store the last token in the index of the token to delete, and
        // then delete the last slot (swap and pop).

        uint256 lastTokenIndex = _allTokens.length - 1;
        uint256 tokenIndex = _allTokensIndex[tokenId];

        // When the token to delete is the last token, the swap operation is unnecessary. However, since this occurs so
        // rarely (when the last minted token is burnt) that we still do the swap here to avoid the gas cost of adding
        // an 'if' statement (like in _removeTokenFromOwnerEnumeration)
        uint256 lastTokenId = _allTokens[lastTokenIndex];

        _allTokens[tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token
        _allTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index

        // This also deletes the contents at the last position of the array
        delete _allTokensIndex[tokenId];
        _allTokens.pop();
    }
}

////// src/SamuraiCats.sol
/* pragma solidity ^0.8.10; */

/* import "@openzeppelin/contracts/access/Ownable.sol"; */
/* import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; */
/* import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; */

contract SamuraiCats is ERC721Enumerable, Ownable, ReentrancyGuard {
    uint256 public constant maxSupply = 4747;
    uint256 public privateSalePrice = 0.12 ether;
    uint256 public preSalePrice = 0.17 ether;
    uint256 public publicSalePrice = 0.17 ether;
    uint256 public maxTokensPerPrivateSaleTx = 20;
    uint256 public maxTokensPerPresaleTx = 4;
    uint256 public maxTokensPerPublicSaleTx = 5;

    /*
     *  Mint max supply :
     * -> Private sale : From 1 to 790
     * -> Pre-sale : From 791 to 1970
     * -> Public sale : From 1971 to 4747
     */
    uint256 public maxSupplyPrivateSale = 790;
    uint256 public maxSupplyPresale = 1970;

    string public baseUri;
    string public unrevealedTokenUri =
        "https://samuraicats.mypinata.cloud/ipfs/QmeXn7JZdyLzTud6qgGcDYFjMChJFqLG896yKswGxSBacM";
    string public contractUri =
        "https://samuraicats.mypinata.cloud/ipfs/QmQsDPxJZYoFFgtGqKgAxrJcDPnWkxQikKxxMPTudoFgGc";

    bool public isRevealed;

    enum SaleStatus {
        NotSale,
        PrivateSale,
        PreSale,
        FreeMint,
        PublicSale
    }
    SaleStatus public currentSaleStatus;

    mapping(address => bool) public privateSaleList;
    mapping(address => uint256) public freeMintList;
    mapping(address => bool) public privateSaleWalletTxStatus;

    /*///////////////////////////////////////////////////////////////
                            CONSTRUCTOR
    //////////////////////////////////////////////////////////////*/

    constructor() ERC721("SamuraiCats by Hiro Ando", "SCHA") {}

    /*///////////////////////////////////////////////////////////////
                            MINT FUNCTIONS
    //////////////////////////////////////////////////////////////*/

    function mintPrivateSale(uint256 numOfToken) external payable nonReentrant {
        require(privateSaleList[msg.sender], "not in private list");
        require(
            currentSaleStatus == SaleStatus.PrivateSale,
            "MINT: not in private sale"
        );
        require(
            !privateSaleWalletTxStatus[msg.sender],
            "MINT: you already done the private sale"
        );
        require(
            numOfToken <= maxTokensPerPrivateSaleTx,
            "MINT: exceed presale limit per transaction"
        );
        require(
            privateSalePrice * numOfToken <= msg.value,
            "MINT: insufficient ether"
        );
        require(
            totalSupply() + numOfToken <= maxSupplyPrivateSale,
            "MINT: reach limit"
        );

        _mintToken(msg.sender, numOfToken);
        privateSaleWalletTxStatus[msg.sender] = true;
    }

    function mintPreSale(uint256 numOfToken) external payable nonReentrant {
        require(
            currentSaleStatus == SaleStatus.PreSale,
            "MINT: not in pre-sale"
        );
        require(
            numOfToken <= maxTokensPerPresaleTx,
            "MINT: exceed presale limit per transaction"
        );
        require(
            preSalePrice * numOfToken <= msg.value,
            "MINT: insufficient ether"
        );

        // Bonus token (+ 1)
        if (numOfToken == maxTokensPerPresaleTx) {
            unchecked {
                numOfToken++;
            }
        }

        require(
            totalSupply() + numOfToken <= maxSupplyPresale,
            "MINT: reach limit"
        );

        _mintToken(msg.sender, numOfToken);
    }

    function freeMint(uint256 numOfToken) external nonReentrant {
        require(
            currentSaleStatus == SaleStatus.FreeMint,
            "MINT: not in free mint"
        );
        require(
            numOfToken <= freeMintList[msg.sender],
            "MINT: exceed free mint allocation"
        );
        require(totalSupply() + numOfToken <= maxSupply, "MINT: reach limit");
        _mintToken(msg.sender, numOfToken);

        freeMintList[msg.sender] -= numOfToken;
    }

    function mintToken(uint256 numOfToken) external payable nonReentrant {
        require(
            currentSaleStatus == SaleStatus.PublicSale,
            "MINT: not in public sale"
        );
        require(
            publicSalePrice * numOfToken <= msg.value,
            "MINT: insufficient ether"
        );
        require(
            numOfToken <= maxTokensPerPublicSaleTx,
            "MINT: exceed presale limit per transaction"
        );
        require(totalSupply() + numOfToken <= maxSupply, "MINT: reach limit");
        _mintToken(msg.sender, numOfToken);
    }

    function mintOwner(address to, uint256 numOfToken) external onlyOwner {
        require(totalSupply() + numOfToken <= maxSupply, "MINT: reach limit");
        _mintToken(to, numOfToken);
    }

    function _mintToken(address to, uint256 numOfToken) internal {
        for (uint256 i = 0; i < numOfToken; i++) {
            uint256 tokenId = totalSupply() + 1;
            _safeMint(to, tokenId);
        }
    }

    /*///////////////////////////////////////////////////////////////
                        FUNCTIONS SALE STATUS
    //////////////////////////////////////////////////////////////*/

    function setCurrentSaleStatus(SaleStatus _status) external onlyOwner {
        currentSaleStatus = _status;
    }

    /*///////////////////////////////////////////////////////////////
                      FUNCTIONS SALE LISTS
    //////////////////////////////////////////////////////////////*/

    function addToPrivateSaleList(address addr) external onlyOwner {
        privateSaleList[addr] = true;
    }

    function batchAddToPrivateSaleList(address[] memory addrs)
        external
        onlyOwner
    {
        uint256 length = addrs.length;
        for (uint256 i; i < length; ) {
            privateSaleList[addrs[i]] = true;
            unchecked {
                i++;
            }
        }
    }

    function addToFreeMintList(address addr, uint256 amount)
        external
        onlyOwner
    {
        freeMintList[addr] = amount;
    }

    function batchAddToFreeMintList(
        address[] memory addrs,
        uint256[] memory amounts
    ) external onlyOwner {
        require(addrs.length == amounts.length, "Length error");
        uint256 length = addrs.length;
        for (uint256 i; i < length; ) {
            freeMintList[addrs[i]] = amounts[i];
            unchecked {
                i++;
            }
        }
    }

    /*///////////////////////////////////////////////////////////////
                Max Tokens Per TX & Sale Price & Max Supply
    //////////////////////////////////////////////////////////////*/

    function setMaxTokensPerPresaleTx(uint256 _limit) external onlyOwner {
        maxTokensPerPresaleTx = _limit;
    }

    function setMaxTokensPerPrivateSaleTx(uint256 _limit) external onlyOwner {
        maxTokensPerPrivateSaleTx = _limit;
    }

    function setMaxTokensPerPublicSaleTx(uint256 _limit) external onlyOwner {
        maxTokensPerPublicSaleTx = _limit;
    }

    function setPrivateSalePrice(uint256 _price) external onlyOwner {
        privateSalePrice = _price;
    }

    function setPreSalePrice(uint256 _price) external onlyOwner {
        preSalePrice = _price;
    }

    function setPublicSalePrice(uint256 _price) external onlyOwner {
        publicSalePrice = _price;
    }

    function setMaxSupplyPrivateSale(uint256 _maxSupplyPrivateSale)
        external
        onlyOwner
    {
        maxSupplyPrivateSale = _maxSupplyPrivateSale;
    }

    function setMaxSupplyPresale(uint256 _maxSupplyPresale) external onlyOwner {
        maxSupplyPresale = _maxSupplyPresale;
    }

    /*///////////////////////////////////////////////////////////////
                          URI/REVEAL FUNCTIONS