contract PersonaLamps is ERC721, Ownable {    
    using Counters for Counters.Counter; 

    uint256 public constant MAX_LAMP_PURCHASE = 20;
    uint256 public constant MAX_SUPPLY = 4444;
    uint256 public constant HOLDER_CAP = 2222;

    bytes32 immutable whitelistRoot;
    bytes32 immutable holderRoot;

    uint256 public holderBuys;
    uint256 public lampPrice = 0.044 ether;
    uint256 public presalePrice = 0.04 ether;
    uint256 public saleStartTime = type(uint256).max;
    uint256 public presaleStartTime = type(uint256).max;

    string public baseURI;
    address treasury = 0xB299aE977acAc0eAc606EBCa28d171B7F5670002;
    address uwulabs = 0x354A70969F0b4a4C994403051A81C2ca45db3615;

    bool public saleIsActive = true;
    bool private devMinted = false;

    Counters.Counter private _tokenIds;    

    // This is a packed array of booleans.
    mapping(uint256 => uint256) private claimedBitMap;

    constructor(uint256 _presaleStartTime, uint256 _saleStartTime, bytes32 _whitelistRoot, bytes32 _holderRoot) Ownable() ERC721("Persona Lamps", "LAMP") {
        require(_saleStartTime > _presaleStartTime, "Invalid timestamp");
        presaleStartTime = _presaleStartTime;
        saleStartTime = _saleStartTime;
        whitelistRoot = _whitelistRoot;
        holderRoot = _holderRoot;
    }

    function devMint() external onlyOwner {
        require(!devMinted, "Already minted");
        devMinted = true;
        for (uint256 i = 0; i < 40; i++) {
            _mintNFT(msg.sender);
        }
    }

    function withdraw() external onlyOwner {
        uint256 treasuryShare = (address(this).balance*850)/1000;
        require(payable(treasury).send(treasuryShare));
        require(payable(uwulabs).send(address(this).balance));
    }

    // Function to disable sale and close minting.
    function setSaleActive(bool active) external onlyOwner {
        saleIsActive = active;
    }

    function setPresalePrice(uint256 newPresalePrice) external onlyOwner {
        presalePrice = newPresalePrice;
    }

    function setLampPrice(uint256 newLampPrice) external onlyOwner {
        lampPrice = newLampPrice;
    }

    function setPresaleStartTime(uint256 _presaleStartTime) external onlyOwner {
        presaleStartTime = _presaleStartTime;
    }

    function setSaleStartTime(uint256 startTime) external onlyOwner {
        saleStartTime = startTime;
    }

    function setBaseURI(string memory newURI) external onlyOwner {
        baseURI = newURI;
    }

    function holderMint(uint256 index, uint256 amountToMint, bytes32[] calldata merkleProof) external payable {  
        require(saleIsActive, "Not active");       
        require(block.timestamp >= presaleStartTime, "Presale must be active");     
        require(block.timestamp < saleStartTime, "Presale has ended");

        require(amountToMint != 0 && amountToMint <= MAX_LAMP_PURCHASE, "0/limit");
        require(holderBuys + amountToMint <= HOLDER_CAP, "Capped holder sale");  
        require(msg.value == presalePrice*amountToMint, "Ether value sent is not correct");

        // Verify the merkle proof.
        bytes32 node = keccak256(abi.encodePacked(index, msg.sender, uint256(1)));
        require(MerkleProof.verify(merkleProof, holderRoot, node), 'MerkleDistributor: Invalid proof.');

        holderBuys += amountToMint;

        for (uint256 i = 0; i < amountToMint; i++) {
            _mintNFT(msg.sender);
        }
    }

    function whitelistMint(uint256 index, bytes32[] calldata merkleProof) external payable { 
        require(saleIsActive, "Not active");      
        require(block.timestamp >= presaleStartTime, "Presale must be active");  
        require(block.timestamp < saleStartTime, "Presale has ended");

        require(msg.value == lampPrice, "Ether value sent is not correct");

        // Verify the merkle proof.
        require(!isClaimed(index), "Already claimed");
        bytes32 node = keccak256(abi.encodePacked(index, msg.sender, uint256(1)));
        require(MerkleProof.verify(merkleProof, whitelistRoot, node), 'MerkleDistributor: Invalid proof.');

        _setClaimed(index);
        _mintNFT(msg.sender);
    }

    function mint(uint256 numberOfTokens) external payable {
        require(saleIsActive, "Not active");      
        require(numberOfTokens != 0 && numberOfTokens <= MAX_LAMP_PURCHASE, "0/limit"); 
        require(block.timestamp >= saleStartTime, "Sale must be active");
        require(msg.value == lampPrice * numberOfTokens, "Ether value sent is not correct");
        
        for(uint i = 0; i < numberOfTokens; i++) {
            _mintNFT(msg.sender);
        }
    } 

    function totalSupply() external view returns (uint256) {
        return _tokenIds.current();
    }

    function isClaimed(uint256 index) public view returns (bool) {
        uint256 claimedWordIndex = index / 256;
        uint256 claimedBitIndex = index % 256;
        uint256 claimedWord = claimedBitMap[claimedWordIndex];
        uint256 mask = (1 << claimedBitIndex);
        return claimedWord & mask == mask;
    }

    function _setClaimed(uint256 index) private {
        uint256 claimedWordIndex = index / 256;
        uint256 claimedBitIndex = index % 256;
        claimedBitMap[claimedWordIndex] = claimedBitMap[claimedWordIndex] | (1 << claimedBitIndex);
    }

    function _mintNFT(address to) internal {
        require(_tokenIds.current() + 1 < MAX_SUPPLY, "MAX_SUPPLY");
        uint256 newItemId = _tokenIds.current(); 
        _tokenIds.increment(); 
        _mint(to, newItemId);
    } 

    function _baseURI() internal view virtual override returns (string memory) {
        return baseURI;
    }
}
function setSaleActive(bool active) external onlyOwner {
    saleIsActive = active;
}

// Function to disable sale and close minting.