// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;

import { ISemver } from "src/universal/ISemver.sol";
import { ERC721BurnableUpgradeable } from
    "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721BurnableUpgradeable.sol";
import { AttestationStation } from "src/periphery/op-nft/AttestationStation.sol";
import { OptimistAllowlist } from "src/periphery/op-nft/OptimistAllowlist.sol";
import { Strings } from "@openzeppelin/contracts/utils/Strings.sol";

/// @author Optimism Collective
/// @author Gitcoin
/// @title  Optimist
/// @notice A Soul Bound Token for real humans only(tm).
contract Optimist is ERC721BurnableUpgradeable, ISemver {
    /// @notice Attestation key used by the attestor to attest the baseURI.
    bytes32 public constant BASE_URI_ATTESTATION_KEY = bytes32("optimist.base-uri");

    /// @notice Attestor who attests to baseURI.
    address public immutable BASE_URI_ATTESTOR;

    /// @notice Address of the AttestationStation contract.
    AttestationStation public immutable ATTESTATION_STATION;

    /// @notice Address of the OptimistAllowlist contract.
    OptimistAllowlist public immutable OPTIMIST_ALLOWLIST;

    /// @notice Semantic version.
    /// @custom:semver 2.1.0
    string public constant version = "2.1.0";

    /// @param _name               Token name.
    /// @param _symbol             Token symbol.
    /// @param _baseURIAttestor    Address of the baseURI attestor.
    /// @param _attestationStation Address of the AttestationStation contract.
    /// @param _optimistAllowlist  Address of the OptimistAllowlist contract
    constructor(
        string memory _name,
        string memory _symbol,
        address _baseURIAttestor,
        AttestationStation _attestationStation,
        OptimistAllowlist _optimistAllowlist
    ) {
        BASE_URI_ATTESTOR = _baseURIAttestor;
        ATTESTATION_STATION = _attestationStation;
        OPTIMIST_ALLOWLIST = _optimistAllowlist;
        initialize(_name, _symbol);
    }

    /// @notice Initializes the Optimist contract.
    /// @param _name   Token name.
    /// @param _symbol Token symbol.
    function initialize(string memory _name, string memory _symbol) public initializer {
        __ERC721_init(_name, _symbol);
        __ERC721Burnable_init();
    }

    /// @notice Allows an address to mint an Optimist NFT. Token ID is the uint256 representation
    ///         of the recipient's address. Recipients must be permitted to mint, eventually anyone
    ///         will be able to mint. One token per address.
    /// @param _recipient Address of the token recipient.
    function mint(address _recipient) public {
        require(isOnAllowList(_recipient), "Optimist: address is not on allowList");
        _safeMint(_recipient, tokenIdOfAddress(_recipient));
    }

    /// @notice Returns the baseURI for all tokens.
    /// @return uri_ BaseURI for all tokens.
    function baseURI() public view returns (string memory uri_) {
        uri_ = string(
            abi.encodePacked(
                ATTESTATION_STATION.attestations(BASE_URI_ATTESTOR, address(this), bytes32("optimist.base-uri"))
            )
        );
    }

    /// @notice Returns the token URI for a given token by ID
    /// @param _tokenId Token ID to query.
    /// @return uri_ Token URI for the given token by ID.
    function tokenURI(uint256 _tokenId) public view virtual override returns (string memory uri_) {
        uri_ = string(
            abi.encodePacked(
                baseURI(),
                "/",
                // Properly format the token ID as a 20 byte hex string (address).
                Strings.toHexString(_tokenId, 20),
                ".json"
            )
        );
    }

    /// @notice Checks OptimistAllowlist to determine whether a given address is allowed to mint
    ///         the Optimist NFT. Since the Optimist NFT will also be used as part of the
    ///         Citizens House, mints are currently restricted. Eventually anyone will be able
    ///         to mint.
    /// @return allowed_ Whether or not the address is allowed to mint yet.
    function isOnAllowList(address _recipient) public view returns (bool allowed_) {
        allowed_ = OPTIMIST_ALLOWLIST.isAllowedToMint(_recipient);
    }

    /// @notice Returns the token ID for the token owned by a given address. This is the uint256
    ///         representation of the given address.
    /// @return Token ID for the token owned by the given address.
    function tokenIdOfAddress(address _owner) public pure returns (uint256) {
        return uint256(uint160(_owner));
    }

    /// @notice Disabled for the Optimist NFT (Soul Bound Token).
    function approve(address, uint256) public pure override {
        revert("Optimist: soul bound token");
    }

    /// @notice Disabled for the Optimist NFT (Soul Bound Token).
    function setApprovalForAll(address, bool) public virtual override {
        revert("Optimist: soul bound token");
    }

    /// @notice Prevents transfers of the Optimist NFT (Soul Bound Token).
    /// @param _from Address of the token sender.
    /// @param _to   Address of the token recipient.
    function _beforeTokenTransfer(address _from, address _to, uint256) internal virtual override {
        require(_from == address(0) || _to == address(0), "Optimist: soul bound token");
    }
}
