/*
 * Copyright 2020 NEM
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.nem.symbol.sdk.infrastructure.vertx;

import com.fasterxml.jackson.databind.node.ObjectNode;
import io.nem.symbol.sdk.api.Listener;
import io.nem.symbol.sdk.api.NamespaceRepository;
import io.nem.symbol.sdk.infrastructure.ListenerBase;
import io.nem.symbol.sdk.infrastructure.ListenerSubscribeMessage;
import io.nem.symbol.sdk.infrastructure.vertx.mappers.GeneralTransactionMapper;
import io.nem.symbol.sdk.infrastructure.TransactionMapper;
import io.nem.symbol.sdk.model.blockchain.BlockInfo;
import io.nem.symbol.sdk.model.transaction.CosignatureSignedTransaction;
import io.nem.symbol.sdk.model.transaction.Transaction;
import io.nem.symbol.sdk.model.transaction.TransactionGroup;
import io.nem.symbol.sdk.openapi.vertx.model.BlockInfoDTO;
import io.nem.symbol.sdk.openapi.vertx.model.Cosignature;
import io.vertx.core.http.HttpClient;
import io.vertx.core.http.RequestOptions;
import io.vertx.core.http.WebSocket;
import io.vertx.core.json.Json;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.concurrent.CompletableFuture;
import org.apache.commons.lang3.exception.ExceptionUtils;

/**
 * Vertx implementations of the {@link Listener}.
 *
 * @since 1.0
 */
public class ListenerVertx extends ListenerBase implements Listener {

    private final URL url;

    private final HttpClient httpClient;

    private final TransactionMapper transactionMapper;

    private WebSocket webSocket;


    /**
     * @param httpClient the http client instance.
     * @param url of the host
     * @param namespaceRepository the namespace repository used to resolve alias.
     */
    public ListenerVertx(HttpClient httpClient, String url, NamespaceRepository namespaceRepository) {
        super(new JsonHelperJackson2(JsonHelperJackson2.configureMapper(Json.mapper)),
            namespaceRepository);
        try {
            this.url = new URL(url);
        } catch (MalformedURLException e) {
            throw new IllegalArgumentException("Parameter '" + url +
                "' is not a valid URL. " + ExceptionUtils.getMessage(e));
        }
        this.httpClient = httpClient;
        this.transactionMapper = new GeneralTransactionMapper(getJsonHelper());
    }

    /**
     * @return a {@link CompletableFuture} that resolves when the websocket connection is opened
     */
    @Override
    public CompletableFuture<Void> open() {

        CompletableFuture<Void> future = new CompletableFuture<>();
        if (this.webSocket != null) {
            return CompletableFuture.completedFuture(null);
        }
        RequestOptions requestOptions = new RequestOptions();
        requestOptions.setHost(this.url.getHost());
        requestOptions.setPort(this.url.getPort());
        requestOptions.setURI("/ws");

        httpClient.websocket(
            requestOptions,
            ws -> {
                this.webSocket = ws;
                ws.handler(
                    handler -> {
                        ObjectNode message = getJsonHelper()
                            .convert(handler.toJsonObject(), ObjectNode.class);
                        handle(message, future);
                    });
            });
        return future;
    }


    @Override
    protected BlockInfo toBlockInfo(Object blockInfoDTO) {
        return BlockRepositoryVertxImpl
            .toBlockInfo(getJsonHelper().convert(blockInfoDTO, BlockInfoDTO.class));
    }

    @Override
    protected Transaction toTransaction(TransactionGroup group, Object transactionInfo) {
        return transactionMapper.mapToFactoryFromDto(transactionInfo).group(group).build();
    }

    @Override
    protected CosignatureSignedTransaction toCosignatureSignedTransaction(
        Object cosignatureJson) {
        Cosignature cosignature = getJsonHelper().convert(cosignatureJson, Cosignature.class);
        return new CosignatureSignedTransaction(cosignature.getVersion(), cosignature.getParentHash(),
            cosignature.getSignature(), cosignature.getSignerPublicKey());
    }

    /**
     * Close webSocket connection
     */
    @Override
    public void close() {
        if (this.webSocket != null) {
            this.setUid(null);
            this.webSocket.close();
            this.webSocket = null;
        }
    }

    protected void subscribeTo(String channel) {
        final ListenerSubscribeMessage subscribeMessage = new ListenerSubscribeMessage(
            this.getUid(),
            channel);
        this.webSocket.writeTextMessage(getJsonHelper().print(subscribeMessage));
    }

}