contract SquadGallery is ERC721Burnable, Ownable, HasSecondarySaleFees {
    bool public allowedCreatorsOnly;
    uint256 public lastMintedId;
    bool public mintingBlocked;
    address payable public royaltyAddress;
    uint256 public royaltyBps;
    mapping(address => bool) public allowedCreators;
    mapping(uint256 => string) public ipfsJson;
    mapping(uint256 => bool) public isLocked;
    mapping(uint256 => address) public creator;

    event Mint(
        address indexed _creator,
        uint256 indexed tokenId,
        string ipfsHash
    );
    event Update(
        address indexed _creator,
        uint256 indexed TokenId,
        string ipfsHash
    );
    event PermanentURI(string _value, uint256 indexed _id);

    modifier onlyAllowedCreators() {
        if (allowedCreatorsOnly) {
            require(allowedCreators[msg.sender], "creator must be allowed");
        }
        _;
    }

    modifier mustBeCreator(uint256 tokenId) {
        require(msg.sender == creator[tokenId], "must be creator");
        _;
    }

    constructor(
        string memory name,
        string memory symbol,
        bool allowedCreatorsOnly_,
        address[] memory allowedCreators_
    ) public ERC721(name, symbol) Ownable() HasSecondarySaleFees() {
        allowedCreatorsOnly = allowedCreatorsOnly_;
        for (uint256 i = 0; i < allowedCreators_.length; ++i) {
            allowedCreators[allowedCreators_[i]] = true;
        }
    }

    function tokenURI(uint256 tokenId)
        public
        view
        override
        returns (string memory)
    {
        return string(abi.encodePacked("ipfs://", ipfsJson[tokenId]));
    }

    function tokenURIs(uint256[] memory tokenIds)
        public
        view
        returns (string[] memory)
    {
        uint256 resLen = tokenIds.length;
        string[] memory res = new string[](resLen);
        for (uint256 i = 0; i < resLen; i++) {
            res[i] = tokenURI(tokenIds[i]);
        }
        return res;
    }

    function creators(uint256[] memory tokenIds)
        public
        view
        returns (address[] memory)
    {
        uint256 resLen = tokenIds.length;
        address[] memory res = new address[](resLen);
        for (uint256 i = 0; i < resLen; i++) {
            res[i] = creator[tokenIds[i]];
        }
        return res;
    }

    // ----- creator functions -----
    function mint(string memory ipfsJson_, bool isLocked_)
        public
        onlyAllowedCreators
    {
        require(!mintingBlocked, "minting blocked");
        uint256 newTokenId = lastMintedId + 1;
        lastMintedId = newTokenId;

        ipfsJson[newTokenId] = ipfsJson_;
        isLocked[newTokenId] = isLocked_;
        creator[newTokenId] = msg.sender;

        _mint(msg.sender, newTokenId);
        emit Mint(msg.sender, newTokenId, ipfsJson_);

        if (isLocked_) {
            emit PermanentURI(tokenURI(newTokenId), newTokenId);
        }
    }

    function modifyIpfsJson(uint256 tokenId, string memory ipfsJson_)
        public
        mustBeCreator(tokenId)
    {
        require(!isLocked[tokenId], "must not be locked");
        ipfsJson[tokenId] = ipfsJson_;
        emit Update(msg.sender, tokenId, ipfsJson_);
    }

    function lockNft(uint256 tokenId) public mustBeCreator(tokenId) {
        isLocked[tokenId] = true;
        // see https://opensea.io/blog/announcements/decentralizing-nft-metadata-on-opensea/
        emit PermanentURI(tokenURI(tokenId), tokenId);
    }

    // ----- admin functions -----
    function setAllowedCreators(
        address[] memory allowedCreators_,
        bool isAllowed
    ) public onlyOwner {
        for (uint256 i = 0; i < allowedCreators_.length; ++i) {
            allowedCreators[allowedCreators_[i]] = isAllowed;
        }
    }

    function setMintingBlocked(bool isBlocked) public onlyOwner {
        mintingBlocked = isBlocked;
    }

    function setRoyaltyAddress(address payable newAddress) public onlyOwner {
        royaltyAddress = newAddress;
    }

    function setRoyaltyBps(uint256 newAmount) public onlyOwner {
        royaltyBps = newAmount;
    }

    // ---- royalties ---
    function getFeeRecipients(uint256 id)
        public
        view
        override
        returns (address payable[] memory)
    {
        require(_exists(id), "does not exist");
        address payable[] memory result = new address payable[](1);
        result[0] = royaltyAddress;
        return result;
    }

    function getFeeBps(uint256 id)
        public
        view
        override
        returns (uint256[] memory)
    {
        require(_exists(id), "does not exist");
        uint256[] memory result = new uint256[](1);
        result[0] = royaltyBps;
        return result;
    }
}
function getFeeRecipients(uint256 id)
    public
    view
    override
    returns (address payable[] memory)
{
    require(_exists(id), "does not exist");
    address payable[] memory result = new address payable[](1);
    result[0] = royaltyAddress;
    return result;
}

// ---- royalties ---