contract Minion is IERC721Receiver, IERC1155Receiver {
    using SafeMath for uint256;
    IMOLOCH public moloch;
    address public molochDepositToken;
    uint256 public minQuorum;
    bool private initialized; // internally tracks deployment under eip-1167 proxy pattern
    mapping(uint256 => Action) public actions; // proposalId => Action

    struct Action {
        uint256 value;
        address to;
        address proposer;
        bool executed;
        bytes data;
        address token;
        uint256 amount;
    }

    event ProposeAction(uint256 proposalId, address proposer);
    event ExecuteAction(uint256 proposalId, address executor);
    event DoWithdraw(address token, uint256 amount);
    event CrossWithdraw(address target, address token, uint256 amount);
    event PulledFunds(address moloch, uint256 amount);
    event ActionCanceled(uint256 proposalId);
    
     modifier memberOnly() {
        require(isMember(msg.sender), "Minion::not member");
        _;
    }
    

    function init(address _moloch, uint256 _minQuorum) external {
        require(!initialized, "initialized"); 
        moloch = IMOLOCH(_moloch);
        minQuorum = _minQuorum;
        molochDepositToken = moloch.depositToken();
        initialized = true;

    }

    function onERC721Received (address, address, uint256, bytes calldata) external pure override returns(bytes4) {
        return bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"));
    } 

    function onERC1155Received(address, address, uint256, uint256, bytes calldata) external pure override returns(bytes4) {
        return bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"));
    }

    function onERC1155BatchReceived(address, address, uint256[] calldata, uint256[] calldata, bytes calldata) external pure override returns(bytes4)
    {
        return bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"));
    }
   

    //  -- Withdraw Functions --

    function doWithdraw(address token, uint256 amount) public memberOnly {
        moloch.withdrawBalance(token, amount); // withdraw funds from parent moloch
        emit DoWithdraw(token, amount);
    }
    
    function crossWithdraw(address target, address token, uint256 amount, bool transfer) external memberOnly {
        // @Dev - Target needs to have a withdrawBalance functions
        IMOLOCH(target).withdrawBalance(token, amount); 
        
        // Transfers token into DAO. 
        if(transfer) {
            bool whitelisted = moloch.tokenWhitelist(token);
            require(whitelisted, "not a whitelisted token");
            require(IERC20(token).transfer(address(moloch), amount), "token transfer failed");
        }
        
        emit CrossWithdraw(target, token, amount);
    }
    
    //  -- Proposal Functions --
    
    function proposeAction(
        address actionTo,
        uint256 actionValue,
        bytes calldata actionData,
        string memory details,
        address withdrawToken,
        uint256 withdrawAmount
    ) external memberOnly returns (uint256) {
        // No calls to zero address allows us to check that proxy submitted
        // the proposal without getting the proposal struct from parent moloch
        require(actionTo != address(0), "invalid actionTo");
        
        uint256 proposalId = moloch.submitProposal(
            address(this),
            0,
            0,
            0,
            molochDepositToken,
            withdrawAmount,
            withdrawToken,
            details
        );

        Action memory action = Action({
            value: actionValue,
            to: actionTo,
            proposer: msg.sender,
            executed: false,
            data: actionData,
            token: withdrawToken,
            amount: withdrawAmount
        });

        actions[proposalId] = action;

        emit ProposeAction(proposalId, msg.sender);
        return proposalId;
    }

    function executeAction(uint256 proposalId) external returns (bytes memory) {
        Action memory action = actions[proposalId];

        require(action.to != address(0), "invalid proposalId");
        require(!action.executed, "action executed");
        
        bool isPassed = hasQuorum(proposalId);

        require(isPassed, "proposal execution requirements not met");
        
        if(moloch.getUserTokenBalance(address(this), action.token) >= action.amount) {
            doWithdraw(action.token, action.amount);
        }
        
        require(address(this).balance >= action.value, "insufficient native token");
        

        // execute call
        actions[proposalId].executed = true;
        (bool success, bytes memory retData) = action.to.call{value: action.value}(action.data);
        require(success, "call failure");
        emit ExecuteAction(proposalId, msg.sender);
        return retData;
    }
    
    function cancelAction(uint256 _proposalId) external {
        Action memory action = actions[_proposalId];
        require(msg.sender == action.proposer, "not proposer");
        delete actions[_proposalId];
        emit ActionCanceled(_proposalId);
        moloch.cancelProposal(_proposalId);
    }
    
    function hasQuorum(uint256 _proposalId) internal returns (bool) {
        // something like this to check is some quorum is met
        // if met execution can proceed before proposal is processed

        uint256 padding = 100;
        uint256 totalShares = moloch.totalShares();
        bool[6] memory flags = moloch.getProposalFlags(_proposalId);


        // uint yesVotes = moloch.proposals(_proposalId).yesVotes;
        // uint noVotes = moloch.proposals(_proposalId).noVotes;
        (, , , , , , , , , , uint256 yesVotes, uint256 noVotes) = moloch.proposals(_proposalId);
        
        if (flags[2]) {
            return true;
        }

        if (minQuorum != 0) {
            uint256 quorum = yesVotes.mul(padding).div(totalShares);
            // if quorum is set it must be met and their can be no NO votes
            return quorum >= minQuorum && noVotes < 1;  
        }
        
        return false;

    }
    
    //  -- Helper Functions --
    
    function isMember(address user) public view returns (bool) {
        
        address memberAddress = moloch.memberAddressByDelegateKey(user);
        (, uint shares,,,,) = moloch.members(memberAddress);
        return shares > 0;
    }

    receive() external payable {}
    fallback() external payable {}
}
function proposeAction(
    address actionTo,
    uint256 actionValue,
    bytes calldata actionData,
    string memory details,
    address withdrawToken,
    uint256 withdrawAmount
) external memberOnly returns (uint256) {
    // No calls to zero address allows us to check that proxy submitted
    // the proposal without getting the proposal struct from parent moloch
    require(actionTo != address(0), "invalid actionTo");
        
    uint256 proposalId = moloch.submitProposal(
        address(this),
        0,
        0,
        0,
        molochDepositToken,
        withdrawAmount,
        withdrawToken,
        details
    );

    Action memory action = Action({
        value: actionValue,
        to: actionTo,
        proposer: msg.sender,
        executed: false,
        data: actionData,
        token: withdrawToken,
        amount: withdrawAmount
    });

    actions[proposalId] = action;

    emit ProposeAction(proposalId, msg.sender);
    return proposalId;
}

//  -- Proposal Functions --