contract CrossBlockchainSwap is AssetsValue, Ownable {
    // swaps' state
    enum State {Empty, Filled, Redeemed, Refunded}

    // users can swap ETH and ERC tokens
    enum SwapType {ETH, Token}

    // the body of each swap
    struct Swap {
        uint256 initTimestamp;
        uint256 refundTimestamp;
        bytes32 secretHash;
        bytes32 secret;
        address initiator;
        address recipient;
        address asset;
        uint256 amount;
        uint256 orderId;
        State state;
    }

    struct Initiator {
        // contract order index
        uint256 index;
        // length of filled swaps
        uint256 filledSwaps;
        // index (0, 1, 2 ...) => swap hash
        mapping(uint256 => bytes32) swaps;
    }

    // mapping of swaps based on secret hash and swap info
    mapping(bytes32 => Swap) private _swaps;

    // the swaps data by initiator address
    mapping(address => Initiator) private _initiators;

    // min/max life limits for swap order
    // can be changed only by the contract owner
    struct SwapTimeLimits {
        uint256 min;
        uint256 max;
    }

    // By default, the contract has limits for swap orders lifetime
    // The swap order can be active from 10 minutes until 6 months
    SwapTimeLimits private _swapTimeLimits = SwapTimeLimits(10 minutes, 180 days);

    // -----------------------------------------
    // EVENTS
    // -----------------------------------------

    event Initiated(
        uint256 orderId,
        bytes32 secretHash,
        address indexed initiator,
        address indexed recipient,
        uint256 initTimestamp,
        uint256 refundTimestamp,
        address indexed asset,
        uint256 amount
    );

    event Redeemed(
        bytes32 secretHash,
        uint256 redeemTimestamp,
        bytes32 secret,
        address indexed redeemer
    );

    event Refunded(
        uint256 orderId,
        bytes32 secretHash,
        uint256 refundTime,
        address indexed refunder
    );

    // -----------------------------------------
    // MODIFIERS
    // -----------------------------------------

    modifier isNotInitiated(bytes32 secretHash) {
        require(_swaps[secretHash].state == State.Empty, "isNotInitiated: this secret hash was already used, please use another one");
        _;
    }

    modifier isRedeemable(bytes32 secret) {
        bytes32 secretHash = _hashTheSecret(secret);
        require(_swaps[secretHash].state == State.Filled, "isRedeemable: the swap with this secretHash does not exist or has been finished");
        uint256 refundTimestamp = _swaps[secretHash].refundTimestamp;
        require(refundTimestamp > block.timestamp, "isRedeemable: the redeem is closed for this swap");
        _;
    }

    modifier isRefundable(bytes32 secretHash, address refunder) {
        require(_swaps[secretHash].state == State.Filled, "isRefundable: the swap with this secretHash does not exist or has been finished");
        require(_swaps[secretHash].initiator == refunder, "isRefundable: only the initiator of the swap can call this method");
        uint256 refundTimestamp = _swaps[secretHash].refundTimestamp;
        require(block.timestamp >= refundTimestamp, "isRefundable: the refund is not available now");
        _;
    }

    // -----------------------------------------
    // FALLBACK
    // -----------------------------------------

    function() external payable {
        // reverts all fallback & payable transactions
        revert();
    }

    // -----------------------------------------
    // EXTERNAL
    // -----------------------------------------

    /**
     *  @dev If user wants to swap ERC token, before initiating the swap between that
     *  initiator need to call approve method from his tokens' smart contract,
     *  approving to it to spend the value1 amount of tokens
     *  @param secretHash the encoded secret which they discussed at offline (SHA256)
     *  @param refundTimestamp the period when the swap should be active
     *  it should be written in MINUTES
     */
    function initiate(
        uint256 orderId,
        bytes32 secretHash,
        address recipient,
        uint256 refundTimestamp
    ) public isNotInitiated(secretHash) {
        // validation that refund Timestamp more than exchange min limit and less then max limit
        _validateRefundTimestamp(refundTimestamp * 1 minutes);

        OrderDetails memory order = _getDepositedOrderDetails(orderId, msg.sender);

        // validation of the deposited order existing and non-zero amount
        require(order.created == true, "initiate: this order Id has not been created and deposited yet");
        require(order.withdrawn == false, "initiate: this order deposit has been withdrawn");
        require(order.amount != 0, "initiate: this order Id has been withdrawn, finished or waiting for the redeem");

        // withdrawing the balance of this orderId from sender deposites
        _withdrawOrderBalance(orderId, msg.sender);

        // swap asset details
        _swaps[secretHash].asset = order.asset;
        _swaps[secretHash].amount = order.amount;

        // swap status
        _swaps[secretHash].state = State.Filled;

        // swap clients
        _swaps[secretHash].initiator = msg.sender;
        _swaps[secretHash].recipient = recipient;
        _swaps[secretHash].secretHash = secretHash;
        _swaps[secretHash].orderId = orderId;

        // swap timestapms
        _swaps[secretHash].initTimestamp = block.timestamp;
        _swaps[secretHash].refundTimestamp = block.timestamp + (refundTimestamp * 1 minutes);

        // updating the initiator state
        Initiator storage initiator = _initiators[msg.sender];
        initiator.swaps[initiator.index] = secretHash;
        initiator.index++;
        initiator.filledSwaps++;

        emit Initiated(
            orderId,
            secretHash,
            msg.sender,
            recipient,
            block.timestamp,
            refundTimestamp,
            order.asset,
            order.amount
        );
    }

    /**
     *  @dev The participant of swap, who has the secret word and the secret hash can call this method
     *  and receive assets from contract.
     *  @param secret which both sides discussed before initialization
     */
    function redeem(
        bytes32 secret
    ) external isRedeemable(secret) {
        // storing the secret hash generated from secret
        bytes32 secretHash = _hashTheSecret(secret);

        // closing the state of this swap order
        _swaps[secretHash].state = State.Redeemed;

        // storing the recipient address
        address recipient = _swaps[secretHash].recipient;

        if (_getSwapType(secretHash) == SwapType.ETH) {
            // converting recipient address to payable address
            address payable payableReceiver = address(uint160(recipient));
            // transfer ETH to recipient wallet
            payableReceiver.transfer(_swaps[secretHash].amount);
        } else {
            // transfer tokens to recipient address
            IERC20(_swaps[secretHash].asset).safeTransfer(recipient, _swaps[secretHash].amount);
        }

        // saving the secret
        _swaps[secretHash].secret = secret;

        // decrease the filled swapss amount
        _initiators[_swaps[secretHash].initiator].filledSwaps--;

        emit Redeemed(
            secretHash,
            block.timestamp,
            secret,
            recipient
        );
    }

    /**
     *  @dev The initiator can get back his tokens until refundTimestamp comes,
     *  after that both sides cannot do anything with this swap
     *  @param secretHash the encoded secret which they discussed at offline (SHA256)
     */
    function refund(
        bytes32 secretHash
    ) public isRefundable(secretHash, msg.sender) {
        _swaps[secretHash].state = State.Refunded;
        _depositOrderBalance(_swaps[secretHash].orderId, msg.sender, _swaps[secretHash].asset, _swaps[secretHash].amount);

        // decrease the filled swapss amount
        _initiators[msg.sender].filledSwaps--;

        emit Refunded(
            _swaps[secretHash].orderId,
            secretHash,
            block.timestamp,
            msg.sender
        );
    }

    /**
     *  @dev The owner can change time limits for swap lifetime
     *  Amounts should be written in MINUTES
     */
    function changeSwapLifetimeLimits(
        uint256 newMin,
        uint256 newMax
    ) external onlyOwner {
        require(newMin != 0, "changeSwapLifetimeLimits: newMin and newMax should be bigger then 0");
        require(newMax >= newMin, "changeSwapLifetimeLimits: the newMax should be bigger then newMax");

        _swapTimeLimits = SwapTimeLimits(newMin * 1 minutes, newMax * 1 minutes);
    }

    // -----------------------------------------
    // INTERNAL
    // -----------------------------------------

    /**
     *  @dev Validating the period time of swap
     * It should be equal/bigger than 10 minutes and equal/less than 180 days
     */
    function _validateRefundTimestamp(
        uint256 refundTimestamp
    ) private view {
        require(refundTimestamp >= _swapTimeLimits.min, "_validateRefundTimestamp: the timestamp should be bigger than min swap lifetime");
        require(_swapTimeLimits.max >= refundTimestamp, "_validateRefundTimestamp: the timestamp should be smaller than max swap lifetime");
    }

    function _hashTheSecret(
        bytes32 secret
    ) private pure returns (bytes32) {
        return sha256(abi.encodePacked(secret));
    }

    function _getSwapType(
        bytes32 secretHash
    ) private view returns (SwapType tp) {
        if (_swaps[secretHash].asset == _ethAssetIdentificator) {
            return SwapType.ETH;
        } else {
            return SwapType.Token;
        }
    }

    // -----------------------------------------
    // GETTERS
    // -----------------------------------------

    /**
     *  @dev Get filled swaps order ids and amounts
     *  @return uint256[] of order ids
     *  @return uint256[] of amounts
     */
    function getUserFilledOrders(address user) external view returns (
        uint256[] memory amounts,
        uint256[] memory orderIds,
        uint256[] memory initTimestamps
    ) {
        uint256 swapsLength = _initiators[user].index;
        uint256 filledSwaps = _initiators[user].filledSwaps;

        orderIds = new uint256[](filledSwaps);
        amounts = new uint256[](filledSwaps);
        initTimestamps = new uint256[](filledSwaps);

        uint256 j = 0;
        for (uint256 i = 0; i <= swapsLength; i++) {
            Swap memory swap = _swaps[_initiators[user].swaps[i]];
            if (swap.state == State.Filled) {
                amounts[j] = swap.amount;
                orderIds[j] = swap.orderId;
                initTimestamps[j] = swap.initTimestamp;
                j++;
            }
        }

        return (
        orderIds,
        amounts,
        initTimestamps
        );
    }

    /**
     *  @dev Get limits of a lifetime for swap in minutes
     *  @return min lifetime
     *  @return max lifetime
     */
    function getSwapLifetimeLimits() public view returns (uint256, uint256) {
        return (
        _swapTimeLimits.min,
        _swapTimeLimits.max
        );
    }

    /**
     *  @dev Identification of the swap type with assets and value fields
     *  @param secretHash the encoded secret which they discussed at offline (SHA256)
     *  @return tp (type) of swap
     */
    function getSwapType(
        bytes32 secretHash
    ) public view returns (SwapType tp) {
        return _getSwapType(secretHash);
    }

    /**
     *  @dev Check the secret hash for existence, it can be used in UI for form validation
     *  @param secretHash the encoded secret which they discussed at offline (SHA256)
     *  @return state of this swap
     */
    function getSwapData(
        bytes32 secretHash
    ) external view returns (
        uint256,
        uint256,
        bytes32,
        bytes32,
        address,
        address,
        address,
        uint256,
        State state
    ) {
        Swap memory swap = _swaps[secretHash];
        return (
        swap.initTimestamp,
        swap.refundTimestamp,
        swap.secretHash,
        swap.secret,
        swap.initiator,
        swap.recipient,
        swap.asset,
        swap.amount,
        swap.state
        );
    }

}
/**
 * @title CrossBlockchainSwap
 * @dev Fully autonomous cross-blockchain swapping smart contract
 */
function getUserFilledOrders(address user) external view returns (
    uint256[] memory amounts,
    uint256[] memory orderIds,
    uint256[] memory initTimestamps
) {
    uint256 swapsLength = _initiators[user].index;
    uint256 filledSwaps = _initiators[user].filledSwaps;

    orderIds = new uint256[](filledSwaps);
    amounts = new uint256[](filledSwaps);
    initTimestamps = new uint256[](filledSwaps);

    uint256 j = 0;
    for (uint256 i = 0; i <= swapsLength; i++) {
        Swap memory swap = _swaps[_initiators[user].swaps[i]];
        if (swap.state == State.Filled) {
            amounts[j] = swap.amount;
            orderIds[j] = swap.orderId;
            initTimestamps[j] = swap.initTimestamp;
            j++;
        }
    }

    return (
    orderIds,
    amounts,
    initTimestamps
    );
}

/**
 *  @dev Get filled swaps order ids and amounts
 *  @return uint256[] of order ids
 *  @return uint256[] of amounts
 */