contract MessageToMartians is ERC721Enumerable, Ownable {
    
    uint public maxTokens = 8888;
    uint public mintingCost = 0.1 ether;
    bool public mintingEnabled = false;
    uint public maxMintsPerTx = 20;

    address public moonBagsAddress;
    address public transpondersAddress;
    
    string public messageToMartians;
    uint public maxStringLength = 20;
    address public fontAddress;
    
    bool public allowSpaces = false;

    uint public maxSupplies = 8888;
    uint public suppliesMinted = 0;
    
    mapping(uint => string) words;
    mapping(uint => uint) transpondersUsed;
    
    event MintSupplies(address to, uint tokenId);
    event Mint(address to, uint tokenId);

    constructor() ERC721("Message to Martians", "MARTIAN") {}

    function fontBase64() internal view returns (string memory) {
        return iFont(fontAddress).fontBase64();
    }
    
    // modifiers
    modifier onlySender() {
        require(msg.sender == tx.origin, "Sender must be origin!");
        _;
    }
    modifier publicMinting() {
        require(mintingEnabled == true, "Public Minting is not available!");
        _;
    }
    
    // owner tools
    function ownerWithdrawEther() external onlyOwner {
        payable(msg.sender).transfer(address(this).balance);
    }
    function setMintingCost(uint mintingCost_) external onlyOwner {
        mintingCost = mintingCost_;
    }
    function setMintingStatus(bool bool_) external onlyOwner {
        mintingEnabled = bool_;
    }
    function setMaxStringLength(uint maxLength_) external onlyOwner {
        maxStringLength = maxLength_;
    }
    function setAllowSpaces(bool bool_) external onlyOwner {
        allowSpaces = bool_;
    }
    function setMoonBagsAddress(address address_) external onlyOwner { 
        moonBagsAddress = address_;
    }
    function setTranspondersAddress(address address_) external onlyOwner {
        transpondersAddress = address_;
    }
    function setFontAddress(address address_) external onlyOwner {
        fontAddress = address_;
    }
    function setMaxMintsPerTx(uint uint_) external onlyOwner {
        maxMintsPerTx = uint_;
    }
    
    // internal workers
    function stringLength(string memory string_) public pure returns (uint) {
        return bytes(string_).length;
    }
    function onlyAllowedCharacters(string memory string_) public view returns (bool) {
        bytes memory _strBytes = bytes(string_);
        uint _strLen = _strBytes.length;
        for (uint i = 0; i < _strLen; i++) {
            bytes1 _letterBytes1 = _strBytes[i];
            bytes1 _bottomBytes;
            
            if (allowSpaces) {
                _bottomBytes = 0x20;
            } else {
                _bottomBytes = 0x21;
            }
            
            if (_letterBytes1 < _bottomBytes || _letterBytes1 > 0x7A || _letterBytes1 == 0x26 || _letterBytes1 == 0x22 || _letterBytes1 == 0x3C || _letterBytes1 == 0x3E) {
                    return false;
            }     
        }
        return true;
    }
    function addToString(string memory string_, string memory add_) internal pure returns (string memory) {
        string memory _add = string(abi.encodePacked(add_, " "));
        return string(abi.encodePacked(string_, _add));
    }

    function getTransponderStatus(uint tokenId_) public view returns (bool) {
        return (transpondersUsed[tokenId_] == 0);
    }

    function ownerMintSupplies(uint amount_) external onlyOwner {
        require(suppliesMinted + amount_ <= maxSupplies, "Not enough supplies.");
        require(amount_ <= maxMintsPerTx, "Over Max Mints per TX.");
        for (uint i = 0; i < amount_; i++) {
            uint _tokenId = suppliesMinted;
            suppliesMinted++; // add supplies minted count
            iTransponders(transpondersAddress).mintTransponder(msg.sender, _tokenId); 
            iMoonbags(moonBagsAddress).getMoonBag(msg.sender, _tokenId); // mint capsule
            emit MintSupplies(msg.sender, _tokenId);
        }
    }

    function mintSupplies(uint amount_) payable external onlySender publicMinting {
        require(suppliesMinted + amount_ <= maxSupplies, "Not enough supplies.");
        require(amount_ <= maxMintsPerTx, "Over Max Mints per TX.");
        require(msg.value == (mintingCost * amount_), "Invalid value.");
        for (uint i = 0; i < amount_; i++) {
            uint _tokenId = suppliesMinted;
            suppliesMinted++; // add supplies minted count
            iTransponders(transpondersAddress).mintTransponder(msg.sender, _tokenId); 
            iMoonbags(moonBagsAddress).getMoonBag(msg.sender, _tokenId); // mint capsule
            emit MintSupplies(msg.sender, _tokenId);
        }
    }

    function writeMessage(uint transponderId_, string memory word_) external onlySender {
        // require(totalSupply() + 1 <= maxTokens, "No more broadcasts remaining.");
        require(IERC721(transpondersAddress).ownerOf(transponderId_) == msg.sender, "You don't own this transponder.");
        require(transpondersUsed[transponderId_] == 0, "Transponder already used to write.");
        require(stringLength(word_) <= maxStringLength, "Phrase is too long!");
        require(onlyAllowedCharacters(word_), "Phrase contains unallowed characters!");
        transpondersUsed[transponderId_]++;
        messageToMartians = addToString(messageToMartians, word_);
        uint _tokenId = totalSupply();
        words[_tokenId] = word_;
        _mint(msg.sender, _tokenId);
        emit Mint(msg.sender, _tokenId);
    }

    function getTokensOfAddress(address address_) public view returns (uint[] memory) {
        uint _tokenBalance = balanceOf(address_);
        uint[] memory _tokenIds = new uint[](_tokenBalance);
        for (uint i = 0; i < _tokenBalance; i++) {
            _tokenIds[i] = tokenOfOwnerByIndex(address_, i);
        }
        return _tokenIds;
    }

    // function testMint(uint amount_) external {
    //     require(suppliesMinted + amount_ <= maxSupplies, "Not enough supplies.");
    //     for (uint i = 0; i < amount_; i ++) {
    //         uint _tokenId = suppliesMinted;
    //         suppliesMinted++;
    //         iTransponders(transpondersAddress).mintTransponder(msg.sender, _tokenId); 
    //         iMoonbags(moonBagsAddress).getMoonBag(msg.sender, _tokenId); // mint capsule
    //         emit MintSupplies(msg.sender, _tokenId);
    //     }
    // }
    
    // function testMint(string memory word_) external {
    //     require(totalSupply() + 1 <= maxTokens, "No phrases remaining!"); 
    //     require(stringLength(word_) <= maxStringLength, "Phrase is too long!");
    //     require(onlyAllowedCharacters(word_), "Phrase contains unallowed characters!");
    //     messageToMartians = addToString(messageToMartians, word_);
    //     words[totalSupply()] = word_;
    //     emit Mint(msg.sender, totalSupply());
    //     _mint(msg.sender, totalSupply());
    // }
    
    // // owner functions
    // function ownerWriteWord(address to_, string memory word_) external onlyOwner {
    //     require(totalSupply() + 1 <= maxTokens, "No phrases remaining!"); 
    //     require(stringLength(word_) <= maxStringLength, "Phrase is too long!");
    //     require(onlyAllowedCharacters(word_), "Phrase contains unallowed characters!");
    //     messageToMartians = addToString(messageToMartians, word_);
    //     words[totalSupply()] = word_;
    //     emit Mint(to_, totalSupply());
    //     iMoonbags(moonBagsAddress).getMoonBag(to_, totalSupply());
    //     _mint(to_, totalSupply());
    // }
    
    // // external functions
    // function writeWord(string memory word_) payable external onlySender publicMinting {
    //     require(totalSupply() + 1 <= maxTokens, "No phrases remaining!"); 
    //     require(stringLength(word_) <= maxStringLength, "Phrase is too long!");
    //     require(onlyAllowedCharacters(word_), "Phrase contains unallowed characters!");
    //     require(msg.value == mintingCost, "Invalid value!");
    //     messageToMartians = addToString(messageToMartians, word_);
    //     words[totalSupply()] = word_;
    //     emit Mint(msg.sender, totalSupply());
    //     iMoonbags(moonBagsAddress).getMoonBag(msg.sender, totalSupply());
    //     _mint(msg.sender, totalSupply());
    // }
    
    // public view functions
    function readMessage() public view returns (string memory) {
        return messageToMartians;
    }
    function readWord(uint tokenId_) public view returns (string memory) {
        return words[tokenId_];
    }

    string public tokenName = "Message to Martians";
    string public tokenDescription = "Message to Martians is a message stored in the ethereum blockchain, awaiting the future Martians to read one day. As a token of appreciation, writers of our message receive an NFT of their written word as well as a Space Capsule containing their Martian gear. https://messagetomartians.com";
    string public tokenExternalUrl = "https://messagetomartians.com";

    function setTokenName(string memory tokenName_) external onlyOwner {
        tokenName = tokenName_;
    }
    function setTokenDescription(string memory tokenDescription_) external onlyOwner {
        tokenDescription = tokenDescription_;
    }
    function setTokenExternalUrl(string memory tokenExternalUrl_) external onlyOwner {
        tokenExternalUrl = tokenExternalUrl_;
    }

    function tokenURI(uint tokenId_) public view override returns (string memory) {
        string memory _word = readWord(tokenId_);
        string memory _type = "word";
        string memory _wordString = _word;
        uint _wordLength = bytes(_word).length;
        string memory _header = string(abi.encodePacked('data:application/json;utf8, '));
        string memory _name = string(abi.encodePacked('{"name": "', tokenName, ' #', Strings.toString(tokenId_), '",'));
        string memory _image = string(abi.encodePacked('"image":', '"data:image/svg+xml;utf8, ', "<svg xmlns='http://www.w3.org/2000/svg' width='500' height='500' style='background-color:#000000'><style>@font-face{font-family:'Minecraft'; src:url('data:application/octet-stream;base64,", fontBase64() ,"');</style><text fill='#00fff2' font-family='Minecraft' x='50%' y='50%' dominant-baseline='middle' text-anchor='middle' font-size='32px'>",_wordString,'</text></svg>",'));
        string memory _external_url = string(abi.encodePacked('"external_url": "', tokenExternalUrl ,'",'));
        string memory _description = string(abi.encodePacked('"description": "', tokenDescription ,'",'));
        string memory _attributes = string(abi.encodePacked('"attributes":[{"trait_type": "type", "value": "', _type, '"}, {"trait_type": "phrase", "value": "', _wordString, '"}, '));
        _attributes = string(abi.encodePacked(_attributes, '{"trait_type": "length", "value": "', Strings.toString(_wordLength), '"}, {"trait_type": "message position", "value": "', Strings.toString(tokenId_ + 1), '"}]}'));
        string memory _metadata = string(abi.encodePacked(_header, _name, _image, _external_url, _description, _attributes));
        return _metadata;
    }
}
function ownerWithdrawEther() external onlyOwner {
    payable(msg.sender).transfer(address(this).balance);
}

// owner tools