// SPDX-License-Identifier: MIT
        pragma solidity ^0.8.24;
        
        import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
        
        contract DandiToken is ERC20 {
            uint256 public constant INITIAL_SUPPLY = 1000000 * 10**18; // Initial supply of 1 million tokens with 18 decimals
            uint256 public releaseAmount = 1000 * 10**18; // Release rate of 1000 tokens per release period
            uint256 public constant START_TIME = 1718078400; // Start time for the token release mechanism
            uint256 public lastReleaseTime; // Timestamp of the last token release
            uint256 public releaseInterval = 23 hours + 58 minutes; // Release interval of 1 day
            uint256 public constant TARA_MIN = 100 ether; // TARA holdings needed to submit a vote
        
            uint256 public accumulatedTokens; // Accumulated tokens not yet released
            uint256 public totalReleasedTokens; // Total tokens released so far
        
            // Make secretHash private to prevent it from being accessed publicly
            bytes32 private secretHash; // Hash of the secret used to verify votes
        
            event TokensReleased(uint256 amount);
            event VoteSubmitted(address voter, address candidate, bytes32 memeID);
            event TokensDistributed(uint256 amountToWinner, uint256 amountToVoters);
            event MemeIDApproved(bytes32 memeID);
            event MemeIDRemoved(bytes32 memeID);
        
            struct Vote {
                address voter;
                address candidate;
                bytes32 memeID;
            }
        
            mapping(bytes32 => uint256) public memeVoteCount;
            mapping(address => bool) public hasVoted;
            mapping(bytes32 => address) public memeToCandidate;
            mapping(bytes32 => bool) public approvedMemeIDs; // Mapping to check if a memeID is approved
        
            bytes32[] public uniqueMemeIDs;
            Vote[] public votes;
        
            // Constructor to initialize the contract
            constructor(bytes32 _secretHash) ERC20("DandiToken", "DANDI") {
                _mint(address(this), INITIAL_SUPPLY); // Mint the initial supply of tokens to the contract itself
                lastReleaseTime = START_TIME; // Set the initial last release time
                totalReleasedTokens = 0; // Initialize total released tokens
                secretHash = _secretHash; // Store the secret hash
            }
        
            // Function to approve multiple memeIDs
            function approveMemeIDs(bytes32[] memory memeIDs, bytes32 secret) public {
                require(secret == secretHash, "Invalid secret");
                for (uint256 i = 0; i < memeIDs.length; i++) {
                    approvedMemeIDs[memeIDs[i]] = true; // Mark the memeID as approved
                    emit MemeIDApproved(memeIDs[i]); // Emit a memeID approved event
                }
            }
        
            // Function to remove multiple approved memeIDs
            function removeMemeIDs(bytes32[] memory memeIDs, bytes32 secret) public {
                require(secret == secretHash, "Invalid secret");
                for (uint256 i = 0; i < memeIDs.length; i++) {
                    require(approvedMemeIDs[memeIDs[i]], "MemeID not approved");
                    delete approvedMemeIDs[memeIDs[i]]; // Remove the memeID from the approved list
                    emit MemeIDRemoved(memeIDs[i]); // Emit a memeID removed event
                }
            }
        
            // Function to remove all approved memeIDs
            function removeAllMemeIDs(bytes32 secret) public {
                require(secret == secretHash, "Invalid secret");
        
                // Create an array to store the meme IDs to remove
                bytes32[] memory memeIDsToRemove = new bytes32[](uniqueMemeIDs.length);
                uint256 count = 0;
        
                // Iterate over uniqueMemeIDs to find approved meme IDs
                for (uint256 i = 0; i < uniqueMemeIDs.length; i++) {
                    bytes32 memeID = uniqueMemeIDs[i];
                    if (approvedMemeIDs[memeID]) {
                        memeIDsToRemove[count] = memeID;
                        count++;
                    }
                }
        
                // Remove the approved meme IDs
                for (uint256 i = 0; i < count; i++) {
                    delete approvedMemeIDs[memeIDsToRemove[i]]; // Remove the memeID from the approved list
                    emit MemeIDRemoved(memeIDsToRemove[i]); // Emit a memeID removed event
                }
            }
        
            // Function to submit a vote
            function submitVote(address candidate, bytes32 memeID) public {
                uint256 requiredBalance = TARA_MIN; // Required TARA balance to submit a vote
                require(msg.sender.balance >= requiredBalance, "Insufficient TARA balance to vote");
                require(!hasVoted[msg.sender], "You have already voted");
                require(approvedMemeIDs[memeID], "MemeID not approved for voting");
        
                votes.push(Vote({voter: msg.sender, candidate: candidate, memeID: memeID})); // Store the vote
                hasVoted[msg.sender] = true; // Mark the voter as having voted
                memeVoteCount[memeID] += 1; // Increment the vote count for the meme
        
                if (memeToCandidate[memeID] == address(0)) {
                    memeToCandidate[memeID] = candidate; // Associate the meme ID with the candidate
                    uniqueMemeIDs.push(memeID); // Add the meme ID to the list of unique meme IDs
                }
        
                emit VoteSubmitted(msg.sender, candidate, memeID); // Emit a vote submitted event
            }
        
            // Function to release tokens based on voting results, restricted to the owner
            function releaseTokens() public {
                uint256 currentTime = block.timestamp;
                uint256 elapsedTime = currentTime - lastReleaseTime;
        
                //Restrict to once a day but allow a 2 minute grace period
                require(elapsedTime >= releaseInterval, "Can only release tokens once a day");
        
                bytes32 winningMemeID = determineWinningMemeID(); // Determine the winning meme ID
                address winner = memeToCandidate[winningMemeID]; // Get the candidate associated with the winning meme
                uint256 tokensToRelease = releaseAmount + accumulatedTokens; // Include accumulated tokens
        
                if (votes.length == 0 || winner == address(0)) {
                    accumulatedTokens += releaseAmount; // Accumulate tokens if no votes or no valid winner
                } else {
                    uint256 amountToWinner = tokensToRelease / 2;
                    uint256 amountToVoters = tokensToRelease - amountToWinner;
        
                    super._transfer(address(this), winner, amountToWinner); // Transfer tokens to the winner
                    distributeTokensToVoters(amountToVoters); // Distribute tokens to the voters
        
                    accumulatedTokens = 0; // Reset accumulated tokens
                    totalReleasedTokens += tokensToRelease; // Update total released tokens
        
                    emit TokensReleased(tokensToRelease); // Emit tokens released event
                    emit TokensDistributed(amountToWinner, amountToVoters); // Emit tokens distributed event
        
                    // Remove the winning memeID from the approved list
                    delete approvedMemeIDs[winningMemeID];
                    emit MemeIDRemoved(winningMemeID);
                }
                lastReleaseTime = currentTime; // Update the last release time
                resetVotes(); // Reset the voting state
            }
        
            // Function to determine the winning meme ID based on votes
            function determineWinningMemeID() internal view returns (bytes32) {
                bytes32 winningMemeID;
                uint256 highestVoteCount = 0;
                bool tie = false;
        
                for (uint256 i = 0; i < uniqueMemeIDs.length; i++) {
                    bytes32 memeID = uniqueMemeIDs[i];
                    uint256 memeVotes = memeVoteCount[memeID];
        
                    if (memeVotes > highestVoteCount) {
                        highestVoteCount = memeVotes;
                        winningMemeID = memeID;
                        tie = false;
                    } else if (memeVotes == highestVoteCount) {
                        tie = true;
                    }
                }
        
                return tie ? bytes32(0) : winningMemeID; // Return 0 if there's a tie, otherwise return the winning meme ID
            }
        
            // Function to distribute tokens to voters
            function distributeTokensToVoters(uint256 amount) internal {
                uint256 voterCount = votes.length;
                uint256 amountPerVoter = amount / voterCount;
        
                for (uint256 i = 0; i < voterCount; i++) {
                    super._transfer(address(this), votes[i].voter, amountPerVoter); // Transfer tokens to each voter
                }
            }
        
            // Function to reset the voting state
            function resetVotes() internal {
                // Reset hasVoted mapping for all voters
                for (uint256 i = 0; i < votes.length; i++) {
                    hasVoted[votes[i].voter] = false;
                }
        
                // Clear the votes array
                delete votes;
        
                // Clear the uniqueMemeIDs array and memeVoteCount mapping
                for (uint256 i = 0; i < uniqueMemeIDs.length; i++) {
                    delete memeVoteCount[uniqueMemeIDs[i]];
                    delete memeToCandidate[uniqueMemeIDs[i]];
                }
                delete uniqueMemeIDs;
            }
        
            // Function to get the current votes
            function getVotes() public view returns (Vote[] memory) {
                return votes; // Return the array of votes
            }
        
            // Function to get the total tokens left in the contract
            function totalTokensLeft() public view returns (uint256) {
                return INITIAL_SUPPLY - totalReleasedTokens; // Return the remaining tokens
            }
        
            // Override transfer function to disable direct transfers from the contract
            function transfer(address recipient, uint256 amount) public override returns (bool) {
                require(msg.sender != address(this), "Direct transfers from the contract are disabled");
                return super.transfer(recipient, amount);
            }
        
            // Override transferFrom function to disable direct transfers from the contract
            function transferFrom(address sender, address recipient, uint256 amount) public override returns (bool) {
                require(sender != address(this), "Direct transfers from the contract are disabled");
                return super.transferFrom(sender, recipient, amount);
            }
        }