// SPDX-License-Identifier: None
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "./ICustomMintable.sol";

contract xSGT is ERC721, ICustomMintable, AccessControl, Ownable
{
    using Counters for Counters.Counter;
    Counters.Counter private currentTokenId;

    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
    bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE");

    // Mapping from token ID to expire time
    mapping(uint256 => uint256) private _expireTimes;

    // Mapping from token ID to voting powers
    mapping(uint256 => uint256) private _votingPowers;

    // Mapping from address to list nft
    mapping(address => uint256[]) private _balanceTokens;

    // Mapping from lending token ID to  expire time
    mapping(uint256 => uint256) private _lendingExpireTimes;

    // Mapping from lending token ID to lending address
    mapping(uint256 => address) private _lendingTokens;

    // Mapping from address to list lending tokens
    mapping(address => uint256[]) private _lendingAddresses;

    /**
     * @dev Emitted when owner lended `tokenId` token  to `lender`.
     */
    event Lended(address indexed owner, address indexed lender, uint256 indexed tokenId , uint256 expires);
    /**
     * @dev Emitted when owner reclaim `tokenId` token  from `lender`.
     */
    event Claim(address indexed owner, uint256 indexed tokenId);

    constructor(address owner) ERC721('xSGT', 'xSGT')
    {
        if (owner == address(0)) {
            owner = msg.sender;
        }
        // Grant the contract deployer the default admin role: it will be able
        // to grant and revoke any roles
        _setupRole(DEFAULT_ADMIN_ROLE, owner);
        _setupRole(MINTER_ROLE, owner);
        _setupRole(BURNER_ROLE, owner);
        if (owner != msg.sender) {
            _setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
            // revoke later
        }
    }



    /**
     * @dev Returns the total voting power for an `address`.
     *
     */
    function getTotalVotingPower(address addr) public view virtual returns (uint256) {
        uint256[] memory tokens = _balanceTokens[addr];
        uint256 totalVp = 0;
        for (uint id = 0; id < tokens.length; id++) {
            uint256 vp = 0;
            if (!_isTokenLended(tokens[id])) {
                vp = _getVotingPower(tokens[id]);
            }
            totalVp = totalVp + vp;
        }
        // lending
        tokens = _lendingAddresses[addr];
        for (uint id = 0; id < tokens.length; id++) {
            if (_lendingExpireTimes[tokens[id]] >= block.timestamp) {
                totalVp = totalVp + _getVotingPower(tokens[id]);
            }
        }
        return totalVp;
    }

    /**
     * @dev Returns the total voting power for list `addresses`.
     */
    function getTotalVotingPowers(address[] memory addresses) external view virtual returns (uint256[] memory) {
        uint256[] memory ret = new uint256[](addresses.length);
        for (uint i = 0; i < addresses.length; i++) {
            address addr = addresses[i];
            ret[i] = getTotalVotingPower(addr);
        }
        return ret;
    }

    /**
     * @dev mint new token to address with fix voting power and expire time
     *
     * Requirements:
     *
     * - caller must have minter role
     */
    function mint(address to, uint256 votingPower, uint256 expires) external virtual override
    {
        require(hasRole(MINTER_ROLE, msg.sender), "Caller is not a minter");
        require(expires >= block.timestamp, "Invalid expire");
        // increase token id
        currentTokenId.increment();
        uint256 newItemId = currentTokenId.current();
        _safeMint(to, newItemId);
        // set voting power
        _votingPowers[newItemId] = votingPower;
        // set expire
        _expireTimes[newItemId] = expires;
        // add token -> address
        _balanceTokens[to].push(newItemId);

    }


    /**
     * @dev Returns the voting power for `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function getVotingPower(uint256 tokenId) external view virtual returns (uint256) {
        require(_exists(tokenId), "Query for nonexistent token");
        return _getVotingPower(tokenId);
    }

    /**
     * @dev Returns the balance token for `addr`
     *
     */
    function getBalanceToken(address addr) external view virtual returns (uint256[] memory) {
        return _balanceTokens[addr];
    }

    /**
     * @dev Returns the lending token for `addr`
     *
     */
    function getLendingAddressToken(address addr) external view virtual returns (uint256[] memory) {
        return _lendingAddresses[addr];
    }

    /**
     * @dev Returns the voting power for `tokenId` token (not validate exist token)
     *
     */
    function _getVotingPower(uint256 tokenId) internal view virtual returns (uint256) {
        if (_expireTimes[tokenId] >= block.timestamp) {
            return _votingPowers[tokenId];
        }
        else {
            return 0;
        }
    }

    /**
     * @dev Returns the expire time for `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function getExpireTime(uint256 tokenId) external view virtual returns (uint256) {
        require(_exists(tokenId), "Query for nonexistent token");
        return _expireTimes[tokenId];
    }

    /**
     * @dev burn a token id
     *
     * Requirements:
     *
     * - caller must have burner role
     */
    function burn(uint256 tokenId) external {
        require(hasRole(BURNER_ROLE, msg.sender), "Caller is not a burner");
        // burn
        _burn(tokenId);
    }

    /**
     * @dev get current token id
     */
    function getCurrentToken() external view virtual returns (uint256) {
        return currentTokenId.current();
    }

    /**
     * @dev Returns the check for `tokenId` token is lended or not
     *
     */
    function _isTokenLended(uint256 tokenId) internal view virtual returns (bool) {
        return _lendingTokens[tokenId] != address(0) && _lendingExpireTimes[tokenId] >= block.timestamp;
    }

    /**
     * @dev owner will let lender borrow token with expire time
     *
     * Requirements:
     *
     * - token must be belong to msg sender
     */
    function lend(address lender, uint256 tokenId, uint256 expires) external virtual
    {
        require(_exists(tokenId), "Query for nonexistent token");
        require(ownerOf(tokenId) == msg.sender, "token is not yours");
        require(!_isTokenLended(tokenId), "token was lended");
        require(expires >= block.timestamp, "Invalid expire");
        require(lender != msg.sender, "Invalid to address");

        // set voting power
        address preLender = _lendingTokens[tokenId];
        _lendingTokens[tokenId] = lender;
        // set expire
        _lendingExpireTimes[tokenId] = expires;
        // add token -> address : need to check if user lend multi time --> list must be a set
        if(preLender != address(0)) {
            // pre lender has expire
            if(preLender != lender) {
                _removeValueFromList(tokenId , _lendingAddresses[preLender]);
                _lendingAddresses[lender].push(tokenId);
            }
            // else lender lend again after expire --> already have in list , do nothing
        } else {
            _lendingAddresses[lender].push(tokenId);
        }
        emit Lended(msg.sender, lender, tokenId , expires);
    }

    /**
     * @dev owner take token from borrower
     *
     * Requirements:
     *
     * - token must be belong to msg sender
     */
    function claim(uint256 tokenId) external virtual
    {
        require(_exists(tokenId), "Query for nonexistent token");
        require(ownerOf(tokenId) == msg.sender, "token is not yours");
        _removeLending(tokenId);
        emit Claim(msg.sender, tokenId);
    }

    /**
     * @dev Hook that is called before any token transfer. This includes minting
     * and burning.
     *
     * Calling conditions:
     *
     * - When `from` and `to` are both non-zero, ``from``'s `tokenId` will be
     * transferred to `to`.
     * - When `from` is zero, `tokenId` will be minted for `to`.
     * - When `to` is zero, ``from``'s `tokenId` will be burned.
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     */
    function _beforeTokenTransfer(address from, address to, uint256 tokenId) internal virtual override {
        // check if token is lended or not
        require(!_isTokenLended(tokenId), "token was lended");
        if(to == address (0)) {
            // burn
            delete _expireTimes[tokenId];
            delete _votingPowers[tokenId];
            _removeValueFromList(tokenId , _balanceTokens[from]);
            //
            _removeLending(tokenId);
        } else if(from != address (0)) {
            // transfer
            _removeValueFromList(tokenId , _balanceTokens[from]);
            _balanceTokens[to].push(tokenId);
            //
            _removeLending(tokenId);
        }
        //
        super._beforeTokenTransfer(from , to , tokenId);
    }

    /**
     * @dev remove lending token
     *
     */
    function _removeLending(uint256 tokenId) internal virtual  {
        delete _lendingExpireTimes[tokenId];
        address lender = _lendingTokens[tokenId];
        _removeValueFromList(tokenId , _lendingAddresses[lender]);
        delete _lendingTokens[tokenId];
    }

    /**
     * @dev remove a value from an array
     *
     */
    function _removeValueFromList(uint256 value , uint256[] storage array) internal virtual {
        uint index = array.length;
        for (uint i = 0; i < array.length; i++) {
            if(array[i] == value) {
                index = i;
                break;
            }
        }
        if(index < array.length) {
            array[index] = array[array.length - 1];
            array.pop();
        }
    }

    function supportsInterface(bytes4 interfaceId) public view override(ERC721, AccessControl) returns (bool) {
        return super.supportsInterface(interfaceId);
    }
}
[{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"approved","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Claim","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"lender","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"expires","type":"uint256"}],"name":"Lended","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[],"name":"BURNER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MINTER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"burn","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"claim","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"getBalanceToken","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCurrentToken","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getExpireTime","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"getLendingAddressToken","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"getTotalVotingPower","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"addresses","type":"address[]"}],"name":"getTotalVotingPowers","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getVotingPower","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"lender","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"expires","type":"uint256"}],"name":"lend","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"votingPower","type":"uint256"},{"internalType":"uint256","name":"expires","type":"uint256"}],"name":"mint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"}]