contract KryptoKatanas is ERC721A, Ownable, IBitCitizenCollection {

    // Token Info
    uint256 public constant MAX_TOKEN = 3333;
    uint256 public constant MAX_PRESALE_TOKEN = 999;
    uint256 private constant INIT_RESERVED = 33;
    uint256 public constant tokenPrice = 0.07 ether;

    // Minting
    uint public constant maxMintPublic = 12;
    uint public constant maxMintPresale = 6;
    bool public isSaleActive = false;
    bool public isPrivateSaleActive = false;

    // Not so hidden password ;)
    bytes32 premintPassword = 0x2e9a250122e5da7faabc65a0079636edc9df2b41ddecd61aae326e345253ca99;

    // Launch
    bool public isRevealed;
    string public CollectionProvenance = "";

    // Base URI
    string private _baseTokenURI;
    string private _unrevealedUri;
    string private _baseExtension = ".json";

    string private _collectionURI;

    // === Construction ============================================================

    constructor(string memory provenanceHash, string memory baseTokenURI, string memory unrevealedMetaUri) ERC721A("KryptoKatanas", "KRYPTOKATANAS") {
        // reserve 1/1s for promotions
        reserveToken(INIT_RESERVED);
        // Set data
        CollectionProvenance = provenanceHash;
        _baseTokenURI = baseTokenURI;
        _unrevealedUri = unrevealedMetaUri;
    }

    // === Owner Methods ============================================================

    /// Withdraw the funds
    function withdraw() public onlyOwner {
        (bool success, ) = payable(owner()).call{value: address(this).balance}("");
        require(success, "Transfer failed.");
    }

    /// Reserve some tokens for founder(s) and promotions
    function reserveToken(uint numberOfTokens) public onlyOwner { 
        require((totalSupply() + numberOfTokens) <= MAX_TOKEN, "Number of tokens requested are not available");
        _safeMint(msg.sender, numberOfTokens);
    }

    /// Set the provenance. This is calculated and set before the presale.
    function setProvenanceHash(string calldata provenanceHash) public onlyOwner {
        CollectionProvenance = provenanceHash;
    }

    /// Set the base uri for the collection
    function setBaseURI(string calldata baseTokenURI) external onlyOwner() {
        _baseTokenURI = baseTokenURI;
    }

    function setUnrevealedURI(string calldata unrevealedMetaUri) external onlyOwner(){
        _unrevealedUri = unrevealedMetaUri;
    }

    function setCollectionURI(string calldata collectionURI) external onlyOwner(){
        _collectionURI = collectionURI;
    }

    /// Get the collection meta data uri
    function getCollectionURI() external view override returns (string memory) {
        return _collectionURI;
    }
        
    /// Get the base uri for the collection
    function _baseURI() internal view virtual override returns (string memory) {
        return _baseTokenURI;
    }
    
    /// Get the full uri to get the token meta data
    function tokenURI(uint256 tokenId) public view virtual override returns (string memory)
    {
        require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");
        
        if(!isRevealed) {
            return _unrevealedUri;
        }

        string memory currentBaseURI = _baseURI();
        return bytes(currentBaseURI).length > 0
            ? string(abi.encodePacked(currentBaseURI, Strings.toString(tokenId+1), _baseExtension))
            : "";
    }

    /// Activate/Deactivate the sale
    function flipSaleState() public onlyOwner returns (bool) {
        isSaleActive = !isSaleActive;
        return  isSaleActive;
    }

    /// Set whether the sale is for the whitelist only (private sale)
    function flipPrivateSaleState() public onlyOwner returns (bool) {
        isPrivateSaleActive = !isPrivateSaleActive;
        return  isPrivateSaleActive;
    }

    /// Reveal the collection to the owners
    function revealCollection() public onlyOwner {
        isRevealed = true;
    }


    /// Mint a new Katana(s)
    function mintToken(uint numberOfTokens) public payable {
        require(isSaleActive, "Sale is not active");
        require((totalSupply() + numberOfTokens) <= MAX_TOKEN, "Number of tokens requested are not available");
        require((tokenPrice * numberOfTokens) <= msg.value, "Not enough ether value sent");
        
        require(numberOfTokens <= maxMintPublic, "Number of tokens requested exceeds the amount allowed per transaction. See 'maxMintPublic'");
        
        // mint the tokens
        _safeMint(msg.sender, numberOfTokens);
        
    }

    /// Mint a new Katana(s) during presale
    function premintToken(uint numberOfTokens, string calldata password) public payable {
        require(isSaleActive, "Sale is not active");
        require(isPrivateSaleActive, "presale is not active");
        require(keccak256(abi.encodePacked(password)) == premintPassword, "Premint password is not correct.");
        require((totalSupply() + numberOfTokens) <= MAX_PRESALE_TOKEN, "Number of tokens requested are not available");
        require((tokenPrice * numberOfTokens) <= msg.value, "Not enough ether value sent");

        require((balanceOf(msg.sender) + numberOfTokens) <= maxMintPresale, "Number of tokens requested exceeds the amount allowed during the presale.");
        require(numberOfTokens <= maxMintPresale, "Number of tokens requested exceeds the amount allowed during the presale. See 'maxMintPresale'");

        // mint the tokens
        _safeMint(msg.sender, numberOfTokens);
    }

}
function revealCollection() public onlyOwner {
    isRevealed = true;
}

/// Reveal the collection to the owners