contract DIYM is ERC20 {
    struct NewMemberProposal
    {        
        mapping (address => bool) votesByAddress;
        address [] votesArray;
        bool hasPassedYet;
    }

    mapping (address => NewMemberProposal) public _newMemberProposals;

    event NewMint(address indexed _to);	
    event VotedFor(address indexed _from, address indexed _votedFor);

    /// @dev Initializes ERC20 currency with DIYM name and initial supply granted to the deploying account.
    constructor(uint256 initialSupply) ERC20("DIYM", "DIYM") {
        _mint(msg.sender, initialSupply);
    }

    /// @dev Tokens are not divisible.
    function decimals() public view virtual override returns (uint8) {
        return 0;
    }

    /** 
    * @dev Casts a "for" vote for a new member.
    * 
    * Emits VotedFor with voting member address and new member address
    *
    * Requirements:
    * - `newMember` must not currently have any tokens
    * - `newMember` must not have previously been inducted.
    * - caller of this function must have at least one token at the time of voting
    * - caller must not have already voted for this particular member    
    * 
    * @param newMember address of new proposed member    
    */
    function voteForNewMember(address newMember) public
    {
        //Make sure caller has at least one token that they can vote with
        require(balanceOf(msg.sender) > 0, "Must have at least 1 token to vote.");
        //Make sure member hasn't already been given a token
        require(balanceOf(newMember) == 0, "Proposed member already has a token and cannot be granted any more tokens.");
        //Make sure member hasn't already been voted in earlier (even if they gave their token away)
        require(!_newMemberProposals[newMember].hasPassedYet, "Proposed member has already been inducted.");
        //Make sure voting member hasn't already voted for this person
        require(_newMemberProposals[newMember].votesByAddress[msg.sender]==false, "Already voted for new member.");
        
        //Validation passed. Vote for this person
        _newMemberProposals[newMember].votesByAddress[msg.sender]=true;
        _newMemberProposals[newMember].votesArray.push(msg.sender);
        emit VotedFor(msg.sender, newMember);
        
    }
    
    /**
    * @dev Attempts to induct a new member. Induction process first tallies all tokens currently 
    * held by addresses who have voted for this member. If the token amount is more than half of the 
    * total supply the new member is inducted and minted a new token.
    * 
    * Emits NewMint with new member address
    * Emits Transfer with new member address
    *
    * Requirements:
    * `newMember` must not already have been inducted
    * `newMember` must not currently have any tokens
    * 
    * @param newMember address of new proposed member
    *
    * @return sufficientVotes true if member inducted. false otherwise
     */
    function inductNewMember(address newMember) public returns (bool sufficientVotes)
    {
        require(!_newMemberProposals[newMember].hasPassedYet, "Proposed member has already been inducted.");
        require(balanceOf(newMember) == 0, "Proposed member already has a token and cannot be granted any more tokens.");
        //Min number of votes for new member to be minted a new coin is > 50% of all token holders
        uint minVotesForApproval = (totalSupply() / 2) + 1;        
        uint numberOfTokensVotingForNewMember = getNumberOfTokensVotingForNewMember(newMember);
        require(numberOfTokensVotingForNewMember >= minVotesForApproval, "Not enough tokens to induct new member.");
        _newMemberProposals[newMember].hasPassedYet = true;
        _mint(newMember, 1);
        emit NewMint(newMember);
        return true;
    }

    /**
    * @dev Tallies all tokens currently held by addresses who have voted for 
    * this member.
    *
    * @param newMember address of new proposed member
    *
    * @return totalTokensVotingFor sum of all token holdings by addresses who have voted for this member
     */
    function getNumberOfTokensVotingForNewMember(address newMember) public view returns (uint totalTokensVotingFor)
    {
        address [] memory votesArray = _newMemberProposals[newMember].votesArray;
        for(uint i=0; i<votesArray.length; i++)
        {
            totalTokensVotingFor += balanceOf(votesArray[i]);
        }
        
        return totalTokensVotingFor;
    }
}
/**
 * @title DIYM - Official ERC20 compatible token of the MBA
 * @author SW
 * @notice Each token holder is an official member of the MBA. The initial supply of token holders is 7 as of June 7, 2021.
 * Tokens are ERC20 compatible, and may be transferred to other wallets. Multiple tokens may be held by one address.
 * Induction of new members requires a majority of votes (represented one vote per token). 
 * If a new member is successfully inducted, a new DIYM coin will be minted and sent to that member's address.
 * @dev Note this inherits from the openzeppelin v 4.1.0 ERC20 contract
 * Initial supply is determined by constructor parameters. After the initial supply, the initial token holder should transfer individual tokens to individual members or remain the custodian of these tokens.
 * Voting process works in the following manner:
 *     1. A token holder calls the voteForNewMember method with the proposed member's address. 
 *      This stores a new proposed member instance, which keeps track of the current "for" votes for the member ("against" votes are not supported)
 *     2. At any point in time anyone can call the inductNewMember method which will tally up "for" votes for the member to determine if enough "for" votes have been cast (more than half).
 *      Tallying sums up all of the current holdings of addresses that have voted for the new member (addresses may have 0, 1, or more tokens as token transfers may have occured after voting).
 *      If more than half of the total supply of tokens have voted for the new member, that member will be minted a new token.
 */
function voteForNewMember(address newMember) public
{
    //Make sure caller has at least one token that they can vote with
    require(balanceOf(msg.sender) > 0, "Must have at least 1 token to vote.");
    //Make sure member hasn't already been given a token
    require(balanceOf(newMember) == 0, "Proposed member already has a token and cannot be granted any more tokens.");
    //Make sure member hasn't already been voted in earlier (even if they gave their token away)
    require(!_newMemberProposals[newMember].hasPassedYet, "Proposed member has already been inducted.");
    //Make sure voting member hasn't already voted for this person
    require(_newMemberProposals[newMember].votesByAddress[msg.sender]==false, "Already voted for new member.");
        
    //Validation passed. Vote for this person
    _newMemberProposals[newMember].votesByAddress[msg.sender]=true;
    _newMemberProposals[newMember].votesArray.push(msg.sender);
    emit VotedFor(msg.sender, newMember);
        
}

/** 
 * @dev Casts a "for" vote for a new member.
 * 
 * Emits VotedFor with voting member address and new member address
 *
 * Requirements:
 * - `newMember` must not currently have any tokens
 * - `newMember` must not have previously been inducted.
 * - caller of this function must have at least one token at the time of voting
 * - caller must not have already voted for this particular member    
 * 
 * @param newMember address of new proposed member    
 */