// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.13;

import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";


error NotApprovedContract(address sender);
error TransferNotAllowed(uint256 tokenId);
error OnlyMotherContract(address sender, address _motherContract);

abstract contract CalculateId {
    function calculateId(uint256 index, bytes4 selector, address votingContract, address minter) public pure returns (uint256) {
        return uint256(bytes32(abi.encodePacked(
            uint32(bytes4(bytes32(index) << 224)),
            bytes12(bytes20(minter)),
            selector,
            bytes12(bytes20(votingContract)))));
    }
}

interface IPlaygroundVotingBadge is IERC721{
    function mint(address to, uint256 index, bytes4 selector, address votingContract) external; 
    function exists(uint256 tokenId) external view returns(bool);
    function balanceOfSignature(address owner, bytes4 selector) external returns(uint256);
    function changeEnableTradingThreshold(uint256 newThreshold) external;
    function changeTradingEnabledGlobally(bool enable) external;
    function approveContract(address newContract, bool approval) external returns(bool);
}

contract PlaygroundVotingBadge is 
CalculateId, ERC721 {

    mapping(address=>bool) public isApprovedContract;
    mapping(address=>bool) public tradingEnabled;
    address public motherContract;
    uint256 public enableTradingThreshold;
    bool public tradingEnabledGlobally;

    mapping(address=>mapping(bytes4=>uint256)) private _balanceOfSignature;

    // e.g. name and symbol = "Playground Voting Badge", "VOT"
    constructor(string memory name, string memory symbol) ERC721(name, symbol){
        motherContract = msg.sender;
        isApprovedContract[msg.sender] = true;
    }

    function balanceOfSignature(address owner, bytes4 selector) public view returns(uint256 balance) {
        balance = _balanceOfSignature[owner][selector];
    } 

    // minting can only happen through approved contracts
    function mint(address to, uint256 index, bytes4 selector, address votingContract) 
    external 
    onlyApprovedContract
    {
        uint256 tokenId = calculateId(index, selector, votingContract, to);

        _mint(to, tokenId);
        
    }

    function exists(uint256 tokenId) external view returns (bool) {
        return _exists(tokenId);
    }

    function _afterTokenTransfer(
        address from,
        address to,
        uint256 tokenId
    ) internal override(ERC721) {
        bytes4 selector = bytes4(bytes32(tokenId) << 128);
        if (from==address(0)){
            // minting
            _balanceOfSignature[to][selector] += 1;
        } else {
            // burning or transfer
            uint addAmount = (to==address(0)) ? 0 : 1;
            _balanceOfSignature[to][selector] += addAmount;
            _balanceOfSignature[from][selector] -= 1;
        }
    }
    

    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 tokenId
    ) internal override(ERC721) {
        if(from!=address(0) && to!=address(0) && !_transferAllowed(tokenId)){
            revert TransferNotAllowed(tokenId);
        }
    }


    function _transferAllowed(uint256 tokenId) internal returns(bool){
        // you need to have at least a certain amount of voting badges
        // transferral must be enabled
        if (!tradingEnabled[msg.sender] && balanceOf(ownerOf(tokenId)) >= enableTradingThreshold){
            // it's sufficient to be above the threshold once.
            tradingEnabled[msg.sender] = true;
        }

        return tradingEnabled[msg.sender] && tradingEnabledGlobally;
    }

    
    function changeEnableTradingThreshold(uint256 newThreshold) 
    external 
    onlyByCallFromMotherContract
    returns(bool)
    {
        enableTradingThreshold = newThreshold;
    }

    function changeTradingEnabledGlobally(bool enable)
    external
    onlyByCallFromMotherContract
    returns(bool)
    {
        tradingEnabledGlobally = enable;
    }

    function approveContract(address newContract, bool approval) 
    external
    onlyByCallFromMotherContract 
    returns(bool)
    {
        isApprovedContract[newContract] = approval;
    }

    
    modifier onlyByCallFromMotherContract {
        if(msg.sender!=motherContract) {
            revert OnlyMotherContract(msg.sender, motherContract);
        }
        _;
    }


    modifier onlyApprovedContract {
        if(!isApprovedContract[msg.sender]) {
            revert NotApprovedContract(msg.sender);
        }
        _;
    }
}
[{"inputs":[{"internalType":"address","name":"votingContractRegistry","type":"address"},{"internalType":"bytes5[]","name":"flagAndSelectors","type":"bytes5[]"},{"internalType":"address[]","name":"votingContracts","type":"address[]"},{"internalType":"uint256[]","name":"minDurations","type":"uint256[]"},{"internalType":"uint256[]","name":"minQuorums","type":"uint256[]"},{"internalType":"bool[]","name":"badgeWeightedVote","type":"bool[]"},{"internalType":"bytes32","name":"hashedBadgeBytecode","type":"bytes32"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"uint256","name":"mainBadge","type":"uint256"},{"internalType":"uint256","name":"numberOfBadges","type":"uint256"}],"name":"BadgeDoesntExist","type":"error"},{"inputs":[{"internalType":"address","name":"imposter","type":"address"}],"name":"IsNotImplementer","type":"error"},{"inputs":[{"internalType":"bytes4","name":"selector","type":"bytes4"}],"name":"IsNotVotableFunction","type":"error"},{"inputs":[{"internalType":"address","name":"implementer","type":"address"}],"name":"OnlyVoteImplementer","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"index","type":"uint256"},{"indexed":false,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"bytes","name":"target","type":"bytes"}],"name":"VotingInstanceStarted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bool","name":"success","type":"bool"},{"indexed":false,"internalType":"bytes","name":"response","type":"bytes"}],"name":"WhatsNow","type":"event"},{"inputs":[],"name":"VOTING_REGISTRY","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"votingContract","type":"address"}],"name":"addSimpleVotingContract","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"analytics","outputs":[{"internalType":"uint256","name":"numberOfInstances","type":"uint256"},{"internalType":"uint256","name":"numberOfVotes","type":"uint256"},{"internalType":"uint256","name":"numberOfImplementations","type":"uint256"},{"internalType":"uint24","name":"numberOfSimpleVotingContracts","type":"uint24"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approveERC20Token","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"approveNFT","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"badges","outputs":[{"internalType":"contract IPlaygroundVotingBadge","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"index","type":"uint256"},{"internalType":"bytes4","name":"selector","type":"bytes4"},{"internalType":"address","name":"votingContract","type":"address"},{"internalType":"address","name":"minter","type":"address"}],"name":"calculateId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes4","name":"selector","type":"bytes4"},{"internalType":"address","name":"newVotingContract","type":"address"}],"name":"changeAssignedContract","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"by","type":"uint256"}],"name":"changeCounter","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"selector","type":"bytes4"},{"internalType":"uint256","name":"minDuration","type":"uint256"},{"internalType":"uint256","name":"minQuorum","type":"uint256"},{"internalType":"address","name":"token","type":"address"}],"name":"changeMetaParameters","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"enum Operation","name":"newOperation","type":"uint8"}],"name":"changeOperation","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"counter","outputs":[{"internalType":"uint256","name":"counter","type":"uint256"},{"internalType":"enum Operation","name":"operation","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"salt","type":"bytes32"},{"internalType":"bytes","name":"bytecode","type":"bytes"},{"internalType":"address","name":"badger","type":"address"}],"name":"deployNewBadge","outputs":[{"internalType":"address","name":"deployedContract","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"salt","type":"bytes32"},{"internalType":"bytes","name":"bytecode","type":"bytes"}],"name":"deployNewContract","outputs":[{"internalType":"address","name":"deployedContract","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"deployedContracts","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"donationsBy","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"name":"fixedVotingContract","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes4","name":"selector","type":"bytes4"}],"name":"getAssignedContract","outputs":[{"internalType":"address","name":"_assignedContract","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"office","type":"string"}],"name":"getIncumbentFromOffice","outputs":[{"internalType":"address","name":"incumbent","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"identifier","type":"uint256"},{"internalType":"bytes","name":"callback","type":"bytes"}],"name":"implement","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"instances","outputs":[{"internalType":"uint256","name":"identifier","type":"uint256"},{"internalType":"address","name":"votingContract","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"minXpToStartAnything","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"name":"minXpToStartThisFunction","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"office","type":"string"},{"internalType":"address","name":"_newIncumbent","type":"address"}],"name":"newIncumbent","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"nftAndBadgesInfo","outputs":[{"internalType":"uint256","name":"mainBadge","type":"uint256"},{"internalType":"bool","name":"acceptingNFTs","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"offices","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"address","name":"from","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"onERC721Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"sendERC20Token","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"sendNFT","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address payable","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"sendNativeToken","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"accept","type":"bool"}],"name":"setAcceptingNFTs","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newThreshold","type":"uint256"}],"name":"setEnableTradingThreshold","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newMainBadge","type":"uint256"}],"name":"setMainBadge","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newXP","type":"uint256"}],"name":"setMinXpToStartAnything","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"selector","type":"bytes4"},{"internalType":"uint256","name":"newXP","type":"uint256"}],"name":"setMinXpToStartThisFunction","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"enable","type":"bool"}],"name":"setTradingEnabledGlobally","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint24","name":"","type":"uint24"}],"name":"simpleVotingContract","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"votingParams","type":"bytes"},{"internalType":"bytes","name":"callback","type":"bytes"}],"name":"start","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"identifier","type":"uint256"},{"internalType":"bytes","name":"votingData","type":"bytes"}],"name":"vote","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"name":"votingMetaParams","outputs":[{"internalType":"uint256","name":"minDuration","type":"uint256"},{"internalType":"uint256","name":"minQuorum","type":"uint256"},{"internalType":"address","name":"token","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"contractAddress","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"wildCard","outputs":[],"stateMutability":"payable","type":"function"},{"stateMutability":"payable","type":"receive"}]