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

import { Semver } from "../../universal/Semver.sol";
import {
    ERC721BurnableUpgradeable
} from "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721BurnableUpgradeable.sol";
import { AttestationStation } from "./AttestationStation.sol";
import { OptimistAllowlist } from "./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, Semver {
    /**
     * @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;

    /**
     * @custom:semver 2.0.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
    ) Semver(2, 0, 0) {
        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 BaseURI for all tokens.
     */
    function baseURI() public view returns (string memory) {
        return
            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 Token URI for the given token by ID.
     */
    function tokenURI(uint256 _tokenId) public view virtual override returns (string memory) {
        return
            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 Whether or not the address is allowed to mint yet.
     */
    function isOnAllowList(address _recipient) public view returns (bool) {
        return 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");
    }
}
