contract DispatcherMultiSig is Ownable {
    Dispatcher private dispatcher;

    address[] private signatories;
    uint256[] private outstandingAddressProposalsIndex;
    uint256[] private outstandingThresholdProposalsIndex;
    uint256 private threshold = 1;

    uint256 private uuid = 0;

    /*Proposal types
        0 = approveSignatory for multisig
        1 = removeSignatory for multisig
        2 = approveNewOwner for multisig
        3 = approveNewMultiSig for dispatcher
        4 = approveNewVault for dispatcher
        5 = approveNewDispatcher for multisig
    */

    struct addressProposal {
        uint256 proposalType;
        address proposal;
        address[] signatures;
        bool signed;
        uint256 timeStamp;
    }
    mapping(uint256 => addressProposal) private addressProposalStore;

    struct thresholdProposal {
        uint256 proposal;
        address[] signatures;
        bool signed;
        uint256 timeStamp;
    }
    mapping(uint256 => thresholdProposal) private thresholdProposalStore;

    constructor() Ownable() {
        signatories.push(msg.sender);
    }

    /*****************  Getters **************** */
    function getSignatories() public view returns (address[] memory) {
        return signatories;
    }

    function getProposal(uint256 _index) public view returns (addressProposal memory) {
        return addressProposalStore[_index];
    }

    function getThresholdProposal(uint256 _index) public view returns (thresholdProposal memory)
    {
        return thresholdProposalStore[_index];
    }

    function getOutstandingAddressProposals() public view returns (uint256[] memory) {
        return outstandingAddressProposalsIndex;
    }

    function getOutstandingThresholdProposals() public view returns (uint256[] memory) {
        return outstandingThresholdProposalsIndex;
    }

    function getDispatcher() public view returns (Dispatcher){
        return dispatcher;
    }

    function getAddressProposal(uint256 i)
        public
        view
        returns (addressProposal memory)
    {
        return addressProposalStore[i];
    }

    function getThreshold() public view returns (uint256) {
        return threshold;
    }

    /*****************  Proposers **************** */
    function proposeAddress(address _address, uint256 _index) external onlyOwner {
        addressProposalStore[uuid].proposal = _address;
        addressProposalStore[uuid].proposalType = _index;
        addressProposalStore[uuid].timeStamp = block.timestamp;
        outstandingAddressProposalsIndex.push(uuid);
        uuid += 1;
        emit ProposeAddress(_address, _index);
    }

    function proposeNewOwner(address _address) external onlySignatories {
        addressProposalStore[uuid].proposal = _address;
        addressProposalStore[uuid].proposalType = 2;
        addressProposalStore[uuid].timeStamp = block.timestamp;
        outstandingAddressProposalsIndex.push(uuid);
        uuid += 1;
        emit ProposeNewOwner(_address);
    }

    function proposeNewThreshold(uint256 _threshold) external onlyOwner {
        thresholdProposalStore[uuid].proposal = _threshold;
        thresholdProposalStore[uuid].timeStamp = block.timestamp;
        outstandingThresholdProposalsIndex.push(uuid);
        uuid += 1;
        emit ProposeNewThreshold(_threshold);
    }
    /***************** Approvers **************** */

    function approveSignatory(uint256 _proposal) external onlySignatories oneVoteAddress(_proposal){
        require(addressProposalStore[_proposal].proposalType == 0, "Not the right proposal type");
        require(addressProposalStore[_proposal].signed == false, "Already Signed");

        addressProposalStore[_proposal].signatures.push(msg.sender);

        if (addressProposalStore[_proposal].signatures.length >= threshold) {
            addressProposalStore[_proposal].signed = true;
            signatories.push(addressProposalStore[_proposal].proposal);
            popAddressProposal(_proposal);
            emit ApprovedSignatory(addressProposalStore[_proposal].proposal);
        }
    }

    function removeSignatory(uint256 _proposal) external onlySignatories oneVoteAddress(_proposal){
        require(addressProposalStore[_proposal].proposalType == 1, "Not the right proposal type");
        require(addressProposalStore[_proposal].signed == false, "Already Signed");

        addressProposalStore[_proposal].signatures.push(msg.sender);

        if (addressProposalStore[_proposal].signatures.length >= threshold) {
            addressProposalStore[_proposal].signed = true;
            //Remove a threshold count in order to avoid not having enough signatories
            if(threshold > 1)
            {
                threshold = threshold - 1;
            }
            removeSignatory(addressProposalStore[_proposal].proposal);
            popAddressProposal(_proposal);
            emit RemovedSignatory(addressProposalStore[_proposal].proposal);
        }
    }
    
    function approveNewOwner(uint256 _proposal) external onlySignatories oneVoteAddress(_proposal){
        require(addressProposalStore[_proposal].proposalType == 2, "Not the right proposal type");
        require(addressProposalStore[_proposal].signed == false, "Already Signed");

        addressProposalStore[_proposal].signatures.push(msg.sender);

        if (addressProposalStore[_proposal].signatures.length >= threshold) {
            addressProposalStore[_proposal].signed = true;

            transferOwnership(addressProposalStore[_proposal].proposal);
            popAddressProposal(_proposal);
            emit ApprovedNewOwner(addressProposalStore[_proposal].proposal);
        }
    }

    function approveNewMultiSig(uint256 _proposal) external onlySignatories oneVoteAddress(_proposal){
        require(addressProposalStore[_proposal].proposalType == 3, "Not the right proposal type");
        require(addressProposalStore[_proposal].signed == false, "Already Signed");

        addressProposalStore[_proposal].signatures.push(msg.sender);

        if (addressProposalStore[_proposal].signatures.length >= threshold) {
            addressProposalStore[_proposal].signed = true;

            dispatcher.newMultiSig(addressProposalStore[_proposal].proposal);
            popAddressProposal(_proposal);
            emit ApprovedNewMultiSig(addressProposalStore[_proposal].proposal);
        }
    }

    function approveNewVault(uint256 _proposal) external onlySignatories oneVoteAddress(_proposal){
        require(addressProposalStore[_proposal].proposalType == 4, "Not the right proposal type");
        require(addressProposalStore[_proposal].signed == false, "Already Signed");

        addressProposalStore[_proposal].signatures.push(msg.sender);

        if (addressProposalStore[_proposal].signatures.length >= threshold) {
            addressProposalStore[_proposal].signed = true;

            dispatcher.newVault(addressProposalStore[_proposal].proposal);
            popAddressProposal(_proposal);
            emit ApprovedNewVault(addressProposalStore[_proposal].proposal);
        }
    }

    function approveNewDispatcher(uint256 _proposal) external onlySignatories oneVoteAddress(_proposal) {
        require(addressProposalStore[_proposal].proposalType == 5, "Not the right proposal type");
        require(addressProposalStore[_proposal].signed == false, "Already Signed");

        addressProposalStore[_proposal].signatures.push(msg.sender);

        if (addressProposalStore[_proposal].signatures.length >= threshold) {
            addressProposalStore[_proposal].signed = true;

            dispatcher = Dispatcher(addressProposalStore[_proposal].proposal);
            popAddressProposal(_proposal);
            emit ApprovedDispatcher(addressProposalStore[_proposal].proposal);
        }
    }
    
    function approveNewThreshold(uint256 _proposal) external onlySignatories oneVoteThreshold(_proposal){
        require(thresholdProposalStore[_proposal].signed == false, "Already Signed");
        require(thresholdProposalStore[_proposal].proposal <= signatories.length, "Can't be less signatories than threshold");
        thresholdProposalStore[_proposal].signatures.push(msg.sender);

        if (thresholdProposalStore[_proposal].signatures.length >= threshold) {
            threshold = thresholdProposalStore[_proposal].proposal;
            popThresholdProposal(_proposal);
            emit ApprovedNewThreshold(thresholdProposalStore[_proposal].proposal);
        }
    }

    /*****************  Internal **************** */

    function popAddressProposal(uint256 _uuid) private {
        for(uint256 i = 0; i <= outstandingAddressProposalsIndex.length; i++)
        {
            if(outstandingAddressProposalsIndex[i] == _uuid)
            {
                outstandingAddressProposalsIndex[i] = outstandingAddressProposalsIndex[outstandingAddressProposalsIndex.length - 1];
                outstandingAddressProposalsIndex.pop();
                break;
            }
        }
    }

    function popThresholdProposal(uint256 _uuid) private {
        for(uint256 i = 0; i <= outstandingThresholdProposalsIndex.length; i++)
        {
            if(outstandingThresholdProposalsIndex[i] == _uuid)
            {
                outstandingThresholdProposalsIndex[i] = outstandingThresholdProposalsIndex[outstandingThresholdProposalsIndex.length - 1];
                outstandingThresholdProposalsIndex.pop();
                break;
            }
        }
    }

    function removeSignatory(address _signatory) private {
        for(uint256 i = 0; i <= signatories.length; i++)
        {
            if(signatories[i] == _signatory)
            {
                signatories[i] = signatories[signatories.length - 1];
                signatories.pop();
                break;
            }
        }
    }

    /*****************  Modifiers **************** */
    modifier onlySignatories() {
        for (uint256 i = 0; i < signatories.length; i++) {
            if (signatories[i] == msg.sender) {
                _;
            }
        }
    }

    modifier oneVoteAddress (uint256 _proposal) {
        for(uint256 i = 0; i < addressProposalStore[_proposal].signatures.length; i++){
            require(addressProposalStore[_proposal].signatures[i] != msg.sender, "You have already voted");
        }

        _;
    }

    modifier oneVoteThreshold (uint256 _proposal) {
        for(uint256 i = 0; i < thresholdProposalStore[_proposal].signatures.length; i++){
            require(thresholdProposalStore[_proposal].signatures[i] != msg.sender, "You have already voted");
        }

        _;
    }
    /*****************  Events **************** */
    event ApprovedSignatory(address newSignatory);
    event RemovedSignatory(address removedSignatory);
    event ApprovedNewOwner(address newOwner);
    event ApprovedNewMultiSig(address newMultiSig);
    event ApprovedNewVault(address newVault);
    event ApprovedDispatcher(address newDispatcher);
    event ApprovedNewThreshold(uint256 newThreshold);
    event ProposeAddress(address proposedAddress, uint256 index);
    event ProposeNewOwner(address newOwner);
    event ProposeNewThreshold(uint256 threshold);
}
function approveSignatory(uint256 _proposal) external onlySignatories oneVoteAddress(_proposal){
    require(addressProposalStore[_proposal].proposalType == 0, "Not the right proposal type");
    require(addressProposalStore[_proposal].signed == false, "Already Signed");

    addressProposalStore[_proposal].signatures.push(msg.sender);

    if (addressProposalStore[_proposal].signatures.length >= threshold) {
        addressProposalStore[_proposal].signed = true;
        signatories.push(addressProposalStore[_proposal].proposal);
        popAddressProposal(_proposal);
        emit ApprovedSignatory(addressProposalStore[_proposal].proposal);
    }
}

/***************** Approvers **************** */