contract ShiNFT is
Context,
Ownable,
ERC165,
IERC721,
IERC721Metadata,
IERC721Enumerable,
Pausable
{
    using SafeMath for uint256;
    using Address for address;
    using EnumerableSet for EnumerableSet.UintSet;
    using EnumerableMap for EnumerableMap.UintToAddressMap;
    using Strings for uint256;
    

    bytes4 private constant _ERC721_RECEIVED = 0x150b7a02;
    mapping(address => EnumerableSet.UintSet) private _holderTokens;
    EnumerableMap.UintToAddressMap private _tokenOwners;

    mapping(uint256 => address) private _tokenApprovals;

    mapping(address => mapping(address => bool)) private _operatorApprovals;
    mapping(address => bool) public whitelistedMinters;

    // Main
    // Name of the characters
    uint256 private constant Shinnosuke = 1;
    uint256 private constant BO = 2;
    uint256 private constant MASAO = 3;
    uint256 private constant NENE = 4;
    uint256 private constant TORU = 5;
    uint256 private constant SHIRO = 6;

    //Attributes Mapping
    // Map based on character and attibute.
    // Suppose for NENE with Attribute 1 ->  attributes[4] = 1
    // This will change the atrrribute of NENE to have attribute 1 globally
    mapping(uint256 => uint[]) public attributes;

    //Token specific attributes
    mapping(uint => uint[]) public tokenAttributes;

    // Character specific pricing
    uint256 public globalMintPrice = 8 * 10**16;
    mapping(uint => uint) public prices;

    // Sum Number of Characters minted
    mapping(uint => uint) public totalMinted;

    // Token Mapping of the Character
    mapping(uint => uint) public characterTokenMapping;
    
    string private _name;
    string private _symbol;
    mapping(uint256 => string) internal _tokenURIs;

    bytes4 private constant _INTERFACE_ID_ERC721 = 0x80ac58cd;
    bytes4 private constant _INTERFACE_ID_ERC721_ENUMERABLE = 0x780e9d63;

    constructor(string memory name_, string memory symbol_) {
        _name = name_;
        _symbol = symbol_;

        
        characterTokenMapping[Shinnosuke] = 0;
        characterTokenMapping[BO] = 100;
        characterTokenMapping[MASAO] = 200;
        characterTokenMapping[NENE] = 300;
        characterTokenMapping[TORU] = 400;
        characterTokenMapping[SHIRO] = 500;

    }

    //ERC 165 Support
    
    function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
        return
        interfaceId == type(IERC721).interfaceId ||
    interfaceId == type(IERC721Metadata).interfaceId ||
    super.supportsInterface(interfaceId);
    }

    function balanceOf(address owner) public view override returns (uint256) {
        require(
            owner != address(0),
            "ERC721: balance query for the zero address"
        );

        return _holderTokens[owner].length();
    }

    function ownerOf(uint256 tokenId) public view override returns (address) {
        return
        _tokenOwners.get(
            tokenId,
            "ERC721: owner query for nonexistent token"
        );
    }

    function name() public view override returns (string memory) {
        return _name;
    }

    function symbol() public view override returns (string memory) {
        return _symbol;
    }
    

    function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
        require(
            _exists(tokenId),
            "ERC721Metadata: URI query for nonexistent token"
        );

        string memory _tokenURI = _tokenURIs[tokenId];

        return _tokenURI;

    }


    function tokenOfOwnerByIndex(address owner, uint256 index)
    public
    view
    override
    returns (uint256)
    {
        return _holderTokens[owner].at(index);
    }

    function totalSupply() public view override returns (uint256) {
        // _tokenOwners are indexed by tokenIds, so .length() returns the number of tokenIds
        return _tokenOwners.length();
    }

    function tokenByIndex(uint256 index)
    public
    view
    override
    returns (uint256)
    {
        (uint256 tokenId,) = _tokenOwners.at(index);
        return tokenId;
    }

    function setTokenAttribute(uint tokenId,  uint attribute) public onlyOwner {
        require(
            tokenId > 0 && tokenId <= totalSupply(),
            "ERC721: invalid token index"
        );

    

        tokenAttributes[tokenId].push(attribute);
    }

    function getTokenAttributes(uint tokenId) public view returns(uint[] memory) {
        require(
            tokenId > 0 && tokenId <= totalSupply(),
            "ERC721: invalid token index"
        );

        return tokenAttributes[tokenId];
    }

    function removeTokenAttribute(uint tokenId,  uint attributeIndex) public onlyOwner {
        require(
            tokenId > 0 && tokenId <= totalSupply(),
            "ERC721: invalid token index"
        );

        require(
            attributeIndex >= 0 && attributeIndex < tokenAttributes[tokenId].length,
            "Attribute Index is not valid"
        );

       

        for(uint i = attributeIndex; i < tokenAttributes[tokenId].length-1; i++) {
            tokenAttributes[tokenId][i] = tokenAttributes[tokenId][i+1];
        }
        tokenAttributes[tokenId].pop();
    }


    function setCharacterAttribute(uint characterIndex,  uint attribute) public onlyOwner {
        require(characterIndex > 0 && characterIndex <= 6, "Invalid character index");

        

        attributes[characterIndex].push(attribute);
    }

    function getCharacterAttributes(uint characterIndex) public view returns(uint[] memory) {
        require(characterIndex > 0 && characterIndex <= 6, "Invalid character index");

        return attributes[characterIndex];
    }


    function removeCharacterAttribute(uint characterIndex,  uint attributeIndex) public onlyOwner {
        require(characterIndex > 0 && characterIndex <= 6, "Invalid character index");

        require(
            attributeIndex >= 0 && attributeIndex < attributes[characterIndex].length,
            "Attribute Index is not valid"
        );

        
        

        for(uint i = attributeIndex; i < attributes[characterIndex].length-1; i++) {
            attributes[characterIndex][i] = attributes[characterIndex][i+1];
        }

        attributes[characterIndex].pop();
    }


    function getTotalMinted() public view returns(uint){
        uint total = 0;

        total += totalMinted[Shinnosuke];
        total += totalMinted[BO];
        total += totalMinted[MASAO];
        total += totalMinted[NENE];
        total += totalMinted[TORU];
        total += totalMinted[SHIRO];

        return total;
    }



    function approve(address to, uint256 tokenId) public virtual override {
        address owner = ownerOf(tokenId);
        require(to != owner, "ERC721: approval to current owner");

        require(
            _msgSender() == owner || isApprovedForAll(owner, _msgSender()),
            "ERC721: approve caller is not owner nor approved for all"
        );

        _approve(to, tokenId);
    }

    function getApproved(uint256 tokenId)
    public
    view
    override
    returns (address)
    {
        require(
            _exists(tokenId),
            "ERC721: approved query for nonexistent token"
        );

        return _tokenApprovals[tokenId];
    }

    function setApprovalForAll(address operator, bool approved)
    public
    virtual
    override
    {
        require(operator != _msgSender(), "ERC721: approve to caller");

        _operatorApprovals[_msgSender()][operator] = approved;
        emit ApprovalForAll(_msgSender(), operator, approved);
    }

    function isApprovedForAll(address owner, address operator)
    public
    view
    override
    returns (bool)
    {
        return _operatorApprovals[owner][operator];
    }

    function transferFrom(
        address from,
        address to,
        uint256 tokenId
    ) public virtual override {
        //solhint-disable-next-line max-line-length
        require(
            _isApprovedOrOwner(_msgSender(), tokenId),
            "ERC721: transfer caller is not owner nor approved"
        );

        _transfer(from, to, tokenId);
    }

    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId
    ) public virtual override {
        safeTransferFrom(from, to, tokenId, "");
    }

    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId,
        bytes memory _data
    ) public virtual override {
        require(
            _isApprovedOrOwner(_msgSender(), tokenId),
            "ERC721: transfer caller is not owner nor approved"
        );
        _safeTransfer(from, to, tokenId, _data);
    }

    function _safeTransfer(
        address from,
        address to,
        uint256 tokenId,
        bytes memory _data
    ) internal virtual {
        _transfer(from, to, tokenId);
    }

    function _exists(uint256 tokenId) internal view returns (bool) {
        return _tokenOwners.contains(tokenId);
    }

    function _isApprovedOrOwner(address spender, uint256 tokenId)
    internal
    view
    returns (bool)
    {
        require(
            _exists(tokenId),
            "ERC721: operator query for nonexistent token"
        );
        address owner = ownerOf(tokenId);
        return (spender == owner ||
        getApproved(tokenId) == spender ||
        isApprovedForAll(owner, spender));
    }
    
    function mintWhitelist(string calldata _tokenURI, uint characterIndex) public {
        require(paused() == false, "Minting is paused");
        require(whitelistedMinters[msg.sender], "Not whitelisted");
        require(characterIndex > 0 && characterIndex <= 6, "Invalid character index");
        require(totalMinted[characterIndex].add(1) <= 100, "Max 100 tokens per character");
        require(_holderTokens[msg.sender].length().add(1) <= 6, "Max 6 tokens per account");

        whitelistedMinters[msg.sender] = false;
        uint256 getID = characterTokenMapping[characterIndex].add(1);

        _safeMint(msg.sender, getID, "");
        _setTokenURI(getID, _tokenURI);
        totalMinted[characterIndex] += 1;
        characterTokenMapping[characterIndex].add(1);
    }

    function mintPublic(string calldata _tokenURI, uint characterIndex) public payable {
        require(paused() == false, "Minting is paused");
        require(characterIndex > 0 && characterIndex <= 6, "Invalid character index");
        require(totalMinted[characterIndex].add(1) <= 100, "Max 100 tokens per character");
        require(_holderTokens[msg.sender].length().add(1) <= 6, "Max 6 tokens per account");

        if(prices[characterIndex] == 0) {
            require(globalMintPrice <= msg.value, "Invalid price");
        } else {
            require(prices[characterIndex] <= msg.value, "Invalid price");
        }

        uint256 getID = characterTokenMapping[characterIndex].add(1);
        _safeMint(msg.sender, getID, "");
        _setTokenURI(getID, _tokenURI);
        totalMinted[characterIndex] += 1;
        characterTokenMapping[characterIndex] += 1;
    }

    function mintMultiplePublic(string[] calldata uris, uint characterIndex) public payable {
        require(paused() == false, "Minting is paused");
        require(characterIndex > 0 && characterIndex <= 6, "Invalid character index");
        require(totalMinted[characterIndex].add(uris.length) <= 100, "Max 100 tokens per character");
        require(_holderTokens[msg.sender].length().add(1) <= 6, "Max 6 tokens Adjnd per account");
        require(_holderTokens[msg.sender].length().add(uris.length) <= 6, "Max 6 tokens per account");

        if(prices[characterIndex] == 0) {
            require(globalMintPrice.mul(uris.length) >= msg.value, "Invalid price");
        } else {
            require(prices[characterIndex].mul(uris.length) >= msg.value, "Invalid price");
        }

        for (uint j = 0; j < uris.length; j++) {
            uint256 newItemId = characterTokenMapping[characterIndex].add(1);
            _safeMint(msg.sender, newItemId, "");
            _setTokenURI(newItemId, uris[j]);
            totalMinted[characterIndex] += 1;
            characterTokenMapping[characterIndex] += 1;
        }

    }

    function setGlobalMintPrice(uint256 _newPrice) external onlyOwner{
        globalMintPrice = _newPrice;
    }
    
    function setCharacterPrice(uint256 _newPrice, uint characterIndex) external onlyOwner{
        require(characterIndex > 0 && characterIndex <= 6, "Invalid character index");
        prices[characterIndex] = _newPrice;
    }

    function mint(address to, uint256 tokenId, string memory _tokenURI) external onlyOwner {
        _safeMint(to, tokenId, "");
        _setTokenURI(tokenId, _tokenURI);
    }

    function _safeMint(address to, uint256 tokenId) internal virtual {
        _safeMint(to, tokenId, "");
    }

    function _safeMint(
        address to,
        uint256 tokenId,
        bytes memory _data
    ) internal virtual {
        _mint(to, tokenId);
    }

    function _mint(address to, uint256 tokenId) internal virtual {
        require(to != address(0), "ERC721: mint to the zero address");
        require(!_exists(tokenId), "ERC721: token already minted");

        _beforeTokenTransfer(address(0), to, tokenId);

        _holderTokens[to].add(tokenId);

        _tokenOwners.set(tokenId, to);

        emit Transfer(address(0), to, tokenId);
    }

    function _burn(uint256 tokenId) internal virtual {
        address owner = ownerOf(tokenId);

        _beforeTokenTransfer(owner, address(0), tokenId);

        // Clear approvals
        _approve(address(0), tokenId);

        // Clear metadata (if any)
        if (bytes(_tokenURIs[tokenId]).length != 0) {
            delete _tokenURIs[tokenId];
        }

        _holderTokens[owner].remove(tokenId);

        _tokenOwners.remove(tokenId);

        emit Transfer(owner, address(0), tokenId);
    }
    
    function addWhitelist(address[] memory _accounts) external onlyOwner{
        for (uint256 i = 0; i < _accounts.length; i++) {
             whitelistedMinters[_accounts[i]] = true;
        }
    }
    
    function changeWhitelistState(address _address, bool _state) external onlyOwner{
        whitelistedMinters[_address] = _state;
    }
    

    function _transfer(
        address from,
        address to,
        uint256 tokenId
    ) internal virtual {
        require(
            ownerOf(tokenId) == from,
            "ERC721: transfer of token that is not own"
        );
        require(to != address(0), "ERC721: transfer to the zero address");

        _beforeTokenTransfer(from, to, tokenId);

        // Clear approvals from the previous owner
        _approve(address(0), tokenId);

        _holderTokens[from].remove(tokenId);
        _holderTokens[to].add(tokenId);

        _tokenOwners.set(tokenId, to);

        emit Transfer(from, to, tokenId);
    }

    function _setTokenURI(uint256 tokenId, string memory _tokenURI)
    internal
    virtual
    {
        require(
            _exists(tokenId),
            "ERC721Metadata: URI set of nonexistent token"
        );
        _tokenURIs[tokenId] = _tokenURI;
    }

    function pauseMint() external onlyOwner{
        _pause();
    }

    function unPauseMint() external onlyOwner{
        _unpause();
    }

    

    function withdrawMoney() external onlyOwner{
        payable(msg.sender).transfer(address(this).balance);
    }

    function _approve(address to, uint256 tokenId) internal {
        _tokenApprovals[tokenId] = to;
        emit Approval(ownerOf(tokenId), to, tokenId);
    }

    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 tokenId
    ) internal virtual {}
}
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
    return
    interfaceId == type(IERC721).interfaceId ||
interfaceId == type(IERC721Metadata).interfaceId ||
super.supportsInterface(interfaceId);
}

//ERC 165 Support