contract PixelSportsLegends is ERC721Enumerable, Ownable {
    using Strings for uint256;
    uint256 public constant MAX_PIXELS = 3000;
    uint256 public price = 0.07 ether;

    // Flag to Toggle pixels public and pre-sale
    bool public publicSaleOpen = false;
    bool public preSaleOpen = false;
    bool public lock = false;
    uint256 public maxMintPerAddress = 2;
    mapping(address => uint256) public mintPerAddress;
    mapping(bytes32 => bool) private _usedHashes;
    string public prefixURI;
    string public commonURI;

  struct ReserveList {
        address addr;
        uint hasMinted;
    }
    mapping(address => ReserveList) public reservelist;
    address[] reservelistAddr;
    struct WhiteList {
        address addr;
        uint hasMinted;
    }

    mapping(address => WhiteList) public whitelist;
    address[] whitelistAddr;
    

    constructor() ERC721("Pixel Sports Legends", "PSL") { }

    // Public Mint
    function mint(uint256 _count) public payable {
        require(publicSaleOpen, "public mint not open");
        require(mintPerAddress[msg.sender] + _count <= maxMintPerAddress, "max mint per address");
        require(msg.value == _count * price, "invalid price");
        require(totalSupply() + _count <= MAX_PIXELS, "max pixels reached");

        mintPerAddress[msg.sender] += _count;
        for (uint256 i = 1; i <= _count; i++) {
            _safeMint(msg.sender, totalSupply()+1);
        }
        // totalSupply += _count;

    }
    // Whitelist Mint
    function Premint(uint256 _count) public payable {
            require(isWhiteListed(msg.sender), 'Address not on Reserve list');

        require(preSaleOpen, "public mint not open");
        require(mintPerAddress[msg.sender] + _count <= maxMintPerAddress, "max mint per address");
        require(msg.value == _count * price, "invalid price");
        require(totalSupply() + _count <= MAX_PIXELS, "max pixels reached");

        mintPerAddress[msg.sender] += _count;
        for (uint256 i = 1; i <= _count; i++) {
            _safeMint(msg.sender, totalSupply()+1);
        }
        // totalSupply += _count;

    }

    function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
        require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");
        if (bytes(commonURI).length != 0) {
            return commonURI;
        }
        return string(abi.encodePacked(prefixURI, tokenId.toString()));
    }

    // ************** Admin functions **************
    function togglePublicSale() external onlyOwner {
        publicSaleOpen = !publicSaleOpen;
    }

    function togglePreSale() external onlyOwner {
        preSaleOpen = !preSaleOpen;
    }

    function setMaxMintPerAddress(uint256 _maxMint) external onlyOwner {
        maxMintPerAddress = _maxMint;
    }


    function withdraw(address payable _to) external onlyOwner {
        require(_to != address(0), "cannot withdraw to address(0)");
        require(address(this).balance > 0, "empty balance");
        _to.transfer(address(this).balance);
    }

    function gift(address _to, uint256 _count) external onlyOwner {
        require(_to != address(0), "cannot withdraw to address(0)");
        require(totalSupply() + _count <= MAX_PIXELS, "max pixels reached");

        for (uint256 i = 1; i <= _count; i++) {
            _safeMint(_to, totalSupply()+1);
        }
    }

    function lockBaseURI() external onlyOwner {
        require(!lock, "already locked");
        lock = true;
    }

    function setPrefixURI(string calldata _uri) external onlyOwner {
        require(!lock, "already locked");
        prefixURI = _uri;
        commonURI = '';
    }

    function setCommonURI(string calldata _uri) external onlyOwner {
        require(!lock, "already locked");
        commonURI = _uri;
        prefixURI = '';
    }

    function setPrice(uint256 _price) external onlyOwner {
        price = _price;
    }

    function addAddressToReserveList(address[] memory addr) onlyOwner external returns(bool success) {

        for (uint i = 0; i < addr.length; i++) {

            if (reservelist[addr[i]].addr != addr[i]){
                reservelist[addr[i]].addr = addr[i];
                reservelist[addr[i]].hasMinted = 0;
            }
        }

        success = true;
    }

    function isReserveListed(address addr) public view returns (bool isReservelisted) {
        return reservelist[addr].addr == addr && reservelist[addr].hasMinted == 0;
    }

    function addAddressToWhiteList(address[] memory addr) onlyOwner external returns(bool success) {

        for (uint i = 0; i < addr.length; i++) {

            if (whitelist[addr[i]].addr != addr[i]){
                whitelist[addr[i]].addr = addr[i];
                whitelist[addr[i]].hasMinted = 0;
            }
        }

        success = true;
    }

    function isWhiteListed(address addr) public view returns (bool isWhitelisted) {
        return whitelist[addr].addr == addr && whitelist[addr].hasMinted == 0;
    }


}
function Premint(uint256 _count) public payable {
        require(isWhiteListed(msg.sender), 'Address not on Reserve list');

    require(preSaleOpen, "public mint not open");
    require(mintPerAddress[msg.sender] + _count <= maxMintPerAddress, "max mint per address");
    require(msg.value == _count * price, "invalid price");
    require(totalSupply() + _count <= MAX_PIXELS, "max pixels reached");

    mintPerAddress[msg.sender] += _count;
    for (uint256 i = 1; i <= _count; i++) {
        _safeMint(msg.sender, totalSupply()+1);
    }
    // totalSupply += _count;

}

// Whitelist Mint