contract GenteFactory is FactoryERC721, Ownable {
    using Strings for string;

    event Transfer(
        address indexed from,
        address indexed to,
        uint256 indexed tokenId
    );

    address public proxyRegistryAddress;
    address public nftAddress;
    address public lootBoxNftAddress;
    string public baseURI = "https://www.lushus.art/collection/1/factory/";

    /*
     * Enforce the existence of only 100 OpenSea creatures.
     */
    uint256 GENTE_SUPPLY = 10000;

    /*
     * Three different options for minting Creatures (basic, premium, and gold).
     */
    uint256 NUM_OPTIONS = 4;
    uint256 SINGLE_GENTE_OPTION = 0;
    uint256 NICKLE_GENTE_PACK_OPTION = 1;
    uint256 DIME_GENTE_PACK_OPTION = 2;
    uint256 DUB_GENTE_PACK_OPTION = 3;

    constructor(address _proxyRegistryAddress, address _nftAddress) {
        proxyRegistryAddress = _proxyRegistryAddress;
        nftAddress = _nftAddress;

        fireTransferEvents(address(0), owner());
    }

    function name() override external pure returns (string memory) {
        return "8bit Homies Gente Presell";
    }

    function symbol() override external pure returns (string memory) {
        return "GENTE";
    }

    function supportsFactoryInterface() override public pure returns (bool) {
        return true;
    }

    function numOptions() override public view returns (uint256) {
        return NUM_OPTIONS;
    }

    function transferOwnership(address newOwner) override public onlyOwner {
        address _prevOwner = owner();
        super.transferOwnership(newOwner);
        fireTransferEvents(_prevOwner, newOwner);
    }

    function fireTransferEvents(address _from, address _to) private {
        for (uint256 i = 0; i < NUM_OPTIONS; i++) {
            emit Transfer(_from, _to, i);
        }
    }

    function mint(uint256 _optionId, address _toAddress) override public {
        // Must be sent from the owner proxy or owner.
        ProxyRegistry proxyRegistry = ProxyRegistry(proxyRegistryAddress);
        assert(
            address(proxyRegistry.proxies(owner())) == _msgSender() ||
                owner() == _msgSender() ||
                _msgSender() == lootBoxNftAddress
        );
        require(canMint(_optionId));

        Gente gente = Gente(nftAddress);
        if (_optionId == SINGLE_GENTE_OPTION) {
            gente.mintTo(_toAddress);
        } else if (_optionId == NICKLE_GENTE_PACK_OPTION) {
            for (
                uint256 i = 0;
                i < 5;
                i++
            ) {
                gente.mintTo(_toAddress);
            }
        }  else if (_optionId == NICKLE_GENTE_PACK_OPTION) {
            for (
                uint256 i = 0;
                i < 10;
                i++
            ) {
                gente.mintTo(_toAddress);
            }
        } else if (_optionId == DUB_GENTE_PACK_OPTION) {
            for (
                uint256 i = 0;
                i < 20;
                i++
            ) {
                gente.mintTo(_toAddress);
            }
        }
    }

    function canMint(uint256 _optionId) override public view returns (bool) {
        if (_optionId >= NUM_OPTIONS) {
            return false;
        }

        Gente gente = Gente(nftAddress);
        uint256 genteSupply = gente.totalSupply();

        uint256 numItemsAllocated = 0;
        if (_optionId == SINGLE_GENTE_OPTION) {
            numItemsAllocated = 1;
        } else if (_optionId == NICKLE_GENTE_PACK_OPTION) {
            numItemsAllocated = 5;
        } else if (_optionId == DIME_GENTE_PACK_OPTION) {
            numItemsAllocated = 10;
        } else if (_optionId == DUB_GENTE_PACK_OPTION) {
            numItemsAllocated = 20;
        }
        return genteSupply < (GENTE_SUPPLY - numItemsAllocated);
    }

    function tokenURI(uint256 _optionId) override external view returns (string memory) {
        return string(abi.encodePacked(baseURI, Strings.toString(_optionId)));
    }

    /**
     * Hack to get things to work automatically on OpenSea.
     * Use transferFrom so the frontend doesn't have to worry about different method names.
     */
    function transferFrom(
        address,
        address _to,
        uint256 _tokenId
    ) public {
        mint(_tokenId, _to);
    }

    /**
     * Hack to get things to work automatically on OpenSea.
     * Use isApprovedForAll so the frontend doesn't have to worry about different method names.
     */
    function isApprovedForAll(address _owner, address _operator)
        public
        view
        returns (bool)
    {
        if (owner() == _owner && _owner == _operator) {
            return true;
        }

        ProxyRegistry proxyRegistry = ProxyRegistry(proxyRegistryAddress);
        if (
            owner() == _owner &&
            address(proxyRegistry.proxies(_owner)) == _operator
        ) {
            return true;
        }

        return false;
    }

    /**
     * Hack to get things to work automatically on OpenSea.
     * Use isApprovedForAll so the frontend doesn't have to worry about different method names.
     */
    function ownerOf(uint256) public view returns (address _owner) {
        return owner();
    }
}
function transferFrom(
    address,
    address _to,
    uint256 _tokenId
) public {
    mint(_tokenId, _to);
}

/**
 * Hack to get things to work automatically on OpenSea.
 * Use transferFrom so the frontend doesn't have to worry about different method names.
 */