import com.wavesplatform.wavesj.*;
import com.wavesplatform.wavesj.matcher.Order;

import java.io.IOException;
import java.net.URISyntaxException;
import java.util.Timer;
import java.util.TimerTask;

public class SpreadBot {
    private final long maxOrderSize = 1 * Asset.MILLI; // in priceAsset
    private final double halfSpread = 0.01; // 1%
    private final long fee = 3 * Asset.MILLI; // in WAVES
    private final long period = 60000; // 1 min

    private final PrivateKeyAccount account;
    private final AssetPair market;
    private final Node node, matcher;
    private final String matcherKey;

    public static SpreadBot testnetInstance() throws IOException, URISyntaxException {
        return new SpreadBot(
                PrivateKeyAccount.fromPrivateKey("25Um7fKYkySZnweUEVAn9RLtxN5xHRd7iqpqYSMNQEeT", Account.TESTNET),
                new AssetPair(Asset.WAVES, "Fmg13HEHJHuZYbtJq8Da8wifJENq8uBxDuWoP9pVe2Qe"),
                "https://testnode2.wavesnodes.com",
                "https://testnode2.wavesnodes.com",  'T');
    }

    public static SpreadBot mainnetInstance() throws IOException, URISyntaxException {
        String nodeUrl = "http://dex-aws-ir-1.wavesnodes.com";
        return new SpreadBot(
                PrivateKeyAccount.fromSeed(
                        "abandon ability able about above absent absorb abstract absurd abuse access accident account accuse achieve",
                        0, Account.MAINNET),
                new AssetPair(Asset.WAVES, "8LQW8f7P5d5PZM7GtZEBgaqRPGSzS3DfPuiXrURJ4AJS"),
                nodeUrl + ":6886",
                nodeUrl, 'W');
    }

    private SpreadBot(PrivateKeyAccount account, AssetPair market, String matcherUrl, String nodeUrl, char chainId) throws IOException, URISyntaxException {
        this.account = account;
        this.market = market;
        this.node = new Node(nodeUrl, chainId);
        this.matcher = new Node(matcherUrl, chainId);
        this.matcherKey = matcher.getMatcherKey();
    }

    private void cancelOrder(String orderId) throws IOException, InterruptedException {
        String status = matcher.cancelOrder(account, market, orderId);
        System.out.printf("%s %s\n", status, orderId);
    }

    private String fileOrder(Order.Type type, long price, long amount) throws IOException, InterruptedException {
        long expiration = System.currentTimeMillis() + Math.max(period, 61000); // matcher requires expiration > 1 min in the future
        Order order = matcher.createOrder(account, matcherKey, market, type, price, amount, expiration, fee);
        System.out.printf("Filed order %s to %s %d at %d\n", order.getId(), type, amount, price);
        return order.getId().getBase58String();
    }

    private void round() throws IOException, InterruptedException {
        System.out.println();

        // Cancel all orders filed at our market
        for (Order order: matcher.getOrders(account, market)) {
            if (order.isActive()) {
                cancelOrder(order.getId().getBase58String());
            }
        }

        // Read order book
        OrderBook book = matcher.getOrderBook(market);
        if (book.getBids().size() <= 0) {
            System.out.println("There are no bids, skipping this round");
            return;
        }
        if (book.getAsks().size() <= 0) {
            System.out.println("There are no asks, skipping this round");
            return;
        }

        // Determine buy and sell prices
        long bestBid = book.getBids().get(0).getPrice();
        long bestAsk = book.getAsks().get(0).getPrice();
        System.out.printf("Spread %d : %d\n", bestBid, bestAsk);
        long meanPrice = (bestBid + bestAsk) / 2;
        long buyPrice = (long) (meanPrice * (1 - halfSpread));
        long sellPrice = (long) (meanPrice * (1 + halfSpread));

        // Find out how much we want to buy, and file an order
        long priceAssetAvail = node.getBalance(account.getAddress(), market.getPriceAsset()) - 2 * fee;
        if (priceAssetAvail > 0) {
            long buyOrderSize = Math.min(priceAssetAvail, maxOrderSize) * Asset.TOKEN / buyPrice;
            fileOrder(Order.Type.BUY, buyPrice, buyOrderSize);
        }

        // Same for sell order
        long amountAssetAvail = node.getBalance(account.getAddress(), market.getAmountAsset()) - 2 * fee;
        if (amountAssetAvail > 0) {
            long sellOrderSize = Math.min(amountAssetAvail, maxOrderSize * Asset.TOKEN / sellPrice);
            fileOrder(Order.Type.SELL, sellPrice, sellOrderSize);
        }
    }

    private class RoundTask extends TimerTask {
        @Override public void run() {
            try {
                round();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public void run() {
        Timer t = new Timer();
        t.schedule(new RoundTask(), 0, period);
    }

    public static void main(String[] args) throws IOException, URISyntaxException {
        SpreadBot.testnetInstance().run();
    }
}