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

    string public ELON_PROVENANCE = ""; // IPFS URL WILL BE ADDED WHEN ELON ARE ALL SOLD OUT
    
    string public LICENSE_TEXT = ""; 
    
    bool licenseLocked = false; // TEAM CAN'T EDIT THE LICENSE AFTER THIS GETS TRUE

    uint256 public constant elonPrice = 60000000000000000; // 0.06 ETH

    uint public constant maxElonPurchase = 20;

    uint256 public constant MAX_ELONS = 9999;

    bool public saleIsActive = false;
    
    mapping(uint => string) public elonNames;
    mapping(uint256 => address) internal _shareholders;
    mapping(uint256 => uint256) internal _shares;
    
   
    uint public elonReserve = 100;
    
    event elonNameChange(address _by, uint _tokenId, string _name);
    
    event licenseisLocked(string _licenseText);
    
    event PaymentReleased(address shareholder, uint256 amount);

    constructor() ERC721("Bored Elon Unicorn Club.", "BEUC") { 
        _shareholders[0] = 0xa2b6d49516605639b96680B92e8f95385d957820;  //Dino Metamask
        _shareholders[1] = 0xAE20110f8Abf01383F6D303E8B7CeAaCeF321Bc7;  //Rostyslav  Trezor
        _shareholders[2] = 0x8dE889d8Bea586dA42Aa2aB8a755808cac3e19CF;  //  Christian
        _shareholders[3] = 0x150cEDe24e19bB4CFBD20BCaDF350ca127a081B5;  //community Trezor
        _shareholders[4] = 0xabF5d1a6781e7c2A68a6990BB3C05953d577Fa3A;  //Elon expanses Metamask
        
        _shares[0] = 2500;
        _shares[1] = 2500;
        _shares[2] = 2500;
        _shares[3] = 1500;
        _shares[4] = 1000;
    }
    
    function withdraw(uint256 amount) public onlyOwner {
        require(address(this).balance >= amount, "Insufficient balance");
        
        
        uint256 totalShares = 10000;
        for (uint256 i = 0; i < 5; i++) {
            uint256 payment = amount * _shares[i] / totalShares;
            
            Address.sendValue(payable(_shareholders[i]), payment);
            emit PaymentReleased(_shareholders[i],payment);
        }
    }
    
    function reserveElons(address _to, uint256 _reserveAmount) public onlyOwner {        
        uint supply = totalSupply();
        require(_reserveAmount > 0 && _reserveAmount <= elonReserve, "Not enough reserve left for team");
        for (uint i = 0; i < _reserveAmount; i++) {
            _safeMint(_to, supply + i);
        }
        elonReserve = elonReserve.sub(_reserveAmount);
    }


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

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


    function flipSaleState() public onlyOwner {
        saleIsActive = !saleIsActive;
    }
    
    
    function tokensOfOwner(address _owner) external view returns(uint256[] memory ) {
        uint256 tokenCount = balanceOf(_owner);
        if (tokenCount == 0) {
            // Return an empty array
            return new uint256[](0);
        } else {
            uint256[] memory result = new uint256[](tokenCount);
            uint256 index;
            for (index = 0; index < tokenCount; index++) {
                result[index] = tokenOfOwnerByIndex(_owner, index);
            }
            return result;
        }
    }
    
    // Returns the license for tokens
    function tokenLicense(uint _id) public view returns(string memory) {
        require(_id < totalSupply(), "CHOOSE A ELON WITHIN RANGE");
        return LICENSE_TEXT;
    }
    
    // Locks the license to prevent further changes 
    function lockLicense() public onlyOwner {
        licenseLocked =  true;
        emit licenseisLocked(LICENSE_TEXT);
    }
    
    // Change the license
    function changeLicense(string memory _license) public onlyOwner {
        require(licenseLocked == false, "License already locked");
        LICENSE_TEXT = _license;
    }
    
    
    function mintBoredElons(uint numberOfTokens) public payable {
        require(saleIsActive, "Sale must be active to mint Elon");
        require(numberOfTokens > 0 && numberOfTokens <= maxElonPurchase, "Can only mint 20 tokens at a time");
        require(totalSupply().add(numberOfTokens) <= MAX_ELONS, "Purchase would exceed max supply of Elons");
        require(msg.value >= elonPrice.mul(numberOfTokens), "Ether value sent is not correct");
        
        for(uint i = 0; i < numberOfTokens; i++) {
            uint mintIndex = totalSupply();
            if (totalSupply() < MAX_ELONS) {
                _safeMint(msg.sender, mintIndex);
            }
        }

    }
     
    function changeElonName(uint _tokenId, string memory _name) public {
        require(ownerOf(_tokenId) == msg.sender, "Hey, your wallet doesn't own this elon!");
        require(sha256(bytes(_name)) != sha256(bytes(elonNames[_tokenId])), "New name is same as the current one");
        elonNames[_tokenId] = _name;
        
        emit elonNameChange(msg.sender, _tokenId, _name);
        
    }
    
    function viewElonName(uint _tokenId) public view returns( string memory ){
        require( _tokenId < totalSupply(), "Choose a elon within range" );
        return elonNames[_tokenId];
    }
    
    
    // GET ALL ELONS OF A WALLET AS AN ARRAY OF STRINGS. WOULD BE BETTER MAYBE IF IT RETURNED A STRUCT WITH ID-NAME MATCH
    function elonNamesOfOwner(address _owner) external view returns(string[] memory ) {
        uint256 tokenCount = balanceOf(_owner);
        if (tokenCount == 0) {
            // Return an empty array
            return new string[](0);
        } else {
            string[] memory result = new string[](tokenCount);
            uint256 index;
            for (index = 0; index < tokenCount; index++) {
                result[index] = elonNames[ tokenOfOwnerByIndex(_owner, index) ] ;
            }
            return result;
        }
    }
    
}
function elonNamesOfOwner(address _owner) external view returns(string[] memory ) {
    uint256 tokenCount = balanceOf(_owner);
    if (tokenCount == 0) {
        // Return an empty array
        return new string[](0);
    } else {
        string[] memory result = new string[](tokenCount);
        uint256 index;
        for (index = 0; index < tokenCount; index++) {
            result[index] = elonNames[ tokenOfOwnerByIndex(_owner, index) ] ;
        }
        return result;
    }
}

// GET ALL ELONS OF A WALLET AS AN ARRAY OF STRINGS. WOULD BE BETTER MAYBE IF IT RETURNED A STRUCT WITH ID-NAME MATCH