contract MarfaMarks is ERC721, Ownable, ReentrancyGuard {
    // Hex colors are stored as base10 uint24s to fit in one memory slot
    // For example, FFFFFF (white) -> 16777215
    struct Palette {
        uint24 color1;
        uint24 color2;
        uint24 color3;
        uint24 color4;
        uint24 color5;
    }

    /**
     * Event emitted when minting a new NFT.
     */
    event Mint(uint256 indexed index, address indexed minter);

    using SafeMath for uint256;
    using ECDSA for bytes32;
    using Address for address;

    address private signVerifier;
    uint16 public constant MAX_COUNT = 500;
    uint256 public generativeScriptSegmentCount;

    bool public saleIsActive = false;
    bool public uriLocked = false;
    bool public publicMinting = true;

    mapping(uint256 => string) public generativeScriptSegments;
    mapping(address => bool) private codeClaimed;
    mapping(uint256 => Palette) public tokenIdToPalette;
    mapping(uint256 => bytes32) public tokenIdToMintHash;

    constructor() ERC721("Marfa Marks", "MMS") {
        signVerifier = 0xb9793D25a3dcBdE7D09e57B3830A169497217019;
        generativeScriptSegmentCount = 0;
    }

    function withdraw(address payable beneficiary) external onlyOwner {
        uint256 balance = address(this).balance;
        beneficiary.transfer(balance);
    }

    function flipSaleState() external onlyOwner {
        saleIsActive = !saleIsActive;
    }

    function flipPublicMinting() external onlyOwner {
        publicMinting = !publicMinting;
    }

    function lockURI() external onlyOwner {
        if (!uriLocked) {
            uriLocked = true;
        }
    }

    function setBaseURI(string memory baseURI) external onlyOwner {
        if (!uriLocked) {
            _setBaseURI(baseURI);
        }
    }

    // @dev Returns whether a user has claimed from presale
    function getCodeClaimed(address user) external view returns (bool) {
        return codeClaimed[user];
    }

    function getAllColorsOrdered() external view returns (uint24[] memory) {
        // Palettes are 5 colors each
        uint256 supply = totalSupply();
        uint24[] memory colors = new uint24[](supply.mul(5));

        for (uint256 i = 0; i < totalSupply(); i++) {
            Palette memory palette = tokenIdToPalette[i];
            uint256 index = i * 5;
            colors[index] = palette.color1;
            colors[index + 1] = palette.color2;
            colors[index + 2] = palette.color3;
            colors[index + 3] = palette.color4;
            colors[index + 4] = palette.color5;
        }
        return colors;
    }

    function getPalette(uint256 tokenId)
        external
        view
        returns (uint24[] memory)
    {
        require(tokenId < totalSupply(), "Invalid Token ID");

        uint24[] memory colors = new uint24[](5);
        Palette memory palette = tokenIdToPalette[tokenId];
        colors[0] = palette.color1;
        colors[1] = palette.color2;
        colors[2] = palette.color3;
        colors[3] = palette.color4;
        colors[4] = palette.color5;
        return colors;
    }

    // @dev Sets a new signature verifier
    function setSignVerifier(address verifier) external onlyOwner {
        signVerifier = verifier;
    }

    function getSigningHash(address recipient, uint256 tokenCount)
        public
        pure
        returns (bytes32)
    {
        return keccak256(abi.encodePacked(recipient, tokenCount));
    }

    function addGenerativeScriptSegment(string memory _scriptSegment)
        external
        onlyOwner
    {
        generativeScriptSegments[generativeScriptSegmentCount] = _scriptSegment;
        generativeScriptSegmentCount.add(1);
    }

    function getGenerativeScriptSegment(uint256 index)
        external
        view
        returns (string memory)
    {
        require(index < generativeScriptSegmentCount, "Segment does not exist");
        return generativeScriptSegments[index];
    }

    function _psuedoRandomHash() private view returns (bytes32) {
        return
            keccak256(
                abi.encodePacked(
                    tx.gasprice,
                    block.number,
                    blockhash(block.number.sub(1)),
                    block.timestamp,
                    msg.sender,
                    totalSupply()
                )
            );
    }

    function mint(
        bytes memory sig,
        uint24 color1,
        uint24 color2,
        uint24 color3,
        uint24 color4,
        uint24 color5
    ) external nonReentrant {
        require(saleIsActive, "Sale must be active to mint MarfaMarks");
        require(
            publicMinting,
            "Public minting must be active to mint MarfaMarks"
        );
        require(
            totalSupply().add(1) <= MAX_COUNT,
            "Purchase would exceed max supply of MarfaMarks"
        );
        require(!codeClaimed[msg.sender], "Already minted with this address");

        bytes32 message = getSigningHash(msg.sender, 1)
            .toEthSignedMessageHash();
        require(
            ECDSA.recover(message, sig) == signVerifier,
            "Permission to call this function failed"
        );

        uint256 tokenId = totalSupply();
        if (tokenId < MAX_COUNT) {
            codeClaimed[msg.sender] = true;

            bytes32 mintHash = _psuedoRandomHash();
            tokenIdToMintHash[tokenId] = mintHash;

            tokenIdToPalette[tokenId] = Palette(
                color1,
                color2,
                color3,
                color4,
                color5
            );

            _safeMint(msg.sender, tokenId);
            emit Mint(tokenId, msg.sender);
        }
    }

    function ownerMintTo(
        address recipient,
        uint24 color1,
        uint24 color2,
        uint24 color3,
        uint24 color4,
        uint24 color5
    ) external onlyOwner nonReentrant {
        require(
            totalSupply().add(1) <= MAX_COUNT,
            "Purchase would exceed max supply of MarfaMarks"
        );

        uint256 tokenId = totalSupply();
        if (tokenId < MAX_COUNT) {
            bytes32 mintHash = _psuedoRandomHash();
            tokenIdToMintHash[tokenId] = mintHash;

            tokenIdToPalette[tokenId] = Palette(
                color1,
                color2,
                color3,
                color4,
                color5
            );
            _safeMint(recipient, tokenId);
            emit Mint(tokenId, recipient);
        }
    }
}
/**
 * @title MarfaMarks contract
 * @dev Extends ERC721 Non-Fungible Token Standard basic implementation
 */
function getCodeClaimed(address user) external view returns (bool) {
    return codeClaimed[user];
}

// @dev Returns whether a user has claimed from presale