contract DepositManager is
    Operational,
    ReentrancyGuard
{
    using LibSafeMath for uint256;

    // Mapping from user to deposit amount
    mapping (address => uint256) public depositAmount;
    // Mapping from order and user to deposit amount for withdraw
    mapping (bytes32 => mapping (address => uint256)) public orderToDepositAmount;

    // Deposit event is emitted whenever `deposit` is executed succesfully.
    event Deposit(
        bytes32 indexed orderHash,
        address indexed senderAddress,
        uint256 amount
    );

    // DepositChanged event is emitted whenever `updateOrder` is executed succesfully.
    event DepositChanged(
        bytes32 indexed newOrderHash,
        uint256 newAmount,
        bytes32 indexed oldOrderHash,
        uint256 oldAmount,
        address indexed senderAddress
    );

    // Withdraw event is emitted whenever `withdraw` (it may be called in `cancelOrder`) is executed succesfully.
    event Withdraw(
        bytes32 indexed orderHash,
        address indexed toAddress,
        uint256 amount
    );

    /// @dev Deposit for offer.
    /// @param orderHash orderHash of the order.
    function deposit(bytes32 orderHash)
        public
        payable
        nonReentrant
    {
        depositInternal(orderHash, msg.sender, msg.value);
    }

    /// @dev Withdraw deposit.
    /// @param orderHash orderHash of the order.
    /// @param toAddress Address to be refund.
    function withdraw(bytes32 orderHash, address payable toAddress)
        public
        nonReentrant
        withdrawable(toAddress)
    {
        withdrawInternal(orderHash, toAddress);
    }

    /// @dev Deposit by operator for dutch auction buying.
    /// @param orderHash orderHash of the order.
    /// @param toAddress Address of deposit target.
    function depositByOperator(bytes32 orderHash, address toAddress)
        public
        payable
        nonReentrant
        onlyDepositOperator
    {
        depositInternal(orderHash, toAddress, msg.value);
    }

    function depositInternal(bytes32 orderHash, address sender, uint256 amount)
        internal
    {
        depositAmount[sender] = depositAmount[sender].safeAdd(amount);
        orderToDepositAmount[orderHash][sender] = orderToDepositAmount[orderHash][sender].safeAdd(amount);
        emit Deposit(orderHash, sender, amount);
    }

    function withdrawInternal(bytes32 orderHash, address payable toAddress)
        internal
    {
        if (orderToDepositAmount[orderHash][toAddress] > 0) {
            uint256 amount = orderToDepositAmount[orderHash][toAddress];
            depositAmount[toAddress] = depositAmount[toAddress].safeSub(amount);
            delete orderToDepositAmount[orderHash][toAddress];
            toAddress.transfer(amount);
            emit Withdraw(orderHash, toAddress, amount);
        }
    }

    function changeDeposit(
        bytes32 newOrderHash,
        uint256 newOfferAmount,
        bytes32 oldOrderHash,
        uint256 oldOfferAmount,
        address payable sender
    )
        internal
    {
        if (msg.value > 0) {
            depositAmount[sender] = depositAmount[sender].safeAdd(msg.value);
            orderToDepositAmount[newOrderHash][sender] = orderToDepositAmount[newOrderHash][sender].safeAdd(msg.value);
        }
        uint256 oldOrderToDepositAmount = orderToDepositAmount[oldOrderHash][sender];
        moveDeposit(oldOrderHash, newOrderHash, sender);
        if (oldOrderToDepositAmount > newOfferAmount) {
            uint256 refundAmount = orderToDepositAmount[newOrderHash][sender].safeSub(newOfferAmount);
            orderToDepositAmount[newOrderHash][sender] = orderToDepositAmount[newOrderHash][sender].safeSub(refundAmount);
            depositAmount[sender] = depositAmount[sender].safeSub(refundAmount);
            sender.transfer(refundAmount);
        }
        emit DepositChanged(newOrderHash, newOfferAmount, oldOrderHash, oldOfferAmount, sender);
    }

    function moveDeposit(
        bytes32 fromOrderHash,
        bytes32 toOrderHash,
        address sender
    )
        internal
    {
        uint256 amount = orderToDepositAmount[fromOrderHash][sender];
        delete orderToDepositAmount[fromOrderHash][sender];
        orderToDepositAmount[toOrderHash][sender] = orderToDepositAmount[toOrderHash][sender].safeAdd(amount);
    }

    function deductOrderToDepositAmount(
        bytes32 orderHash,
        address target,
        uint256 amount
    )
        internal
    {
        orderToDepositAmount[orderHash][target] = orderToDepositAmount[orderHash][target].safeSub(amount);
    }
}
function withdraw(bytes32 orderHash, address payable toAddress)
    public
    nonReentrant
    withdrawable(toAddress)
{
    withdrawInternal(orderHash, toAddress);
}

/// @dev Withdraw deposit.
/// @param orderHash orderHash of the order.
/// @param toAddress Address to be refund.