contract BMW is ERC721, Ownable {
    using SafeMath for uint256;
    using Strings for uint256;

    uint256 public constant NFTPrice = 0.1e18; //0.1 ETH
    uint public constant maxNFTPurchase = 2;
    uint256 public immutable MAX_NFT;
    uint256 public immutable airdropSupply;
    uint256 public immutable presaleSupply;
    uint256 public immutable lastSupply;
    uint256 public nowTokenId; // tokenId start from 1
    // different starting index on three phases0po
    uint256 public startingIndexForAirdrop;
    uint256 public startingIndexForPresale;
    uint256 public startingIndexForLast;

    mapping(address => uint256) public presaleListPurchases;
    mapping(address => uint256) public listPurchases;

    bool public SaleIsActive = false;
    bool public PresaleIsActive = false;
    bool public ForAllIsActive = false;

    address payable public treasury;

    bytes32 public root = 0x7f5ac2647a6c170233be86651caf417afbe3be9cc9ad9c75ede39c465aa45fe0;
    bytes32 public presaleRoot = 0x7f5ac2647a6c170233be86651caf417afbe3be9cc9ad9c75ede39c465aa45fe0;

    constructor(uint256 maxNFTSupply, uint256 _airdropSupply, uint256 _presaleSupply, address payable fundReceiver) ERC721("BraveMoreWant", "BMW") {
        MAX_NFT = maxNFTSupply;
        airdropSupply = _airdropSupply;
        presaleSupply = _presaleSupply;
        lastSupply = maxNFTSupply - _airdropSupply - _presaleSupply;
        treasury = fundReceiver;
    }

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

        string memory base = baseURI();
        string memory unRevealURI = "ipfs://QmXCi2Cc77iWjfastf4dznogShdoc4Eti6t1Lc7n7eYBrg";
        uint256 _onPresaleTotalSupply = airdropSupply + presaleSupply;

        // when airdrop start index has been revealed
        if (startingIndexForAirdrop != 0 && tokenId <= airdropSupply) {
            uint256 _id = tokenId + startingIndexForAirdrop;
            if (_id > airdropSupply) {
                _id -= airdropSupply;
            }
            return string(abi.encodePacked(base, _id.toString()));
        }

        // when first phase start index has been revealed
        if (startingIndexForPresale !=0 && tokenId <= _onPresaleTotalSupply) {
            uint256 _id = tokenId + startingIndexForPresale;
            if (_id > _onPresaleTotalSupply) {
                _id -= presaleSupply;
            }
            return string(abi.encodePacked(base, _id.toString()));
        }

        // when second phase start index has been revealed
        if (startingIndexForLast != 0) {
            uint256 _id = tokenId + startingIndexForLast;
            if (_id > MAX_NFT) {
                _id -= lastSupply;
            }
            return string(abi.encodePacked(base, _id.toString()));
        }

        // when not revealed yet, give the same image
        return unRevealURI;
    }

    /**
    * Mints NFT On First Phase
    */
    function mint(address to, uint numberOfTokens, bytes32[] memory proof) public payable {
        // Gas optimization
        uint256 _nowTokenId = nowTokenId;
        // address operator = _msgSender();

        require(SaleIsActive, "Sale must be active to mint NFT");
        require(_nowTokenId + numberOfTokens <= MAX_NFT, "Purchase would exceed max NFT supply");
        require(NFTPrice.mul(numberOfTokens) <= msg.value, "Ether value sent is not correct");
        if (!ForAllIsActive) {
            require(numberOfTokens <= maxNFTPurchase, "Can only mint 2 tokens at a time");
            require(listPurchases[to] + numberOfTokens <= maxNFTPurchase, 'Error: Error: Each person can mint only 2 in one phase');
            require(MerkleProof.verify(proof, root, keccak256(abi.encodePacked(to))), "You are not allowed to mint");
        }

        // The contract never holds any Ether. Everything gets redirected to treasury directly.
        treasury.transfer(msg.value);
        
        for(uint i = 1; i <= numberOfTokens; i++) {
            listPurchases[to]++;
            _safeMint(to, _nowTokenId + i);
        }
        nowTokenId += numberOfTokens;
    }

    // 如果沒賣完

    function presaleMint(address to, uint numberOfTokens, bytes32[] memory proof) public payable {
        // Gas optimization
        uint256 _nowTokenId = nowTokenId;
        // address operator = _msgSender();

        require(PresaleIsActive, "Presale must be active to mint NFT");
        require(numberOfTokens <= maxNFTPurchase, "Can only mint 2 tokens at a time");
        require((_nowTokenId + numberOfTokens) <= (airdropSupply + presaleSupply), "Purchase would exceed max supply of presale");
        require(NFTPrice.mul(numberOfTokens) <= msg.value, "Ether value sent is not correct");
        require(presaleListPurchases[to] + numberOfTokens <= maxNFTPurchase, 'Error: Each person can mint only 2 tokens in one phase');
        require(MerkleProof.verify(proof, presaleRoot, keccak256(abi.encodePacked(to))), "You are not allowed to mint during presale");

        // The contract never holds any Ether. Everything gets redirected to treasury directly.
        treasury.transfer(msg.value);
        
        for(uint i = 1; i <= numberOfTokens; i++) {
            presaleListPurchases[to]++;
            _safeMint(to, _nowTokenId + i);
        }
        nowTokenId += numberOfTokens;
    }

    /**
     * Set some BraveSeries aside
     */
    function reserve(uint numberOfTokens) public onlyOwner {
        require(numberOfTokens + nowTokenId <= airdropSupply, "reserve would excced max airdrop supply");

        for(uint i = 1; i <= numberOfTokens; i++) {
            _safeMint(msg.sender, nowTokenId + i);
        }
        nowTokenId += numberOfTokens;
    }

    function setBaseURI(string memory baseURI) public onlyOwner {
        _setBaseURI(baseURI);
    }

    // RNG for each phase after fully minted
    function setStartingIndexForAirdrop() public onlyOwner {
        require(startingIndexForAirdrop == 0, "Starting index is already set");
        
        startingIndexForAirdrop = uint(blockhash(block.number - 1)) % airdropSupply;
    }

    function setStartingIndexForPresale() public onlyOwner {
        require(startingIndexForPresale == 0, "Starting index is already set");
        
        startingIndexForPresale = uint(blockhash(block.number - 1)) % presaleSupply;
    }

    function setStartingIndexForLast() public onlyOwner {
        require(startingIndexForLast == 0, "Starting index is already set");
        
        startingIndexForLast = uint(blockhash(block.number - 1)) % lastSupply;
    }

    function setRoot(bytes32 _root) public onlyOwner {
        root = _root;
    }

    function setPresaleRoot(bytes32 _presaleRoot) public onlyOwner {
        presaleRoot = _presaleRoot;
    }

    /*
    * Pause sale if active, make active if paused
    */
    function flipForAllSaleState() public onlyOwner {
        ForAllIsActive = !ForAllIsActive;
    }

    function flipSaleState() public onlyOwner {
        SaleIsActive = !SaleIsActive;
    }

        
    function flipPresaleState() public onlyOwner{
        PresaleIsActive = !PresaleIsActive;
    }

    // burn it down!!!!
    function burn(uint256 tokenId) external {
        require(_isApprovedOrOwner(msg.sender, tokenId), "NFT: burn caller is not owner nor approved");
        _burn(tokenId);
    }
    
}
/**
 * @title NFT
 * @dev Extends ERC721 Non-Fungible Token Standard basic implementation
 */
function setStartingIndexForAirdrop() public onlyOwner {
    require(startingIndexForAirdrop == 0, "Starting index is already set");
        
    startingIndexForAirdrop = uint(blockhash(block.number - 1)) % airdropSupply;
}

// RNG for each phase after fully minted