package com.wavesplatform.wavesj.protobuf;

import com.google.protobuf.ByteString;
import com.wavesplatform.protobuf.AmountOuterClass;
import com.wavesplatform.protobuf.order.OrderOuterClass;
import com.wavesplatform.protobuf.transaction.RecipientOuterClass;
import com.wavesplatform.protobuf.transaction.ScriptOuterClass;
import com.wavesplatform.protobuf.transaction.TransactionOuterClass;
import com.wavesplatform.wavesj.*;
import com.wavesplatform.wavesj.matcher.Order;
import com.wavesplatform.wavesj.matcher.OrderV1;
import com.wavesplatform.wavesj.matcher.OrderV2;
import com.wavesplatform.wavesj.transactions.*;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

@SuppressWarnings({"unused", "WeakerAccess"})
public class PBTransactions {
    public static Transaction toVanilla(final TransactionOuterClass.SignedTransaction signedTransaction) {
        final TransactionOuterClass.Transaction tx = signedTransaction.getTransaction();
        if (tx.getVersion() != 1 && tx.getVersion() != 2)
            throw new IllegalArgumentException("TX version not supported: " + tx);

        final PublicKeyAccount senderPublicKey = new PublicKeyAccount(tx.getSenderPublicKey().toByteArray(), (byte) tx.getChainId());
        final long feeAmount = tx.getFee().getAmount();
        final long timestamp = tx.getTimestamp();
        final com.wavesplatform.wavesj.ByteString signature = toSignature(signedTransaction.getProofsList());
        final List<com.wavesplatform.wavesj.ByteString> proofs = toVanillaProofs(signedTransaction.getProofsList());

        if (tx.hasBurn()) {
            final TransactionOuterClass.BurnTransactionData burn = tx.getBurn();
            switch (tx.getVersion()) {
                case Transaction.V1:
                    return new BurnTransactionV1(senderPublicKey, toVanillaAssetId(burn.getAssetAmount().getAssetId()), burn.getAssetAmount().getAmount(), feeAmount, timestamp, signature);

                case Transaction.V2:
                    return new BurnTransactionV2(senderPublicKey, (byte) tx.getChainId(), toVanillaAssetId(burn.getAssetAmount().getAssetId()), burn.getAssetAmount().getAmount(), feeAmount, timestamp, proofs);
            }
        } else if (tx.hasCreateAlias()) {
            final TransactionOuterClass.CreateAliasTransactionData createAlias = tx.getCreateAlias();
            switch (tx.getVersion()) {
                case Transaction.V1:
                    return new AliasTransactionV1(senderPublicKey, new Alias(createAlias.getAlias(), (byte) tx.getChainId()), feeAmount, timestamp, signature);

                case Transaction.V2:
                    return new AliasTransactionV2(senderPublicKey, new Alias(createAlias.getAlias(), (byte) tx.getChainId()), feeAmount, timestamp, proofs);
            }
        } else if (tx.hasDataTransaction()) {
            final TransactionOuterClass.DataTransactionData data = tx.getDataTransaction();
            return new DataTransaction(senderPublicKey, toVanillaDataEntryList(data.getDataList()), feeAmount, timestamp, proofs);
        } else if (tx.hasExchange()) {
            final TransactionOuterClass.ExchangeTransactionData exchange = tx.getExchange();
            switch (tx.getVersion()) {
                case Transaction.V1:
                    return new ExchangeTransactionV1(toVanillaOrder(exchange.getOrders(0)), toVanillaOrder(exchange.getOrders(1)), senderPublicKey, exchange.getAmount(), exchange.getPrice(), exchange.getBuyMatcherFee(), exchange.getSellMatcherFee(), feeAmount, timestamp, signature);

                case Transaction.V2:
                    return new ExchangeTransactionV2(senderPublicKey, toVanillaOrder(exchange.getOrders(0)), toVanillaOrder(exchange.getOrders(1)), exchange.getAmount(), exchange.getPrice(), exchange.getBuyMatcherFee(), exchange.getSellMatcherFee(), feeAmount, timestamp, proofs);
            }

        }  else if (tx.hasInvokeScript()) {
            final TransactionOuterClass.InvokeScriptTransactionData data = tx.getInvokeScript();
            final List<InvokeScriptTransaction.Payment> payments = new ArrayList<InvokeScriptTransaction.Payment>(data.getPaymentsCount());
            for (AmountOuterClass.Amount payment : data.getPaymentsList()) payments.add(new InvokeScriptTransaction.Payment(payment.getAmount(), toVanillaAssetId(payment.getAssetId())));
            return new InvokeScriptTransaction((byte) tx.getChainId(), senderPublicKey, Base58.encode(data.getDApp().toByteArray()), InvokeScriptTransaction.FunctionCall.fromBytes(data.getFunctionCall().asReadOnlyByteBuffer()), Collections.unmodifiableList(payments), feeAmount, toVanillaAssetId(tx.getFee().getAssetId()), timestamp, proofs);
        } else if (tx.hasIssue()) {
            final TransactionOuterClass.IssueTransactionData issue = tx.getIssue();
            switch (tx.getVersion()) {
                case Transaction.V1:
                    return new IssueTransactionV1(senderPublicKey, new String(issue.getName().toByteArray()), new String(issue.getDescription().toByteArray()), issue.getAmount(), (byte) issue.getDecimals(), issue.getReissuable(), feeAmount, timestamp, signature);

                case Transaction.V2:
                    return new IssueTransactionV2(senderPublicKey, (byte) tx.getChainId(), new String(issue.getName().toByteArray()), new String(issue.getDescription().toByteArray()), issue.getAmount(), (byte) issue.getDecimals(), issue.getReissuable(), Base58.encode(issue.getScript().getBytes().toByteArray()), feeAmount, timestamp, proofs);
            }
        } else if (tx.hasReissue()) {
            final TransactionOuterClass.ReissueTransactionData reissue = tx.getReissue();
            switch (tx.getVersion()) {
                case Transaction.V1:
                    return new ReissueTransactionV1(senderPublicKey, toVanillaAssetId(reissue.getAssetAmount().getAssetId()), reissue.getAssetAmount().getAmount(), reissue.getReissuable(), feeAmount, timestamp, signature);

                case Transaction.V2:
                    return new ReissueTransactionV2(senderPublicKey, (byte) tx.getChainId(), toVanillaAssetId(reissue.getAssetAmount().getAssetId()), reissue.getAssetAmount().getAmount(), reissue.getReissuable(), feeAmount, timestamp, proofs);
            }
        } else if (tx.hasSetAssetScript()) {
            final TransactionOuterClass.SetAssetScriptTransactionData sas = tx.getSetAssetScript();
            return new SetAssetScriptTransaction(senderPublicKey, (byte) tx.getChainId(), toVanillaAssetId(sas.getAssetId()), Base58.encode(sas.getScript().getBytes().toByteArray()), feeAmount, timestamp, proofs);
        } else if (tx.hasSetScript()) {
            final TransactionOuterClass.SetScriptTransactionData setScript = tx.getSetScript();
            return new SetScriptTransaction(senderPublicKey, Base58.encode(setScript.getScript().getBytes().toByteArray()), (byte) tx.getChainId(), feeAmount, timestamp, proofs);
        } else if (tx.hasTransfer()) {
            final TransactionOuterClass.TransferTransactionData transfer = tx.getTransfer();
            switch (tx.getVersion()) {
                case Transaction.V1:
                    return new TransferTransactionV1(senderPublicKey, toRecipientString(transfer.getRecipient(), (byte) tx.getChainId()), transfer.getAmount().getAmount(), toVanillaAssetId(transfer.getAmount().getAssetId()), feeAmount, toVanillaAssetId(tx.getFee().getAssetId()), toVanillaByteString(transfer.getAttachment()), timestamp, signature);

                case Transaction.V2:
                    return new TransferTransactionV2(senderPublicKey, toRecipientString(transfer.getRecipient(), (byte) tx.getChainId()), transfer.getAmount().getAmount(), toVanillaAssetId(transfer.getAmount().getAssetId()), feeAmount, toVanillaAssetId(tx.getFee().getAssetId()), toVanillaByteString(transfer.getAttachment()), timestamp, proofs);
            }
        } else if (tx.hasLease()) {
            final TransactionOuterClass.LeaseTransactionData lease = tx.getLease();
            switch (tx.getVersion()) {
                case Transaction.V1:
                    return new LeaseTransactionV1(senderPublicKey, toRecipientString(lease.getRecipient(), (byte) tx.getChainId()), lease.getAmount(), feeAmount, timestamp, signature);

                case Transaction.V2:
                    return new LeaseTransactionV2(senderPublicKey, toRecipientString(lease.getRecipient(), (byte) tx.getChainId()), lease.getAmount(), feeAmount, timestamp, proofs);
            }
        } else if (tx.hasLeaseCancel()) {
            final TransactionOuterClass.LeaseCancelTransactionData leaseCancel = tx.getLeaseCancel();
            switch (tx.getVersion()) {
                case Transaction.V1:
                    return new LeaseCancelTransactionV1(senderPublicKey, Base58.encode(leaseCancel.getLeaseId().toByteArray()), feeAmount, timestamp, signature);

                case Transaction.V2:
                    return new LeaseCancelTransactionV2(senderPublicKey, (byte) tx.getChainId(), Base58.encode(leaseCancel.getLeaseId().toByteArray()), feeAmount, timestamp, proofs);
            }
        } else if (tx.hasMassTransfer()) {
            final TransactionOuterClass.MassTransferTransactionData massTransfer = tx.getMassTransfer();
            final List<Transfer> transfers = new ArrayList<Transfer>(massTransfer.getTransfersList().size());
            for (TransactionOuterClass.MassTransferTransactionData.Transfer transfer : massTransfer.getTransfersList()) {
                final Transfer transfer1 = new Transfer(toRecipientString(transfer.getAddress(), (byte) tx.getChainId()), transfer.getAmount());
                transfers.add(transfer1);
            }
            return new MassTransferTransaction(senderPublicKey, toVanillaAssetId(massTransfer.getAssetId()), Collections.unmodifiableList(transfers), feeAmount, toVanillaByteString(massTransfer.getAttachment()), timestamp, proofs);
        } else if (tx.hasSponsorFee()) {
            final TransactionOuterClass.SponsorFeeTransactionData sponsorFee = tx.getSponsorFee();
            return new SponsorTransaction(senderPublicKey, toVanillaAssetId(sponsorFee.getMinFee().getAssetId()), sponsorFee.getMinFee().getAmount(), feeAmount, timestamp, proofs);
        }

        throw new IllegalArgumentException("Invalid TX: " + tx);
    }

    public static TransactionOuterClass.SignedTransaction toPB(final Transaction tx) {
        TransactionOuterClass.Transaction.Builder base = TransactionOuterClass.Transaction.newBuilder()
                .setFee(toPBAmount(Asset.WAVES, tx.getFee()))
                .setTimestamp(tx.getTimestamp())
                .setVersion(tx.getVersion());

        if (tx instanceof IssueTransaction) {
            final IssueTransaction issue = (IssueTransaction) tx;

            ByteString script = ByteString.EMPTY;
            if (issue instanceof IssueTransactionV2)
                script = ByteString.copyFrom(Base58.decode(((IssueTransactionV2) issue).getScript()));

            final TransactionOuterClass.IssueTransactionData data = TransactionOuterClass.IssueTransactionData.newBuilder()
                    .setAmount(issue.getQuantity())
                    .setDecimals(issue.getDecimals())
                    .setName(ByteString.copyFrom(issue.getName().getBytes()))
                    .setDescription(ByteString.copyFrom(issue.getDescription().getBytes()))
                    .setReissuable(issue.isReissuable())
                    .setScript(ScriptOuterClass.Script.newBuilder().setBytes(script))
                    .build();
            base.setIssue(data);
        } else if (tx instanceof ReissueTransaction) {
            final ReissueTransaction reissue = (ReissueTransaction) tx;
            final TransactionOuterClass.ReissueTransactionData data = TransactionOuterClass.ReissueTransactionData.newBuilder()
                    .setAssetAmount(AmountOuterClass.Amount.newBuilder().setAssetId(assetIdToBytes(reissue.getAssetId())).setAmount(reissue.getQuantity()).build())
                    .setReissuable(reissue.isReissuable())
                    .build();
            base.setReissue(data);
        } else if (tx instanceof BurnTransaction) {
            final BurnTransaction burn = (BurnTransaction) tx;
            final TransactionOuterClass.BurnTransactionData data = TransactionOuterClass.BurnTransactionData.newBuilder()
                    .setAssetAmount(AmountOuterClass.Amount.newBuilder().setAssetId(assetIdToBytes(burn.getAssetId())).setAmount(burn.getAmount()).build())
                    .build();
            base.setBurn(data);
        } else if (tx instanceof SetScriptTransaction) {
            final SetScriptTransaction setScript = (SetScriptTransaction) tx;
            final TransactionOuterClass.SetScriptTransactionData data = TransactionOuterClass.SetScriptTransactionData.newBuilder()
                    .setScript(ScriptOuterClass.Script.newBuilder().setBytes(ByteString.copyFrom(Base58.decode(setScript.getScript()))).build())
                    .build();
            base.setSetScript(data);
        } else if (tx instanceof SetAssetScriptTransaction) {
            final SetAssetScriptTransaction sas = (SetAssetScriptTransaction) tx;
            final TransactionOuterClass.SetAssetScriptTransactionData data = TransactionOuterClass.SetAssetScriptTransactionData
                    .newBuilder()
                    .setAssetId(assetIdToBytes(sas.getAssetId()))
                    .setScript(ScriptOuterClass.Script.newBuilder().setBytes(ByteString.copyFrom(Base58.decode(sas.getScript()))).build())
                    .build();
            base.setSetAssetScript(data);
        } else if (tx instanceof DataTransaction) {
            final DataTransaction dataTransaction = (DataTransaction) tx;

            final List<TransactionOuterClass.DataTransactionData.DataEntry> dataEntries = new ArrayList<TransactionOuterClass.DataTransactionData.DataEntry>(dataTransaction.getData().size());
            for (DataEntry<?> dataEntry : dataTransaction.getData()) dataEntries.add(toPBDataEntry(dataEntry));

            final TransactionOuterClass.DataTransactionData data = TransactionOuterClass.DataTransactionData.newBuilder()
                    .addAllData(dataEntries)
                    .build();

            base.setDataTransaction(data);
        } else if (tx instanceof MassTransferTransaction) {
            final MassTransferTransaction mtt = (MassTransferTransaction) tx;
            final ByteString assetId = assetIdToBytes(mtt.getAssetId());
            final List<TransactionOuterClass.MassTransferTransactionData.Transfer> transfers = new ArrayList<TransactionOuterClass.MassTransferTransactionData.Transfer>(mtt.getTransfers().size());
            for (Transfer transfer : mtt.getTransfers()) {
                TransactionOuterClass.MassTransferTransactionData.Transfer transfer1 = TransactionOuterClass.MassTransferTransactionData.Transfer.newBuilder()
                        .setAddress(toPBRecipient(transfer.getRecipient()))
                        .setAmount(transfer.getAmount()).build();
                transfers.add(transfer1);
            }

            final TransactionOuterClass.MassTransferTransactionData data = TransactionOuterClass.MassTransferTransactionData.newBuilder()
                    .setAssetId(assetId)
                    .setAttachment(toPBByteString(mtt.getAttachment()))
                    .addAllTransfers(transfers)
                    .build();
            base.setMassTransfer(data);
        } else if (tx instanceof TransferTransaction) {
            final TransferTransaction transfer = (TransferTransaction) tx;
            final TransactionOuterClass.TransferTransactionData data = TransactionOuterClass.TransferTransactionData.newBuilder()
                    .setRecipient(toPBRecipient(transfer.getRecipient()))
                    .setAmount(toPBAmount(transfer.getAssetId(), transfer.getAmount()))
                    .setAttachment(toPBByteString(transfer.getAttachment()))
                    .build();
            base.setTransfer(data);

            if (tx instanceof TransferTransactionV2)
                base.setFee(toPBAmount(((TransferTransactionV2) tx).getFeeAssetId(), tx.getFee()));
        } else if (tx instanceof SponsorTransaction) {
            final SponsorTransaction sponsor = (SponsorTransaction) tx;
            final TransactionOuterClass.SponsorFeeTransactionData data = TransactionOuterClass.SponsorFeeTransactionData.newBuilder()
                    .setMinFee(AmountOuterClass.Amount.newBuilder().setAssetId(assetIdToBytes(sponsor.getAssetId())).setAmount(sponsor.getMinSponsoredAssetFee()).build())
                    .build();
            base.setSponsorFee(data);
        } else if (tx instanceof ExchangeTransaction) {
            final ExchangeTransaction exchange = (ExchangeTransaction) tx;
            final TransactionOuterClass.ExchangeTransactionData data = TransactionOuterClass.ExchangeTransactionData.newBuilder()
                    .setPrice(exchange.getPrice())
                    .setAmount(exchange.getAmount())
                    .setBuyMatcherFee(exchange.getBuyMatcherFee())
                    .setSellMatcherFee(exchange.getSellMatcherFee())
                    .setOrders(0, toPBOrder(exchange.getOrder1()))
                    .setOrders(1, toPBOrder(exchange.getOrder2()))
                    .build();
            base.setExchange(data);
        } else if (tx instanceof InvokeScriptTransaction) {
            final InvokeScriptTransaction ist = (InvokeScriptTransaction) tx;
            final List<AmountOuterClass.Amount> payments = new ArrayList<AmountOuterClass.Amount>(ist.getPayments().size());
            for (InvokeScriptTransaction.Payment payment : ist.getPayments())
                payments.add(toPBAmount(payment.getAssetId(), payment.getAmount()));

            final TransactionOuterClass.InvokeScriptTransactionData data = TransactionOuterClass.InvokeScriptTransactionData.newBuilder()
                    .setDApp(toPBRecipient(ist.getdApp()))
                    .setFunctionCall(ByteString.copyFrom(ist.getCall().toBytes()))
                    .addAllPayments(payments)
                    .build();
            base.setInvokeScript(data);
        }

        List<ByteString> proofs = new ArrayList<ByteString>();
        if (tx instanceof TransactionWithProofs)
            //noinspection unchecked
            for (com.wavesplatform.wavesj.ByteString proof : ((List<com.wavesplatform.wavesj.ByteString>) (((TransactionWithProofs) tx).getProofs())))
                proofs.add(toPBByteString(proof));
        else if (tx instanceof TransactionWithSignature)
            proofs.add(toPBByteString(((TransactionWithSignature) tx).getSignature()));

        return TransactionOuterClass.SignedTransaction.newBuilder()
                .addAllProofs(proofs)
                .setTransaction(base)
                .build();
    }

    public static String toVanillaAssetId(final ByteString assetId) {
        if (assetId.isEmpty()) return Asset.WAVES;
        else return Asset.normalize(Base58.encode(assetId.toByteArray()));
    }


    public static ByteString assetIdToBytes(final String assetId) {
        if (Asset.isWaves(assetId)) return ByteString.EMPTY;
        else return ByteString.copyFrom(Base58.decode(assetId));
    }

    public static AmountOuterClass.Amount toPBAmount(final String assetId, final long amount) {
        return AmountOuterClass.Amount.newBuilder().setAssetId(assetIdToBytes(assetId)).build();
    }

    public static DataEntry<?> toVanillaDataEntry(final TransactionOuterClass.DataTransactionData.DataEntry dataEntry) {
        DataEntry<?> result;
        switch (dataEntry.getValueCase()) {
            case STRING_VALUE:
                result = new DataEntry.StringEntry(dataEntry.getKey(), dataEntry.getStringValue());
                break;
            case BOOL_VALUE:
                result = new DataEntry.BooleanEntry(dataEntry.getKey(), dataEntry.getBoolValue());
                break;
            case INT_VALUE:
                result = new DataEntry.LongEntry(dataEntry.getKey(), dataEntry.getIntValue());
                break;
            case BINARY_VALUE:
                result = new DataEntry.BinaryEntry(dataEntry.getKey(), toVanillaByteString(dataEntry.getBinaryValue()));
                break;
            default:
                throw new IllegalArgumentException("Not supported: " + dataEntry);
        }
        return result;
    }

    public static TransactionOuterClass.DataTransactionData.DataEntry toPBDataEntry(final DataEntry<?> dataEntry) {
        TransactionOuterClass.DataTransactionData.DataEntry.Builder builder = TransactionOuterClass.DataTransactionData.DataEntry.newBuilder();

        if (dataEntry.getType().equals("integer")) builder.setIntValue((Long) dataEntry.getValue());
        else if (dataEntry.getType().equals("string")) builder.setStringValue((String) dataEntry.getValue());
        else if (dataEntry.getType().equals("boolean")) builder.setBoolValue((Boolean) dataEntry.getValue());
        else if (dataEntry.getType().equals("binary"))
            builder.setBinaryValue(toPBByteString((com.wavesplatform.wavesj.ByteString) dataEntry.getValue()));

        return builder.setKey(dataEntry.getKey()).build();
    }

    public static Order toVanillaOrder(final OrderOuterClass.Order order) {
        final Order.Type orderType = order.getOrderSide() == OrderOuterClass.Order.Side.BUY ? Order.Type.BUY : Order.Type.SELL;
        final AssetPair assetPair = new AssetPair(toVanillaAssetId(order.getAssetPair().getAmountAssetId()), toVanillaAssetId(order.getAssetPair().getPriceAssetId()));
        final PublicKeyAccount senderPublicKey = new PublicKeyAccount(order.getSenderPublicKey().toByteArray(), (byte) order.getChainId());
        final PublicKeyAccount matcherPk = new PublicKeyAccount(order.getMatcherPublicKey().toByteArray(), (byte) order.getChainId());

        switch (order.getVersion()) {
            case Order.V1:
                return new OrderV1(senderPublicKey, matcherPk, orderType, assetPair, order.getAmount(), order.getPrice(), order.getTimestamp(), order.getExpiration(), order.getMatcherFee().getAmount(), toSignature(order.getProofsList()));
            case Order.V2:
                return new OrderV2(senderPublicKey, matcherPk, orderType, assetPair, order.getAmount(), order.getPrice(), order.getTimestamp(), order.getExpiration(), order.getMatcherFee().getAmount(), (byte) order.getVersion(), toVanillaProofs(order.getProofsList()));
            default:
                throw new IllegalArgumentException("Order not supported: " + order);
        }
    }

    public static OrderOuterClass.Order toPBOrder(final Order order) {
        final OrderOuterClass.Order.Side orderType = order.getOrderType() == Order.Type.BUY ? OrderOuterClass.Order.Side.BUY : OrderOuterClass.Order.Side.SELL;

        final List<ByteString> proofs = new ArrayList<ByteString>(order.getProofs().size());
        for (com.wavesplatform.wavesj.ByteString proof : order.getProofs()) proofs.add(toPBByteString(proof));

        return OrderOuterClass.Order.newBuilder()
                .setAmount(order.getAmount())
                .setPrice(order.getPrice())
                .setAssetPair(OrderOuterClass.AssetPair.newBuilder()
                        .setAmountAssetId(assetIdToBytes(order.getAssetPair().getAmountAsset()))
                        .setPriceAssetId(assetIdToBytes(order.getAssetPair().getPriceAsset())).build())
                .setExpiration(order.getExpiration())
                .setMatcherFee(AmountOuterClass.Amount.newBuilder().setAmount(order.getMatcherFee()).build())
                .setMatcherPublicKey(ByteString.copyFrom(order.getMatcherPublicKey().getPublicKey()))
                .setOrderSide(orderType)
                .setSenderPublicKey(ByteString.copyFrom(order.getSenderPublicKey().getPublicKey()))
                .setVersion(1)
                .addAllProofs(proofs)
                .build();
    }

    public static String toRecipientString(final RecipientOuterClass.Recipient recipient, final byte chainId) {
        switch (recipient.getRecipientCase()) {
            case ALIAS:
                return recipient.getAlias();
            case ADDRESS:
                final ByteBuffer withoutChecksum = ByteBuffer.allocate(2 + recipient.getAddress().size())
                        .put((byte) 1)
                        .put(chainId)
                        .put(recipient.getAddress().toByteArray());
                withoutChecksum.flip();
                final byte[] checksum = Hash.secureHash(withoutChecksum.array(), 0, withoutChecksum.capacity());

                final ByteBuffer addrBytes = ByteBuffer.allocate(withoutChecksum.capacity() + 4)
                        .put(withoutChecksum)
                        .put(checksum, 0, 4);
                addrBytes.flip();
                return Base58.encode(addrBytes.array());

            default:
                throw new IllegalArgumentException("Recipient not supported: " + recipient);
        }
    }

    public static RecipientOuterClass.Recipient toPBRecipient(final String recipient) {
        try {
            final byte[] sourceAddr = Base58.decode(recipient);
            assert sourceAddr.length == 20;

            final byte[] addr = Arrays.copyOfRange(sourceAddr, 2, sourceAddr.length - 4);
            return RecipientOuterClass.Recipient.newBuilder().setAddress(ByteString.copyFrom(addr)).build();
        } catch (Throwable e) {
            return RecipientOuterClass.Recipient.newBuilder().setAlias(recipient).build();
        }
    }

    private static com.wavesplatform.wavesj.ByteString toSignature(final List<ByteString> proofs) {
        if (proofs.isEmpty()) return com.wavesplatform.wavesj.ByteString.EMPTY;
        else return toVanillaByteString(proofs.get(0));
    }

    public static com.wavesplatform.wavesj.ByteString toVanillaByteString(final ByteString bs) {
        return new com.wavesplatform.wavesj.ByteString(bs.toByteArray());
    }


    public static ByteString toPBByteString(final com.wavesplatform.wavesj.ByteString bs) {
        return ByteString.copyFrom(bs.getBytes());
    }

    private static List<com.wavesplatform.wavesj.ByteString> toVanillaProofs(final List<ByteString> proofs) {
        final List<com.wavesplatform.wavesj.ByteString> result = new ArrayList<com.wavesplatform.wavesj.ByteString>(proofs.size());
        for (ByteString proof : proofs) result.add(toVanillaByteString(proof));
        return Collections.unmodifiableList(result);
    }

    private static List<DataEntry<?>> toVanillaDataEntryList(final List<TransactionOuterClass.DataTransactionData.DataEntry> dataEntries) {
        final List<DataEntry<?>> result = new ArrayList<DataEntry<?>>(dataEntries.size());
        for (TransactionOuterClass.DataTransactionData.DataEntry dataEntry : dataEntries)
            result.add(toVanillaDataEntry(dataEntry));
        return Collections.unmodifiableList(result);
    }
}