contract NeonJunkies is ERC721, Ownable {
    using SafeMath for uint256;

    CIPHER private cipher = CIPHER(0x2252D401Ec9D16065069529B053B105Fe42E0176);

    uint256 public nftPrice = 40000000000000000; //0.04 ETH

    uint256 public maxNFTPurchase = 10;
    uint256 public MAX_CLAIM = 3623;
    uint256 public MAX_BUY = 6377;
    uint256 public BUY_START_TIMESTAMP = 1630620000;

    bool public saleIsActive = false;
    uint256 public soldAmount = 0;

    address payable private constant _tFirst = payable(0x31ED6272EE42493E0D898a595D15e9FB55196F32);
    address payable private constant _tSecond = payable(0xBE97e949A89a45F7c141A4d686864dA501cD0664);

    constructor() ERC721("Neon Junkies", "JUNKIES") {}

    function withdraw() external onlyOwner {
        uint256 total = address(this).balance;
        uint256 amount = total.div(3);
        _tFirst.transfer(amount);
        _tSecond.transfer(total.sub(amount));
    }

    function isMinted(uint256 tokenId) public view returns (bool) {
        require(tokenId < MAX_CLAIM + MAX_BUY, "tokenId outside collection bounds");
        return _exists(tokenId);
    }

    /**
     * Set maximum nft supply
     */
    function setMaxBuy(uint256 _maxValue) external onlyOwner {
        require(_maxValue > MAX_BUY, "Invalid new max value");
        MAX_BUY = _maxValue;
    }

    /**
     * Set maximum count to mint per once.
     */
    function setMaxToMint(uint256 _maxValue) external onlyOwner {
        maxNFTPurchase = _maxValue;
    }

    /**
     * Mint NFTs by owner
     */
    function reserveNFTs(address _to, uint256 _numberOfTokens) external onlyOwner {
        require(_to != address(0), "Invalid address to reserve.");
        uint256 supply = MAX_CLAIM.add(soldAmount);
        uint256 i;

        for (i = 0; i < _numberOfTokens; i++) {
            _safeMint(_to, supply + i);
        }
        soldAmount = soldAmount.add(_numberOfTokens);
    }

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

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

    /**
     * Get the array of token for owner.
     */
    function tokensOfOwner(address _owner) external view returns(uint256[] memory) {
        uint256 tokenCount = balanceOf(_owner);
        if (tokenCount == 0) {
            return new uint256[](0);
        } else {
            uint256[] memory result = new uint256[](tokenCount);
            for (uint256 index; index < tokenCount; index++) {
                result[index] = tokenOfOwnerByIndex(_owner, index);
            }
            return result;
        }
    }

    function numberOfClaimableTokens(address user) view public returns (uint256) {
        uint256 claimableTokens = 0;
        for (uint256 i = 0; i < cipher.balanceOf(user); i++) {
            uint256 tokenId = cipher.tokenOfOwnerByIndex(user, i);
            if (!isMinted(tokenId))
                claimableTokens++;
        }
        return claimableTokens;
    }

    /**
    * Claim NFTs
    */
                              
    function claimJunkies(uint256 numberOfTokens) public {
        require(saleIsActive, "Claim has not started yet");
        require(numberOfTokens > 0, "Must claim at least one junkie");
        require(numberOfTokens <= maxNFTPurchase, "You can't claim more than 10 junkies in one transaction");
        uint256 balance = cipher.balanceOf(msg.sender);
        require(balance > 0, "Must hold at least one Ciphersquare to claim a junkie");
        require(balance >= numberOfTokens, "Must hold at least as many Ciphersquares as the number of Neon Junkies you intend to mint");

        uint256 j = 0;
        for (uint256 i = 0; i < balance; i++) {
            uint256 tokenId = cipher.tokenOfOwnerByIndex(msg.sender, i);
            if (!isMinted(tokenId)) {
                _safeMint(msg.sender, tokenId);
                j++;
            }
            if (j == numberOfTokens) {
                break;
            }
        }
    }

    /**
    * Buy NFTs
    */
    function buyJunkies(uint numberOfTokens) public payable {
        require(block.timestamp >= BUY_START_TIMESTAMP, "Sale has not started yet");
        require(saleIsActive, "Sale must be active to mint");
        require(numberOfTokens > 0, "Must buy at least one junkie");
        require(numberOfTokens <= maxNFTPurchase, "You can't buy more than 10 junkies in one transaction");
        require(soldAmount.add(numberOfTokens) <= MAX_BUY, "Purchase would exceed max supply of NFTs");
        require(nftPrice.mul(numberOfTokens) <= msg.value, "Ether value sent is not correct");
        
        for(uint i = 0; i < numberOfTokens; i++) {
            if (soldAmount < MAX_BUY) {
                _safeMint(msg.sender, MAX_CLAIM.add(soldAmount));
                soldAmount++;
            }
        }
    }
}
/**
 * @title Neon Junkies contract
 * @dev Extends ERC721 Non-Fungible Token Standard basic implementation
 */
function claimJunkies(uint256 numberOfTokens) public {
    require(saleIsActive, "Claim has not started yet");
    require(numberOfTokens > 0, "Must claim at least one junkie");
    require(numberOfTokens <= maxNFTPurchase, "You can't claim more than 10 junkies in one transaction");
    uint256 balance = cipher.balanceOf(msg.sender);
    require(balance > 0, "Must hold at least one Ciphersquare to claim a junkie");
    require(balance >= numberOfTokens, "Must hold at least as many Ciphersquares as the number of Neon Junkies you intend to mint");

    uint256 j = 0;
    for (uint256 i = 0; i < balance; i++) {
        uint256 tokenId = cipher.tokenOfOwnerByIndex(msg.sender, i);
        if (!isMinted(tokenId)) {
            _safeMint(msg.sender, tokenId);
            j++;
        }
        if (j == numberOfTokens) {
            break;
        }
    }
}

/**
 * Claim NFTs
 */