contract AGE is ERC721, ERC721Enumerable, Pausable, Ownable {

    using Counters for Counters.Counter;

    uint public constant AGE_PRICE = 0.1 ether;

    uint public constant AGE_PREPRICE = 0.088 ether;

    uint public constant MAX_AGE_PURCHASE = 20;

    uint public constant MAX_AGES = 10000;

    //Address => Stage => tokenIDs
    mapping(address => mapping(uint => uint[])) public mintRecords;

    //Stage => merkle proof
    mapping(uint => bytes32) public preMintRoot;

    //pre mint stage, zero is public mint
    uint public preStage = 1;

    bool public preSaleActive = true;

    //Get startingIndex from community vote
    uint public startingIndex = 0;

    address public hostAddress;

    Counters.Counter private _tokenIdCounter;

    GuardVault private guardVault;

    // Base URI
    string private _baseURIextended;

    constructor(string memory name, string memory symbol, address host, string memory uri) ERC721(name, symbol) {
        hostAddress = host;
        _baseURIextended = uri;
    }

    /**
    * Mints Guardians
    */
    function mintAge(uint numberOfTokens) public payable whenNotPaused {
        require(numberOfTokens <= MAX_AGE_PURCHASE, "Can only mint 20 tokens at a time.");
        require(totalSupply() + numberOfTokens <= MAX_AGES, "Purchase would exceed max supply of Guardians.");
        require(AGE_PRICE * numberOfTokens <= msg.value, "Ether value sent is not correct.");
        require(!preSaleActive, "Now is pre mint.");

        uint vaultValue = msg.value / 10 / numberOfTokens;
        require(vaultValue >= 0.01 ether, "Ether value for vault is not enough.");

        for (uint i = 0; i < numberOfTokens; i++) {
            _safeMint(msg.sender, _tokenIdCounter.current());
            guardVault.initGuardianVault{value : vaultValue}(_tokenIdCounter.current(), vaultValue);
            mintRecords[_msgSender()][0].push(_tokenIdCounter.current());
            _tokenIdCounter.increment();
        }
    }

    function preMintAge(
        uint numberOfTokens,
        uint maxNumber,
        bytes32[] memory proof) public payable whenNotPaused {
        require(totalSupply() + numberOfTokens <= MAX_AGES, "Purchase would exceed max supply of Guardians.");
        require(preSaleActive && preStage > 0, "Pre mint is not active.");
        require(msg.value >= AGE_PREPRICE * numberOfTokens, "Ether value for vault is not enough.");

        uint mintedNum = mintRecords[_msgSender()][preStage].length;
        require(mintedNum + numberOfTokens <= maxNumber, "Pre mint already the max purchase.");

        bytes32 root = preMintRoot[preStage];
        bytes32 node = keccak256(abi.encodePacked(_msgSender(), maxNumber));

        require(MerkleProof.verify(proof, root, node), "You are not in pre mint list.");

        uint vaultValue = 0.01 ether;

        for (uint i = 0; i < numberOfTokens; i++) {
            _safeMint(_msgSender(), _tokenIdCounter.current());
            guardVault.initGuardianVault{value : vaultValue}(_tokenIdCounter.current(), vaultValue);
            mintRecords[_msgSender()][preStage].push(_tokenIdCounter.current());
            _tokenIdCounter.increment();
        }
    }

    /**
    * Reserve Guardians for community
    */
    function reserveAge(uint numberOfTokens, address to) public onlyOwner payable {
        require(numberOfTokens <= MAX_AGE_PURCHASE, "Can only mint 20 tokens at a time.");
        require(AGE_PRICE * numberOfTokens / 10 <= msg.value, "Not enough Ether to mint.");

        uint vaultsValue = msg.value / numberOfTokens;
        for (uint i = 0; i < numberOfTokens; i++) {
            _safeMint(to, _tokenIdCounter.current());
            guardVault.initGuardianVault{value : vaultsValue}(_tokenIdCounter.current(), vaultsValue);
            _tokenIdCounter.increment();
        }
    }

    /**
     * Set the starting index for the AGE
     */
    function setStartingIndex(uint index) public onlyOwner {
        require(startingIndex == 0, "Starting index is already set.");
        startingIndex = index;
    }

    function burn(uint tokenId) external {
        require(startingIndex != 0, "ETH need Guardians.");
        require(tx.origin == ownerOf(tokenId), "Only owner can burn token.");
        require(_msgSender() == address(guardVault), "Only vault can burn token.");
        _burn(tokenId);
    }

    // The following functions are overrides required by Solidity.
    function _burn(uint tokenId) internal override(ERC721) {
        require(_exists(tokenId), "Token must exists.");
        super._burn(tokenId);
    }

    function setStageProof(uint stage, bytes32 proof) public onlyOwner {
        preMintRoot[stage] = proof;
    }

    function getMintRecord(address minter, uint stage) public view returns (uint[] memory) {
        return mintRecords[minter][stage];
    }

    function setHost(address host) public onlyOwner {
        hostAddress = host;
    }

    function claim() public payable onlyOwner {
        require(hostAddress != address(0), "Host address is zero.");
        payable(hostAddress).transfer(address(this).balance);
    }

    function setBaseURI(string memory baseURI_) external onlyOwner {
        _baseURIextended = baseURI_;
    }

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


    function flipPreSaleState() public onlyOwner {
        preSaleActive = !preSaleActive;
    }

    function setPreMintStage(uint nextStage) public onlyOwner {
        require(nextStage > preStage, "Set pre mint stage incorrect.");
        preStage = nextStage;
    }

    function setVault(address vaultAddress) public onlyOwner {
        guardVault = GuardVault(vaultAddress);
    }

    function getVaultAddress() public view returns (address) {
        return address(guardVault);
    }

    function pause() public onlyOwner {
        _pause();
    }

    function unpause() public onlyOwner {
        _unpause();
    }

    function _beforeTokenTransfer(address from, address to, uint tokenId)
    internal
    override(ERC721, ERC721Enumerable)
    {
        super._beforeTokenTransfer(from, to, tokenId);
    }

    function tokenURI(uint tokenId)
    public
    view
    override(ERC721)
    returns (string memory)
    {
        return super.tokenURI(tokenId);
    }

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

    receive() external payable virtual {
    }

    fallback() external payable virtual {
    }

}
// import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
// import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
// import "@openzeppelin/contracts/security/Pausable.sol";
// import "@openzeppelin/contracts/access/Ownable.sol";
// import "@openzeppelin/contracts/utils/Counters.sol";
// import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
// import "contracts/GuardVault.sol";
function mintAge(uint numberOfTokens) public payable whenNotPaused {
    require(numberOfTokens <= MAX_AGE_PURCHASE, "Can only mint 20 tokens at a time.");
    require(totalSupply() + numberOfTokens <= MAX_AGES, "Purchase would exceed max supply of Guardians.");
    require(AGE_PRICE * numberOfTokens <= msg.value, "Ether value sent is not correct.");
    require(!preSaleActive, "Now is pre mint.");

    uint vaultValue = msg.value / 10 / numberOfTokens;
    require(vaultValue >= 0.01 ether, "Ether value for vault is not enough.");

    for (uint i = 0; i < numberOfTokens; i++) {
        _safeMint(msg.sender, _tokenIdCounter.current());
        guardVault.initGuardianVault{value : vaultValue}(_tokenIdCounter.current(), vaultValue);
        mintRecords[_msgSender()][0].push(_tokenIdCounter.current());
        _tokenIdCounter.increment();
    }
}

/**
 * Mints Guardians
 */