contract PaymentDeposit is Ownable {

    // Every depositor has a unique identifier. Helps with off-chain tracking. 
    mapping(address => uint) public depositors;
    uint public uniqueDepositors; 

    event Deposit(address indexed sender, uint amount, uint indexed index);
    event Withdraw(address indexed depositor, address indexed recipient, uint amount);

    /**
     * @param _recipient Associate funds with this address. 
     * Supply a deposit for a specified recipient.
     * Caution: The recipient must be an externally owned account as all jobs sent to 
     * any.sender must be signed and associated with a positive balance in this contract. 
     */
    function depositFor(address _recipient) public payable { 
        require(msg.value > 0, "No value provided to depositFor.");
        uint index = getDepositorIndex(_recipient);
        emit Deposit(_recipient, msg.value, index);
    }

    /** 
     * @param _depositor Depositor address
     * Sets the depositors index if necessary.
     */
    function getDepositorIndex(address _depositor) internal returns(uint) {
        if(depositors[_depositor] == 0) {
            uniqueDepositors = uniqueDepositors + 1;
            depositors[_depositor] = uniqueDepositors;
        }

        return depositors[_depositor];
    }

    /*
     * It is only intended for external users who want to deposit via a wallet.
     */ 
    receive() external payable {
        require(msg.value > 0, "No value provided to fallback.");
        require(tx.origin == msg.sender, "Only EOA can deposit directly.");
        uint index = getDepositorIndex(msg.sender);
        emit Deposit(msg.sender, msg.value, index);
    }

    /**
     * Internal function for sending funds. 
     */
    function withdraw(address payable _depositor, address payable _recipient, uint _amount) internal {
        _recipient.transfer(_amount);
        emit Withdraw(_depositor, _recipient, _amount);
    }

    /**
     * @param _depositor Funds being withdrawn from (e.g. deducts their balance). 
     * @param _recipient Receiver of the funds
     * @param _amount Funds to send
     * Move funds out of the contract
     * Depositor is the OWNER of the funds being withdrawn. 
     * Recipient is the RECEIVER of the funds. 
     */
    function withdrawFor(address payable _depositor, address payable _recipient, uint _amount) public onlyOwner {
        withdraw(_depositor, _recipient, _amount);
    }

    /**
     * @param _recipient Address that receives funds in the new PaymentDeposit
     * @param _amount Funds to send
     * @param _otherDeposit New Payment Deposit contract
     * Use admin privileges to migrate a user's deposits to another deposit contract
     */
    function migrate(address payable _recipient, uint _amount, PaymentDeposit _otherDeposit) public onlyOwner {
        require(address(this).balance >= _amount, "Not enough balance to migrate.");
        require(address(_otherDeposit) != address(this), "Cannot migrate to same contract.");
        _otherDeposit.depositFor.value(_amount)(_recipient); // We assume an upgraded contract has this interface.
        emit Withdraw(address(this), _recipient, _amount);
    }
}
/* 
 * @author Patrick McCorry & Chris Buckland (ITX)
 * @title Payment Deposit contract
 * @notice Handles customers deposits and only the owner/child contracts can issue withdraws. 
 */
function migrate(address payable _recipient, uint _amount, PaymentDeposit _otherDeposit) public onlyOwner {
    require(address(this).balance >= _amount, "Not enough balance to migrate.");
    require(address(_otherDeposit) != address(this), "Cannot migrate to same contract.");
    _otherDeposit.depositFor.value(_amount)(_recipient); // We assume an upgraded contract has this interface.
    emit Withdraw(address(this), _recipient, _amount);
}

/**
 * @param _recipient Address that receives funds in the new PaymentDeposit
 * @param _amount Funds to send
 * @param _otherDeposit New Payment Deposit contract
 * Use admin privileges to migrate a user's deposits to another deposit contract
 */