contract Noundles is ERC721, ERC721Enumerable, Ownable {
    
    InterfaceRainbows public Rainbows;

    // Constants.
    uint256 public constant MAX_NOUNDLES         = 8888;
    uint256 public constant MAX_FREE_MINT        = 1500;
    uint256 public constant MAX_RESERVE_NOUNDLES = 100;

    // Configuration.
    string  private baseURI;
    uint256 public  mintPrice        = 0.04 ether;
    uint256 public  saleTime         = 0;
    bool    private reservedNoundles = false;
    uint256 public  reserveMinted    = 0;
    bool    public  saleEnabled      = false;

    // presale for the noundle community :)
    uint256 public prenoundleCount = 0;
    mapping(address => bool) prenoundleAllowed;
    mapping(address => uint256) prenoundleMinted;

    mapping (address => uint256) public noundleBalance;

    // team wallets
    address public noundle1;
    address public noundle2;

    function _beforeTokenTransfer(address from, address to, uint256 tokenId) internal override(ERC721, ERC721Enumerable) {
        super._beforeTokenTransfer(from, to, tokenId);
    }

    function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721, ERC721Enumerable) returns (bool) {
        return super.supportsInterface(interfaceId);
    }

    modifier isSaleStarted() {
        require(block.timestamp >= saleTime && saleTime > 0, "Sale hasn't started");
        _;
    }
    
    modifier isSaleEnabled() {
        require(saleEnabled, "Cannot be sold yet.");
        _;
    }

    constructor(string memory _noundles) ERC721("NOUNDLES", "NOUNDLES") {
        baseURI = _noundles;
    }

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

    function setMintPrice(uint256 _noundles) public onlyOwner {
        mintPrice = _noundles;
    }

    function setSaleTime(uint256 _noundles) public onlyOwner {
        saleTime = _noundles;
    }

    function addPrenoundles(address[] memory _noundles) public onlyOwner {
        for (uint256 __noundles;__noundles < _noundles.length;__noundles++) {
            prenoundleAllowed[_noundles[__noundles]] = true;
            prenoundleCount += 1;
        }
    }

    function checkPrenoundles(address _noundles) public view returns (bool) {
        return prenoundleAllowed[_noundles];
    }


    function setRainbow(address _rainbow) external onlyOwner { Rainbows = InterfaceRainbows(_rainbow); }

    function setSaleEnabled() public onlyOwner {
        saleEnabled = true;
    }

    function setPayoutAddresses(address[] memory _noundles) public onlyOwner {
        noundle1 = _noundles[0];
        noundle2 = _noundles[1];
    }

    function withdrawFunds(uint256 _noundles) public payable onlyOwner {
        uint256 percentle = _noundles / 100;

        require(payable(noundle1).send(percentle * 50));
        require(payable(noundle2).send(percentle * 50));
    }

    // gets the noundles you own and returns it for our staking dapp
    function getNoundlesFromWallet(address _noundles) external view returns (uint256[] memory) {
        uint256 __noundles = balanceOf(_noundles);

        uint256[] memory ___noundles = new uint256[](__noundles);
        for (uint256 i;i < __noundles;i++) {
            ___noundles[i] = tokenOfOwnerByIndex(_noundles, i);
        }

        return ___noundles;
    }

    // reserves some of the supply of the noundles for giveaways & the community
    function reserveNoundles(uint256 _noundles) public onlyOwner {
        uint256 ___noundles = totalSupply();

        require(reserveMinted + _noundles <= MAX_RESERVE_NOUNDLES, "Too many reserved ser");

        for (uint256 i; i < _noundles; i++) {
            _safeMint(msg.sender, ___noundles + i);
            noundleBalance[msg.sender]++;
            reserveMinted += 1;
        }
    }

    // Mint your doodled noun :)
    function freeMintNoundles() public payable isSaleStarted {
        uint256 __noundles = totalSupply();

        require(prenoundleCount + 1 <= MAX_FREE_MINT, "We ran out of noundles :(");
        require(prenoundleAllowed[msg.sender], "You are not on whitelist");
        require(prenoundleMinted[msg.sender] < 1, "You already minted your free noundle.");

        _safeMint(msg.sender, __noundles);
        noundleBalance[msg.sender]++;

        prenoundleMinted[msg.sender] = 1;
        prenoundleCount += 1;
    }

    // mint your doodled noun :)
    function mintNoundles(uint256 _noundles) public payable isSaleStarted {
        uint256 __noundles = totalSupply();

        require(_noundles > 0 && _noundles <= 10, "Your amount needs to be greater then 0 and can't exceed 10");
        require(_noundles * mintPrice <= msg.value, "Ser we need more money for your noundles");
        require(_noundles + __noundles <= (MAX_NOUNDLES - MAX_FREE_MINT - MAX_RESERVE_NOUNDLES), "We ran out of noundles! Try minting with less!");

        for (uint256 ___noundles;___noundles < _noundles;___noundles++) {
            _safeMint(msg.sender, __noundles + ___noundles);
            noundleBalance[msg.sender]++;
        }
    }
 
    function transferFrom(address from, address to, uint256 tokenId) public override isSaleEnabled {
        Rainbows.transferTokens(from, to);
        noundleBalance[from]--;
        noundleBalance[to]++;
        ERC721.transferFrom(from, to, tokenId);
    }

    function safeTransferFrom(address from, address to, uint256 tokenId) public override isSaleEnabled {
        Rainbows.transferTokens(from, to);
        noundleBalance[from]--;
        noundleBalance[to]++;
        ERC721.safeTransferFrom(from, to, tokenId);
    }

    function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public override isSaleEnabled {
        Rainbows.transferTokens(from, to);
        noundleBalance[from]--;
        noundleBalance[to]++;
        ERC721.safeTransferFrom(from, to, tokenId, data);
    }
}
function freeMintNoundles() public payable isSaleStarted {
    uint256 __noundles = totalSupply();

    require(prenoundleCount + 1 <= MAX_FREE_MINT, "We ran out of noundles :(");
    require(prenoundleAllowed[msg.sender], "You are not on whitelist");
    require(prenoundleMinted[msg.sender] < 1, "You already minted your free noundle.");

    _safeMint(msg.sender, __noundles);
    noundleBalance[msg.sender]++;

    prenoundleMinted[msg.sender] = 1;
    prenoundleCount += 1;
}

// Mint your doodled noun :)