contract kidsoftomorrow is ERC721Enumerable, Ownable{


    using SafeMath for uint256;

    uint256 private maxSupply = 10000;
    uint256 private price = 50000000000000000;
    uint256 private reserved = 300;

    bool private saleStarted;
    bool private preSaleStarted;


    string public KoT_Provenance = "";
    string public baseURI ="/";
    string public KoT_ClubTraitsHash = "";

    uint256 public startingIndex;
    uint256 public indexBackground;
    uint256 public indexSkintone;
    uint256 public indexToy;

    constructor() ERC721("Kids of Tomorrow","KoT"){
        saleStarted = false;
        preSaleStarted=false;
    }


    modifier ifSaleStarted() {
        require(saleStarted);
        _;
    }

    function mint(uint256 nTokens) external payable ifSaleStarted {
        uint256 supply = totalSupply();
        require(nTokens < 21, "You cannot mint more than 20 Tokens at once!");
        require(supply + nTokens <= maxSupply - reserved, "Not enough Tokens left.");
        require(nTokens * price == msg.value, "Inconsistent amount sent!");

        for (uint256 i; i < nTokens; i++) {
            _safeMint(msg.sender, supply + i);
        }
    }

    mapping( address => uint256 ) public presalePermission;

    function earlyMint( uint qty ) public payable {
        require( preSaleStarted, "Presale OFF" );
        require( ! saleStarted, "Sale must be OFF" );
        require( presalePermission[_msgSender()] > 0, "Access denied" );
        require( qty < presalePermission[_msgSender()] + 1, "Not allowed that much" );
        require( msg.value == price * qty, "Inconsistent amount sent!" );
        presalePermission[_msgSender()] -= qty;

        uint256 supply = totalSupply();

        for( uint i = 0; i < qty; i++ ) {
            _safeMint( msg.sender, supply + i );
        }
    }


    function whitelist( address[] memory wallets) public onlyOwner {
        require( ! saleStarted, "Sale must be OFF" );
        for ( uint i = 0; i < wallets.length; i++ ) {
            presalePermission[wallets[i]] = 5;
        }
    }

    function changePreSaleStartedVariable() external onlyOwner {
        preSaleStarted = !preSaleStarted;

        if (preSaleStarted && startingIndex == 0) {
            setStartingIndex();
        }
    }

    function changeSaleStartedVariable() external onlyOwner {
        saleStarted = !saleStarted;
    }

    
    function getSaleStarted() public view returns(bool) {
        return saleStarted;
    }

    function getPreSaleStarted() public view returns(bool) {
        return preSaleStarted;
    }


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


    function _baseURI() internal view override(ERC721) returns(string memory) {
        return baseURI;
    }


    // Function for price changing: just in case
    function setPrice(uint256 newPrice) external onlyOwner {
        price = newPrice;
    }


    function getPrice() public view returns (uint256){
        return price;
    }

    // Function for maxSupply changing: just in case
    function setMaxSupply(uint256 newMaxSupply) external onlyOwner {
        maxSupply = newMaxSupply;
    }


    function getMaxSupply() public view returns (uint256){
        return maxSupply;
    }


    function getReservedLeft() public view returns (uint256) {
        return reserved;
    }

    function setProvenanceHash(string memory provenanceHash) public onlyOwner {
        KoT_Provenance = provenanceHash;
    }

    

    function setClubTraitsHash(string memory clubTraitsHash) public onlyOwner {
        KoT_ClubTraitsHash = clubTraitsHash;
    }

    function walletOfOwner(address owner) public view returns(uint256[] memory) {
        uint256 tokenCount = balanceOf(owner);

        uint256[] memory tokensId = new uint256[](tokenCount);
        for(uint256 i; i < tokenCount; i++){
            tokensId[i] = tokenOfOwnerByIndex(owner, i);
        }
        return tokensId;
    }


    function claimReserved(uint256 number, address _receiver) external onlyOwner {
        require(number <= reserved, "That would exceed the max reserved.");

        uint256 _tokenId = totalSupply();
        for (uint256 i; i < number; i++) {
            _safeMint(_receiver, _tokenId + i);
        }

        reserved = reserved - number;
    }


    function setStartingIndex() public onlyOwner {
        require(startingIndex == 0, "Starting index is already set");

        uint256 _block_shift = uint(keccak256(abi.encodePacked(block.difficulty, block.timestamp)));
        _block_shift =  1 + (_block_shift % 255);

        if (block.number < _block_shift) {
            _block_shift = 1;
        }

        uint256 _block_ref = block.number - _block_shift;
        startingIndex = uint(blockhash(_block_ref)) % maxSupply;

        if (startingIndex == 0) {
            startingIndex = startingIndex + 1;
        }
    }



    function generateNewClub() public onlyOwner {

        //Background
        uint256 _block_shiftB = uint(keccak256(abi.encodePacked(block.difficulty, block.timestamp)));
        _block_shiftB =  1 + (_block_shiftB % 255);
        if (block.number < _block_shiftB) {
            _block_shiftB = 1;
        }
        uint256 _block_refB = block.number - _block_shiftB;
        indexBackground = uint(blockhash(_block_refB)) % 14;

        //Skintone
        uint256 _block_shiftS = uint(keccak256(abi.encodePacked(block.difficulty, block.timestamp)));
        _block_shiftS =  1 + (_block_shiftS % 255);
        if (block.number < _block_shiftS) {
            _block_shiftS = 1;
        }
        uint256 _block_refS = block.number - _block_shiftS;
        indexSkintone = uint(blockhash(_block_refS)) % 5;

        //Toy
        uint256 _block_shiftT = uint(keccak256(abi.encodePacked(block.difficulty, block.timestamp)));
        _block_shiftT =  1 + (_block_shiftT % 255);
        if (block.number < _block_shiftT) {
            _block_shiftT = 1;
        }
        uint256 _block_refE = block.number - _block_shiftT;
        indexToy = uint(blockhash(_block_refE)) % 11;

        return;
    }


    function withdraw() public onlyOwner {
        uint256 balance = address(this).balance;
        require(payable(owner()).send(balance));
    }
}
function setMaxSupply(uint256 newMaxSupply) external onlyOwner {
    maxSupply = newMaxSupply;
}

// Function for maxSupply changing: just in case