contract EscrowETH is
    EscrowBaseInterface,
    Disputed,
    FeeApplicable,
    EscrowEmitter,
    FeeConstants
{

    using SafeMath for uint;

    bytes32 constant ETH_SYMBOL = "ETH";

    uint private accumulatedFee;

    mapping(bytes32 => Escrow) private escrows;

    struct Escrow {
        bool exists;
        bool disputed;
        uint value;
        address arbiter;
    }

    function() external payable {
        revert("ESCROW_ETH_DOES_NOT_SUPPORT_ETH");
    }

    function getFeeBalance() public view returns (uint) {
        return accumulatedFee;
    }

    /// @notice Creates an escrow record for provided symbol "`_symbol`"
    /// @dev Escrow is reusable so the same tradeRecordId could be reused after an escrow
    ///		with the same identifier is resolved.
    /// @dev Escrow is identified by a message = keccak256(_tradeRecordId, _seller, _buyer)
    /// @param _tradeRecordId identifier of escrow record
    /// @param _seller who will mainly deposit to escrow
    /// @param _buyer who will eventually is going to receive payment
    /// @param _symbol symbol of payment currency
    /// @param _value amount to initially deposit to escrow; could be 0
    /// @param _transferImmediatelyToBuyerAmount amount to transfer immediately to a buyer
    /// @param _feeStatus fee condition (1st bit - seller, 2nd - buyer)
    /// @return result code of an operation
    function createEscrow(
        bytes32 _tradeRecordId,
        address _seller,
        address _buyer,
        bytes32 _symbol,
        uint _value,
        uint _transferImmediatelyToBuyerAmount,
        uint8 _feeStatus
    )
    external
    payable
    onlyRelay
    returns (uint)
    {
        bytes32 _escrowHash = _getEscrowHash(_tradeRecordId, _seller, _buyer);
        Escrow storage _escrow = escrows[_escrowHash];
        require(!_escrow.exists, "ESCROW_ETH_ESCROW_SHOULD_NOT_EXIST");
        require(hasCurrencySupport(_symbol), "ESCROW_ETH_CURRENCY_SHOULD_BE_SUPPORTED");

        uint _valueWithoutFee = _value;
        if (_value > 0) {
            require(_value == msg.value && _value >= _transferImmediatelyToBuyerAmount, "ESCROW_ETH_VALUE_SHOULD_BE_CORRECT");
            _valueWithoutFee = _takeDepositFee(_value, _feeStatus);
            if (_transferImmediatelyToBuyerAmount > 0) {
                //send upfront to buyer
                _valueWithoutFee = _valueWithoutFee.sub(_transferImmediatelyToBuyerAmount);
                uint _transferImmediatelyToBuyerAmountWithoutFee = _takeWithdrawalFee(_transferImmediatelyToBuyerAmount, _feeStatus);
                _buyer.transfer(_transferImmediatelyToBuyerAmountWithoutFee);
            }
        }
        escrows[_escrowHash] = Escrow(true, false, _valueWithoutFee, address(0));
        _emitCreated(_escrowHash, ETH_SYMBOL, _valueWithoutFee);

        return OK;
    }

    /// @notice Deposits to an escrow provided amount `_value`
    /// @param _tradeRecordId identifier of escrow record
    /// @param _seller who will mainly deposit to escrow
    /// @param _buyer who will eventually is going to receive payment
    /// @param _value amount to deposit
    /// @param _transferImmediatelyToBuyerAmount amount to transfer immediately to a buyer
    /// @param _feeStatus fee condition (1st bit - seller, 2nd - buyer)
    /// @return result code of an operation
    function deposit(
        bytes32 _tradeRecordId,
        address _seller,
        address _buyer,
        uint _value,
        uint _transferImmediatelyToBuyerAmount, // transfers _transferImmediatelyToBuyerAmount directly to _buyer.
        uint8 _feeStatus
    )
    external
    payable
    onlyRelay
    returns (uint)
    {
        bytes32 _escrowHash = _getEscrowHash(_tradeRecordId, _seller, _buyer);
        Escrow storage _escrow = escrows[_escrowHash];
        require(_escrow.exists, "ESCROW_ETH_ESCROW_SHOULD_EXIST");
        require(!_escrow.disputed, "ESCROW_ETH_ESCROW_SHOULD_NOT_BE_DISPUTED");
        require(msg.value == _value && msg.value > 0 && _value >= _transferImmediatelyToBuyerAmount, "ESCROW_ETH_VALUE_SHOULD_BE_CORRECT");

        uint _valueWithoutFee = _takeDepositFee(_value, _feeStatus);
        if (_transferImmediatelyToBuyerAmount > 0) {
            //send upfront to buyer
            _valueWithoutFee = _valueWithoutFee.sub(_transferImmediatelyToBuyerAmount);
            uint _transferImmediatelyToBuyerAmountWithoutFee = _takeWithdrawalFee(_transferImmediatelyToBuyerAmount, _feeStatus);
            _buyer.transfer(_transferImmediatelyToBuyerAmountWithoutFee);
        }
        _escrow.value = _escrow.value.add(_valueWithoutFee);
        _emitDeposited(_escrowHash, ETH_SYMBOL, _valueWithoutFee);

        return OK;
    }

    /// @notice Transfers `_value` from escrow to buyer `_buyer`
    /// @dev Escrow is identified by a message = keccak256(_tradeRecordId, _seller, _buyer)
    /// @dev Seller shoud sign hash of (message, escrow address, msg.sig, _value, _expireAtBlock, _salt) data
    /// @param _tradeRecordId identifier of escrow record
    /// @param _seller who will mainly deposit to escrow
    /// @param _buyer who will eventually is going to receive payment
    /// @param _value amount to withdraw from escrow
    /// @param _expireAtBlock expiry block after which transaction will be invalid
    /// @param _salt random bytes to identify signed data
    /// @param _sellerSignature signature produced by seller
    /// @param _feeStatus fee condition (1st bit - seller, 2nd - buyer)
    /// @return result code of an operation
    function releaseBuyerPayment(
        bytes32 _tradeRecordId,
        address _seller,
        address _buyer,
        uint _value,
        uint _expireAtBlock,
        uint _salt,
        bytes _sellerSignature,
        uint8 _feeStatus
    )
    external
    onlyRelay
    returns (uint)
    {
        bytes32 _escrowHash = _getEscrowHash(_tradeRecordId, _seller, _buyer);
        Escrow storage _escrow = escrows[_escrowHash];
        bytes32 _encodedSaltWithEscrow = _getEncodedSaltWithEscrowHash(_salt, _escrowHash);
        require(_escrow.exists, "ESCROW_ETH_ESCROW_SHOULD_EXIST");
        require(!_escrow.disputed, "ESCROW_ETH_ESCROW_SHOULD_NOT_BE_DISPUTED");
        require(!_saltWithEscrow2flagMapping[_encodedSaltWithEscrow], "ESCROW_ETH_SUCH_SALT_SHOULD_NOT_EXIST");
        _assertSeller(_escrowHash, _seller, _value, _expireAtBlock, _salt, _sellerSignature);
        require(block.number < _expireAtBlock, "ESCROW_ETH_TX_SHOULD_NOT_BE_EXPIRED");
        require(_escrow.value >= _value && _value > 0, "ESCROW_ETH_VALUE_SHOULD_BE_CORRECT");

        _saltWithEscrow2flagMapping[_encodedSaltWithEscrow] = true;
        _escrow.value = _escrow.value.sub(_value);
        uint _valueWithoutFee = _takeWithdrawalFee(_value, _feeStatus);
        _buyer.transfer(_valueWithoutFee);
        _emitReleasedPayment(_escrowHash, ETH_SYMBOL, _valueWithoutFee);

        return OK;
    }

    function _assertSeller(
        bytes32 _escrowHash,
        address _seller,
        uint _value,
        uint _expireAtBlock,
        uint _salt,
        bytes _sellerSignature
    )
    private
    view
    {
        bytes memory _message = abi.encodePacked(
            _escrowHash,
            address(this),
            msg.sig,
            _value,
            _expireAtBlock,
            _salt
        );
        address _signer = Signatures.getSignerFromSignature(keccak256(_message), _sellerSignature);
        require(_signer == _seller, "ESCROW_ETH_SIGNER_SHOULD_BE_SELLER");
    }


    /// @notice Transfers `_value` from escrow to seller `_seller`
    /// @dev Escrow is identified by a message = keccak256(_tradeRecordId, _seller, _buyer)
    /// @dev Buyer shoud sign hash of (message, escrow address, msg.sig, _value, _expireAtBlock, _salt) data
    /// @param _tradeRecordId identifier of escrow record
    /// @param _seller who will mainly deposit to escrow
    /// @param _buyer who will eventually is going to receive payment
    /// @param _value amount to withdraw from escrow
    /// @param _expireAtBlock expiry block after which transaction will be invalid
    /// @param _salt random bytes to identify signed data
    /// @param _buyerSignature signature produced by buyer
    /// @return result code of an operation
    function sendSellerPayback(
        bytes32 _tradeRecordId,
        address _seller,
        address _buyer,
        uint _value,
        uint _expireAtBlock,
        uint _salt,
        bytes _buyerSignature
    )
    external
    onlyRelay
    returns (uint)
    {
        bytes32 _escrowHash = _getEscrowHash(_tradeRecordId, _seller, _buyer);
        Escrow storage _escrow = escrows[_escrowHash];
        bytes32 _encodedSaltWithEscrow = _getEncodedSaltWithEscrowHash(_salt, _escrowHash);
        require(_escrow.exists, "ESCROW_ETH_ESCROW_SHOULD_EXIST");
        require(!_escrow.disputed, "ESCROW_ETH_ESCROW_SHOULD_NOT_BE_DISPUTED");
        require(!_saltWithEscrow2flagMapping[_encodedSaltWithEscrow], "ESCROW_ETH_SUCH_SALT_SHOULD_NOT_EXIST");
        bytes memory _message = abi.encodePacked(
            _escrowHash,
            address(this),
            msg.sig,
            _value,
            _expireAtBlock,
            _salt
        );
        address _signer = Signatures.getSignerFromSignature(keccak256(_message), _buyerSignature);
        require(_signer == _buyer, "ESCROW_ETH_SIGNER_SHOULD_BE_BUYER");
        require(block.number < _expireAtBlock, "ESCROW_ETH_TX_SHOULD_NOT_BE_EXPIRED");
        require(_escrow.value >= _value && _value > 0, "ESCROW_ETH_VALUE_SHOULD_BE_CORRECT");

        _saltWithEscrow2flagMapping[_encodedSaltWithEscrow] = true;
        _escrow.value = _escrow.value.sub(_value);
        _seller.transfer(_value);
        _emitPayback(_escrowHash, ETH_SYMBOL, _value);

        return OK;
    }

    /// @notice Transfers `_value` from escrow to seller `_seller` and buyer `_buyer`
    /// @dev Escrow is identified by a message = keccak256(_tradeRecordId, _seller, _buyer)
    /// @dev Seller and buyer shoud sign hash of (message, escrow address, msg.sig, _sellerValue, _buyerValue, _expireAtBlock, _salt) data
    /// @param _tradeRecordId identifier of escrow record
    /// @param _seller who will mainly deposit to escrow
    /// @param _buyer who will eventually is going to receive payment
    /// @param _sellerValue amount to withdraw from escrow to the seller
    /// @param _buyerValue amount to withdraw from escrow to the buyer
    /// @param _expireAtBlock expiry block after which transaction will be invalid
    /// @param _salt random bytes to identify signed data
    /// @param _signatures concatenated signatures produced by seller and buyer
    /// @param _feeStatus fee condition (1st bit - seller, 2nd - buyer)
    /// @return result code of an operation
    function releaseNegotiatedPayment(
        bytes32 _tradeRecordId,
        address _seller,
        address _buyer,
        uint _sellerValue,
        uint _buyerValue,
        uint _expireAtBlock,
        uint _salt,
        bytes _signatures,
        uint8 _feeStatus
    )
    external
    onlyRelay
    returns (uint)
    {
        bytes32 _escrowHash = _getEscrowHash(_tradeRecordId, _seller, _buyer);
        Escrow storage _escrow = escrows[_escrowHash];
        require(_escrow.exists, "ESCROW_ETH_ESCROW_SHOULD_EXIST");
        require(!_escrow.disputed, "ESCROW_ETH_ESCROW_SHOULD_NOT_BE_DISPUTED");
        require(!_saltWithEscrow2flagMapping[_getEncodedSaltWithEscrowHash(_salt, _escrowHash)], "ESCROW_ETH_SUCH_SALT_SHOULD_NOT_EXIST");
        _assertSellerAndBuyer(_escrowHash, _seller, _buyer, _sellerValue, _buyerValue, _expireAtBlock, _salt, _signatures);
        require(block.number < _expireAtBlock, "ESCROW_ETH_TX_SHOULD_NOT_BE_EXPIRED");
        require(_escrow.value >= _sellerValue.add(_buyerValue) && _sellerValue.add(_buyerValue) > 0, "ESCROW_ETH_VALUE_SHOULD_BE_CORRECT");

        _saltWithEscrow2flagMapping[_getEncodedSaltWithEscrowHash(_salt, _escrowHash)] = true;
        _escrow.value = _escrow.value.sub(_sellerValue.add(_buyerValue));
        _seller.transfer(_sellerValue);
        uint _buyerValueWithoutFee = _takeWithdrawalFee(_buyerValue, _feeStatus);
        _buyer.transfer(_buyerValueWithoutFee);
        _emitPayback(_escrowHash, ETH_SYMBOL, _sellerValue);
        _emitReleasedPayment(_escrowHash, ETH_SYMBOL, _buyerValueWithoutFee);

        return OK;
    }

    function _assertSellerAndBuyer(
        bytes32 _escrowHash,
        address _seller,
        address _buyer,
        uint _sellerValue,
        uint _buyerValue,
        uint _expireAtBlock,
        uint _salt,
        bytes _signatures
    )
    private
    view
    {
        bytes memory _message = abi.encodePacked(
            _escrowHash,
            address(this),
            msg.sig,
            _sellerValue,
            _buyerValue,
            _expireAtBlock,
            _salt
        );
        address[] memory _signers = Signatures.getSignersFromSignatures(keccak256(_message), _signatures);
        require(_signers.length == 2 && ((_signers[0] == _seller && _signers[1] == _buyer) ||
            (_signers[0] == _buyer && _signers[1] == _seller)), "ESCROW_ETH_SIGNERS_SHOULD_BE_BUYER_AND_SELLER");
    }

    /// @notice Transfers disputed value from escrow to the seller `_seller` and the buyer `_buyer` according
    /// 	to provided buyer value `_buyerValue`. The value of escrow - _buyerValue will be transferred to the seller.
    /// @dev Escrow is identified by a message = keccak256(_tradeRecordId, _seller, _buyer)
    /// @dev Arbiter should sign hash of (message, escrow address, msg.sig, _buyerValue, _expireAtBlock) data
    /// @param _tradeRecordId identifier of escrow record
    /// @param _seller who will mainly deposit to escrow
    /// @param _buyer who will eventually is going to receive payment
    /// @param _buyerValue value that will be transferred to the buyer
    /// @param _expireAtBlock expiry block after which transaction will be invalid
    /// @param _arbiterSignature signature of an arbiter
    /// @return result code of an operation
    function releaseDisputedPayment(
        bytes32 _tradeRecordId,
        address _seller,
        address _buyer,
        uint _buyerValue,
        uint _expireAtBlock,
        bytes _arbiterSignature
    )
    external
    onlyRelay
    returns (uint)
    {
        bytes32 _escrowHash = _getEscrowHash(_tradeRecordId, _seller, _buyer);
        Escrow storage _escrow = escrows[_escrowHash];
        require(_escrow.exists, "ESCROW_ETH_ESCROW_SHOULD_EXIST");
        require(_escrow.disputed, "ESCROW_ETH_ESCROW_SHOULD_BE_DISPUTED");
        bytes memory _message = abi.encodePacked(
            _escrowHash,
            address(this),
            msg.sig,
            _buyerValue,
            _expireAtBlock
        );
        address _signer = Signatures.getSignerFromSignature(keccak256(_message), _arbiterSignature);
        require(_signer == _escrow.arbiter, "ESCROW_ETH_SIGNER_SHOULD_BE_ARBITER");
        require(block.number < _expireAtBlock, "ESCROW_ETH_TX_SHOULD_NOT_BE_EXPIRED");
        require(_buyerValue <= _escrow.value, "ESCROW_ETH_VALUE_SHOULD_BE_CORRECT");

        if (_buyerValue < _escrow.value) {
            uint _sellerValue = _escrow.value.sub(_buyerValue);
            _seller.transfer(_sellerValue);
            _emitPayback(_escrowHash, ETH_SYMBOL, _sellerValue);
        }
        uint _buyerValueWithoutFee = _takeWithdrawalFee(_buyerValue);
        _buyer.transfer(_buyerValueWithoutFee);

        _deleteEscrow(_escrowHash);
        delete _disputeInitiators[_escrowHash];
        _emitReleasedPayment(_escrowHash, ETH_SYMBOL, _buyerValueWithoutFee);
        _emitDisputeCanceled(_escrowHash);

        return OK;
    }

    /// @notice Deletes escrow record when it is no more needed.
    ///     Escrow should be empty to be deleted.
    /// @param _tradeRecordId identifier of escrow record
    /// @param _seller who will mainly deposit to escrow
    /// @param _buyer who will eventually is going to receive payment
    function deleteEscrow(
        bytes32 _tradeRecordId,
        address _seller,
        address _buyer
    ) external returns (uint) {
        bytes32 _escrowHash = _getEscrowHash(_tradeRecordId, _seller, _buyer);
        Escrow storage _escrow = escrows[_escrowHash];
        if (_escrow.exists && _escrow.value == 0) {
            _deleteEscrow(_escrowHash);
        }

        return OK;
    }

    function _deleteEscrow(bytes32 _escrowHash) private {
        delete escrows[_escrowHash];
    }

    /// @notice Charges a service fee for deposits to escrow contract.
    /// @param _value value from which the commission is taken
    /// @param _feeStatus activate state fee: 1 - active, 0 - not active. 1st bit - seller, 2nd bit - buyer
    /// @return result value without service fee
    function _takeDepositFee(uint _value, uint8 _feeStatus) private returns (uint _valueToDeposit) {
        _valueToDeposit = _value;
        bool _isActivatedRelay = isActivatedRelay();
        (, uint16 _serviceFeeValue, uint _feePrecision) = getServiceFeeInfo();
        if (_serviceFeeValue > 0 && (!_isActivatedRelay || (_isActivatedRelay && _isFeeFlagAppliedFor(_feeStatus, SELLER_FLAG_BIT_IDX)))) {
            _valueToDeposit = PercentCalculator.getFullValueFromPercentedValue(_value, _feePrecision.add(_serviceFeeValue), _feePrecision);
            uint _feeValue = _value.sub(_valueToDeposit);
            accumulatedFee = accumulatedFee.add(_feeValue);
        }
    }

    /// @notice Charges a service fee for withdrawal payments from escrow contract.
    /// @param _value value from which the commission is taken
    /// @param _feeStatus activate state fee: 1 - active, 0 - not active. 1st bit - seller, 2nd bit - buyer
    /// @return result value without service fee
    function _takeWithdrawalFee(uint _value, uint8 _feeStatus) private returns (uint) {
        bool _isActivatedRelay = isActivatedRelay();
        if (!_isActivatedRelay || (_isActivatedRelay && _isFeeFlagAppliedFor(_feeStatus, BUYER_FLAG_BIT_IDX))) {
            return _takeWithdrawalFee(_value);
        }
        return _value;
    }

    function _takeWithdrawalFee(uint _value) private returns (uint) {
        (, uint16 _serviceFeeValue, uint _feePrecision) = getServiceFeeInfo();
        if (_serviceFeeValue > 0) {
            uint _feeValue = PercentCalculator.getPercent(_value, _serviceFeeValue, _feePrecision);
            _value = _value.sub(_feeValue);
            accumulatedFee = accumulatedFee.add(_feeValue);
        }
        return _value;
    }

    /// @notice Transfer accumulated service fee to the serviceFeeAddress
    function withdrawFee() external onlyContractOwner {
        (address _feeDestinationAddress,,) = getServiceFeeInfo();
        require(_feeDestinationAddress != 0x0, "ESCROW_ETH_INVALID_SERVICE_FEE_ADDRESS");

        if (accumulatedFee > 0) {
            uint _accumulatedFee = accumulatedFee;
            accumulatedFee = 0;
            _feeDestinationAddress.transfer(_accumulatedFee);
        }
    }

	function retranslateToFeeRecipient(bytes32 _symbol, address /* _from */, uint _amount) external payable returns (uint) {
        require(hasCurrencySupport(_symbol), "ESCROW_ETH_CURRENCY_SHOULD_BE_SUPPORTED");
        require(msg.value == _amount, "ESCROW_ETH_VALUE_SHOULD_BE_CORRECT");

        accumulatedFee = accumulatedFee.add(_amount);

        return OK;
    }

    function hasCurrencySupport(bytes32 _symbol) public view returns (bool) {
        return _symbol == ETH_SYMBOL;
    }

    function _isFeeAdmin(address _account) internal view returns (bool) {
        return contractOwner == _account;
    }

    function _getEscrowExists(bytes32 _escrowHash) internal view returns(bool) {
        Escrow storage _escrow = escrows[_escrowHash];
        return _escrow.exists;
    }

    function _getEscrowDisputed(bytes32 _escrowHash) internal view returns(bool) {
        Escrow storage _escrow = escrows[_escrowHash];
        return _escrow.disputed;
    }

    function _getEscrowSymbol(bytes32 /* _escrowHash */) internal view returns(bytes32) {
        return ETH_SYMBOL;
    }

    function _getEscrowValue(bytes32 _escrowHash) internal view returns(uint) {
        Escrow storage _escrow = escrows[_escrowHash];
        return _escrow.value;
    }

    function _getEscrowArbiter(bytes32 _escrowHash) internal view returns(address) {
        Escrow storage _escrow = escrows[_escrowHash];
        return _escrow.arbiter;
    }

    function _setEscrowDisputed(bytes32 _escrowHash, bool _disputeStatus) internal {
        Escrow storage _escrow = escrows[_escrowHash];
        _escrow.disputed = _disputeStatus;
    }

    function _setEscrowArbiter(bytes32 _escrowHash, address _arbiter) internal {
        Escrow storage _escrow = escrows[_escrowHash];
        _escrow.arbiter = _arbiter;
    }
}
function deleteEscrow(
    bytes32 _tradeRecordId,
    address _seller,
    address _buyer
) external returns (uint) {
    bytes32 _escrowHash = _getEscrowHash(_tradeRecordId, _seller, _buyer);
    Escrow storage _escrow = escrows[_escrowHash];
    if (_escrow.exists && _escrow.value == 0) {
        _deleteEscrow(_escrowHash);
    }

    return OK;
}

/// @notice Deletes escrow record when it is no more needed.
///     Escrow should be empty to be deleted.
/// @param _tradeRecordId identifier of escrow record
/// @param _seller who will mainly deposit to escrow
/// @param _buyer who will eventually is going to receive payment