// File: MerkleProof.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.2) (utils/cryptography/MerkleProof.sol)

pragma solidity ^0.8.0;

/**
 * @dev These functions deal with verification of Merkle Tree proofs.
 *
 * The tree and the proofs can be generated using our
 * https://github.com/OpenZeppelin/merkle-tree[JavaScript library].
 * You will find a quickstart guide in the readme.
 *
 * WARNING: You should avoid using leaf values that are 64 bytes long prior to
 * hashing, or use a hash function other than keccak256 for hashing leaves.
 * This is because the concatenation of a sorted pair of internal nodes in
 * the merkle tree could be reinterpreted as a leaf value.
 * OpenZeppelin's JavaScript library generates merkle trees that are safe
 * against this attack out of the box.
 */
library MerkleProof {
    /**
     * @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
     * defined by `root`. For this, a `proof` must be provided, containing
     * sibling hashes on the branch from the leaf to the root of the tree. Each
     * pair of leaves and each pair of pre-images are assumed to be sorted.
     */
    function verify(bytes32[] memory proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {
        return processProof(proof, leaf) == root;
    }

    /**
     * @dev Calldata version of {verify}
     *
     * _Available since v4.7._
     */
    function verifyCalldata(bytes32[] calldata proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {
        return processProofCalldata(proof, leaf) == root;
    }

    /**
     * @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
     * from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
     * hash matches the root of the tree. When processing the proof, the pairs
     * of leafs & pre-images are assumed to be sorted.
     *
     * _Available since v4.4._
     */
    function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) {
        bytes32 computedHash = leaf;
        for (uint256 i = 0; i < proof.length; i++) {
            computedHash = _hashPair(computedHash, proof[i]);
        }
        return computedHash;
    }

    /**
     * @dev Calldata version of {processProof}
     *
     * _Available since v4.7._
     */
    function processProofCalldata(bytes32[] calldata proof, bytes32 leaf) internal pure returns (bytes32) {
        bytes32 computedHash = leaf;
        for (uint256 i = 0; i < proof.length; i++) {
            computedHash = _hashPair(computedHash, proof[i]);
        }
        return computedHash;
    }

    /**
     * @dev Returns true if the `leaves` can be simultaneously proven to be a part of a merkle tree defined by
     * `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
     *
     * CAUTION: Not all merkle trees admit multiproofs. See {processMultiProof} for details.
     *
     * _Available since v4.7._
     */
    function multiProofVerify(
        bytes32[] memory proof,
        bool[] memory proofFlags,
        bytes32 root,
        bytes32[] memory leaves
    ) internal pure returns (bool) {
        return processMultiProof(proof, proofFlags, leaves) == root;
    }

    /**
     * @dev Calldata version of {multiProofVerify}
     *
     * CAUTION: Not all merkle trees admit multiproofs. See {processMultiProof} for details.
     *
     * _Available since v4.7._
     */
    function multiProofVerifyCalldata(
        bytes32[] calldata proof,
        bool[] calldata proofFlags,
        bytes32 root,
        bytes32[] memory leaves
    ) internal pure returns (bool) {
        return processMultiProofCalldata(proof, proofFlags, leaves) == root;
    }

    /**
     * @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction
     * proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another
     * leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false
     * respectively.
     *
     * CAUTION: Not all merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree
     * is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the
     * tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer).
     *
     * _Available since v4.7._
     */
    function processMultiProof(
        bytes32[] memory proof,
        bool[] memory proofFlags,
        bytes32[] memory leaves
    ) internal pure returns (bytes32 merkleRoot) {
        // This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
        // consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
        // `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
        // the merkle tree.
        uint256 leavesLen = leaves.length;
        uint256 proofLen = proof.length;
        uint256 totalHashes = proofFlags.length;

        // Check proof validity.
        require(leavesLen + proofLen - 1 == totalHashes, "MerkleProof: invalid multiproof");

        // The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
        // `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
        bytes32[] memory hashes = new bytes32[](totalHashes);
        uint256 leafPos = 0;
        uint256 hashPos = 0;
        uint256 proofPos = 0;
        // At each step, we compute the next hash using two values:
        // - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
        //   get the next hash.
        // - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
        //   `proof` array.
        for (uint256 i = 0; i < totalHashes; i++) {
            bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
            bytes32 b = proofFlags[i]
                ? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
                : proof[proofPos++];
            hashes[i] = _hashPair(a, b);
        }

        if (totalHashes > 0) {
            require(proofPos == proofLen, "MerkleProof: invalid multiproof");
            unchecked {
                return hashes[totalHashes - 1];
            }
        } else if (leavesLen > 0) {
            return leaves[0];
        } else {
            return proof[0];
        }
    }

    /**
     * @dev Calldata version of {processMultiProof}.
     *
     * CAUTION: Not all merkle trees admit multiproofs. See {processMultiProof} for details.
     *
     * _Available since v4.7._
     */
    function processMultiProofCalldata(
        bytes32[] calldata proof,
        bool[] calldata proofFlags,
        bytes32[] memory leaves
    ) internal pure returns (bytes32 merkleRoot) {
        // This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
        // consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
        // `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
        // the merkle tree.
        uint256 leavesLen = leaves.length;
        uint256 proofLen = proof.length;
        uint256 totalHashes = proofFlags.length;

        // Check proof validity.
        require(leavesLen + proofLen - 1 == totalHashes, "MerkleProof: invalid multiproof");

        // The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
        // `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
        bytes32[] memory hashes = new bytes32[](totalHashes);
        uint256 leafPos = 0;
        uint256 hashPos = 0;
        uint256 proofPos = 0;
        // At each step, we compute the next hash using two values:
        // - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
        //   get the next hash.
        // - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
        //   `proof` array.
        for (uint256 i = 0; i < totalHashes; i++) {
            bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
            bytes32 b = proofFlags[i]
                ? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
                : proof[proofPos++];
            hashes[i] = _hashPair(a, b);
        }

        if (totalHashes > 0) {
            require(proofPos == proofLen, "MerkleProof: invalid multiproof");
            unchecked {
                return hashes[totalHashes - 1];
            }
        } else if (leavesLen > 0) {
            return leaves[0];
        } else {
            return proof[0];
        }
    }

    function _hashPair(bytes32 a, bytes32 b) private pure returns (bytes32) {
        return a < b ? _efficientHash(a, b) : _efficientHash(b, a);
    }

    function _efficientHash(bytes32 a, bytes32 b) private pure returns (bytes32 value) {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x00, a)
            mstore(0x20, b)
            value := keccak256(0x00, 0x40)
        }
    }
}

// File: SharedRevenueMerkle.sol



// SPDX-License-Identifier: UNKNOWN

pragma solidity ^0.8.9;
import "MerkleProof.sol";

contract SharedRevenueMerkle {
    // Address that can distribute the Ether
    address public owner;

    struct Distribution {
        bytes32 merkleroot;
        uint256 totalAmount;
        uint256 amountLocked;
        uint256 expirationTime;
        uint256 totalRecipients;
        bool isCancelled;
        mapping(address => bool) addressHasClaimed;
    }

    mapping(uint256 => Distribution) public distributions;

    uint256 public nextDistributionId = 0;

    // Amount of funds that are locked in an active Distribution
    uint256 lockedFunds = 0;

    // Event to log when Ether is distributed
    event Distributed(
        uint256 distributionId,
        uint256 amount,
        uint256 amountOfRecipients
    );

    event DistributionCanceled(uint256 distributionId, uint256 remainingAmount, uint256 totalAmount);

    // Event to log when Ether is claimed
    event Claimed(
        address indexed claimer,
        uint256 amount,
        uint256 distributionId
    );

    event Withdrawal(uint amount, uint when);

    // Contract constructor
    constructor() {
        owner = msg.sender;
    }

    // Modifier to ensure only the owner can call certain functions
    modifier onlyOwner() {
        require(msg.sender == owner, "Not the contract owner");
        _;
    }

    // Function to change the owner of the contract
    function changeOwner(address newOwner) external onlyOwner {
        owner = newOwner;
    }

    // Function to withdraw all unlocked funds
    function withdraw() external onlyOwner(){
        emit Withdrawal(address(this).balance, block.timestamp);
        payable(owner).transfer(address(this).balance);
    }

    // Function to deposit Ether into the contract
    function deposit() external payable {}

    // Fallback function is called when a function that doesn't exist is called
    fallback() external payable {}

    // Receive function is specifically for sending Ether directly to the contract
    receive() external payable {}

    // Function to distribute Ether to a list of addresses
    function createDistribution(
        uint256 totalAmount,
        uint256 totalRecipients,
        uint256 lockDuration,
        bytes32 merkleroot
    ) external onlyOwner {
        require(
            address(this).balance - lockedFunds >= totalAmount,
            "Insufficient balance in contract"
        );

        Distribution storage dist = distributions[nextDistributionId];

        dist.merkleroot = merkleroot;
        dist.totalAmount = totalAmount;
        dist.amountLocked = totalAmount;
        dist.expirationTime = block.timestamp + lockDuration;
        dist.totalRecipients = totalRecipients;
        dist.isCancelled = false;

        lockedFunds += totalAmount;

        emit Distributed(nextDistributionId, totalAmount, totalRecipients);
        nextDistributionId++;
    }

    // Function to claim Ether
    function claim(
        uint256 distributionId,
        uint256 amount,
        bytes32[] memory proof
    ) external {
        Distribution storage dist = distributions[distributionId];
        require(
            !dist.addressHasClaimed[msg.sender],
            "Address has already claimed from this distribution"
        );
        require(!dist.isCancelled, "Distribution is cancelled");

        require(
            MerkleProof.verify(
                proof,
                dist.merkleroot,
                _generateClaimMerkleLeaf(msg.sender, amount)
            ),
            "Provided data does not match the proof"
        );
        // --- uncomment if you want to lock the claim after expiration
        require(block.timestamp < dist.expirationTime, "Distribution expired");

        dist.addressHasClaimed[msg.sender] = true;
        dist.amountLocked -= amount;
        

        lockedFunds -= amount;

        emit Claimed(msg.sender, amount, distributionId);
        payable(msg.sender).transfer(amount);
    }

    // Function to cancel Distribution
    function cancelDistribution(uint256 distributionId) external onlyOwner {
        Distribution storage dist = distributions[distributionId];
        require(
            block.timestamp > dist.expirationTime,
            "Distribution still active"
        );
        require(!dist.isCancelled, "Distribution is already cancelled");

        lockedFunds -= dist.amountLocked;

        dist.isCancelled = true;

        emit DistributionCanceled(distributionId, dist.amountLocked, dist.totalAmount);
    }

    function _generateClaimMerkleLeaf(
        address _account,
        uint256 _amount
    ) internal pure returns (bytes32) {
        return keccak256(abi.encodePacked(_account, _amount));
    }

    // Function to get the contract balance
    function getBalance() external view returns (uint256) {
        return address(this).balance;
    }

    // Function to get the available balance from the contract
    function getAvailableBalance() external view returns (uint256) {
        return address(this).balance - lockedFunds;
    }

    function getLockedBalance() external view returns (uint256) {
        return lockedFunds;
    }

    // Function to get total eth claimed
    function getTotalClaimed() external view returns (uint256) {
        uint256 res = 0;
        for (uint i = 0; i < nextDistributionId; i++){
            res += (distributions[i].totalAmount - distributions[i].amountLocked);
        }

        return res;
    }
}