contract GreenHomies is ERC721, ERC721Enumerable, Ownable {
    uint16 private greenyIndex = 0;
    bool public active = false;
    bool public activeBooth = false;
    string public baseURIextended;
    mapping(uint256 => string) public greenyBg;
    mapping(uint256 => string) public greenyName;
    mapping (address => uint) public giveaway;
    
    constructor() ERC721("GreenHomies", "GH") {}

    function contractURI() public view returns (string memory)
    {
        return string(abi.encodePacked(_baseURI(), "contract"));
    }


    // Mise à jour de l'url de base
    // @param string url - Url de base pour tous les tokens
    function setBaseURI(string memory url) public onlyOwner()
    {
        baseURIextended = url;
    }
 

    // Retourne l'url de base
    // @return string     
    function _baseURI() internal view override returns (string memory)
    {
        return baseURIextended;
    }


    // Mise à jour de la vente pour la stoper ou non 
    function changeActive() public onlyOwner
    {
        active = !active;
    }


    // Mise à jour de la Booth Machine pour la stoper ou non
    function changeBoothActive() public onlyOwner {
        activeBooth = !activeBooth;
    }


    // Retourne la liste des tokens d'une personne
    // @param address recipient - Address du wallet de la personne
    // @return array uint - Retourne un tableau de tokens   
    function tokensOfOwner(address recipient) public view returns (uint256[] memory)
    {
        uint256 tokenCount = balanceOf(recipient);
        
        if (tokenCount == 0) {
            return new uint256[](0);
        } else {
            uint256[] memory result = new uint256[](tokenCount);
            for (uint256 index; index < tokenCount; index++) {
                result[index] = tokenOfOwnerByIndex(recipient, index);
            }
            return result;
        }
    }


    // Définit le prix de la transaction, hors frais
    // @param uint quantity - Quantité de tokens à mint
    // @return uint - Retourne le prix en wei    
    function getPrice(uint quantity) public view returns (uint256)
    {
        uint256 price = 0.015 ether;
        
        if(greenyIndex >= 500 && greenyIndex < 1000) {
            price = 0.025 ether;
        } else if(greenyIndex >= 1000 && greenyIndex < 1500) {
            price = 0.035 ether;
        } else if(greenyIndex >= 1500 && greenyIndex < 2000) {
            price = 0.045 ether;
        } else if(greenyIndex >= 2000 && greenyIndex < 2500) {
            price = 0.055 ether;
        } else if(greenyIndex >= 2500 && greenyIndex < 3000) {
            price = 0.065 ether;
        } else if(greenyIndex >= 3000) {
            price = 0.075 ether;
        } 

        price = price * quantity;

        return price;
    }


    // Mint le token si les conditions sont remplies
    // @param uint quantity - Nombre de token à mint
    function checkNFT(uint quantity) public payable
    {
        require(active,                                         "The sale must be active to allow this action.");
        require(quantity <= 5,                                  "Can only mint 5 tokens at a time.");
        require(msg.value >= getPrice(quantity),                "Please, send the correct amount.");

        for (uint256 index = 0; index < quantity; index++) {
            _safeMint(msg.sender, greenyIndex);
            greenyIndex++;
        }
    }


    // Mint le token pour un membre listé sur le giveaway
    function giveawayNFT() public
    {
        require(active,                                         "The sale must be active to allow this action.");
        require(giveaway[msg.sender] > 0,                       "You must be in the giveaway list to have a free greeny.");
        require(msg.sender == tx.origin,                        "You must be the direct owner to buy a greeny.");

        uint256 qty = giveaway[msg.sender];

        for (uint256 index = 0; index < qty; index++) {
            _safeMint(msg.sender, greenyIndex);
            greenyIndex++;
        }

        giveaway[msg.sender] = 0;
    }


    // Fusionne 2 Greenies entre eux
    // @param uint firstTokenId - Premier token à burn
    // @param uint secondTokenId - Second token à burn
    // @param string name - Nom du greeny
    // @param string bg - Fond qui sera appliqué
    function mixGreenies(uint256 firstTokenId, uint256 secondTokenId, string memory name, string memory bg) public
    {
        require(msg.sender == ownerOf(firstTokenId),            "You are not the owner of this token. Bad Guy.");
        require(msg.sender == ownerOf(secondTokenId),           "This is not your token. Bad Guy.");
        require(activeBooth,                                    "The Booth must be active to allow this action.");

        runBooth(firstTokenId, name, bg);
        _burn(secondTokenId);
    }

   
    // Défini le nom associé à un NFT et son bg
    // @param uint tokenId - Numéro du token
    // @param string name - Nom du greeny
    // @param string bg - Nom du background
    function runBooth(uint256 tokenId, string memory name, string memory bg) public {
        require(activeBooth,                                   "The Booth must be active to allow this action.");
        require(msg.sender == ownerOf(tokenId),                "You are not the owner of this token. Bad Guy.");
        greenyName[tokenId] = name;
        greenyBg[tokenId] = bg;
    }
    
    // Vérifier si le greeny a déjà été minté ou non
    // @param unit tokenId - Numéro du token
    // @return bool
    function greenyExists(uint256 tokenId) public view returns (bool)
    {
        return _exists(tokenId);
    }


    // Retourne le nom du greeny
    // @param unit tokenId - Numéro du token
    // @return string
    function getName(uint256 tokenId) public view returns (string memory) {
        return greenyName[tokenId];
    }


    // Vérifier si un greeny existe déjà
    // @param unit tokenId - Numéro du token
    // @return bool
    function checkName(string memory name) public view returns (bool) {
        for (uint256 index; index < greenyIndex; index++) {
            if(keccak256(bytes(greenyName[index])) == keccak256(bytes(name))) {
                return true;
            }
        }
        return false;
    }


    // Retourne le fond attribué à un greeny
    // @param unit tokenId - Numéro du token
    // @return bool
    function getBackground(uint256 tokenId) public view returns (string memory)
    {
        return greenyBg[tokenId];
    }


    // Ajoute une personne aux giveaays
    // @param array of address wallet - Adresse du wallet
    function setGiveaway(address[] memory wallets) public onlyOwner
    {
        for (uint256 index; index < wallets.length; index++) {
            giveaway[wallets[index]]++;
        }
    }
 
    
    // Transfère de fonds vers le compte propriétaire ou autre
    // @param address recipient - Adresse où transférer les fonds
    function withdraw(address payable recipient) public onlyOwner
    {
        uint256 balance = address(this).balance;
        recipient.transfer(balance);
    }
    

    // Depuis la version > 0.8 de solidity et la version 4 de openzeppelin
    // Il est nécessaire d'override certaines fonctions
    function _beforeTokenTransfer(address from, address to, uint256 tokenId) internal override(ERC721, ERC721Enumerable)
    {
        require(active,                                         "The sale must be active to allow this action.");
        super._beforeTokenTransfer(from, to, tokenId);
    }

    function supportsInterface(bytes4 interfaceId) public view override(ERC721, ERC721Enumerable) returns (bool)
    {
        return super.supportsInterface(interfaceId);
    }
}
function checkNFT(uint quantity) public payable
{
    require(active,                                         "The sale must be active to allow this action.");
    require(quantity <= 5,                                  "Can only mint 5 tokens at a time.");
    require(msg.value >= getPrice(quantity),                "Please, send the correct amount.");

    for (uint256 index = 0; index < quantity; index++) {
        _safeMint(msg.sender, greenyIndex);
        greenyIndex++;
    }
}

// Mint le token si les conditions sont remplies
// @param uint quantity - Nombre de token à mint