package wallettemplate;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import network.thunder.core.ThunderContext;
import network.thunder.core.communication.ServerObject;
import network.thunder.core.database.DBHandler;
import network.thunder.core.database.InMemoryDBHandler;
import network.thunder.core.etc.Constants;
import network.thunder.core.helper.callback.results.NullResultCommand;
import network.thunder.core.helper.wallet.MockWallet;
import org.bitcoinj.core.*;
import org.bitcoinj.kits.WalletAppKit;
import org.bitcoinj.script.Script;
import wallettemplate.controls.NotificationBarPane;
import wallettemplate.utils.GuiUtils;
import wallettemplate.utils.TextFieldValidator;

import javax.annotation.Nullable;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.List;
import java.util.concurrent.TimeUnit;

import static wallettemplate.utils.GuiUtils.*;

/**
 * Entry point for starting the Wallet. We create the context here and load the UI.
 * <p>
 * The rest of the functionality is hidden inside the ThunderContext and wired to the buttons.
 */
public class Main extends Application {
    public static final String APP_NAME = "ThunderWallet";

    public static Main instance;
    public static Wallet wallet;

    public static ThunderContext thunderContext;
    public static DBHandler dbHandler = new InMemoryDBHandler();
    public static ServerObject node = new ServerObject();

    private StackPane uiStack;
    private Pane mainUI;
    public MainController controller;
    public NotificationBarPane notificationBar;
    public Stage mainWindow;

    public static int CLIENTID = 1;
    public static String REQUEST;

    public static WalletAppKit walletAppKit;

    @Override
    public void start (Stage mainWindow) throws Exception {
        try {
            realStart(mainWindow);
        } catch (Throwable e) {
            GuiUtils.crashAlert(e);
            throw e;
        }
    }

    private void realStart (Stage mainWindow) throws IOException {
        instance = this;
        prepareUI(mainWindow);

        //Initiate the ThunderContext with a ServerObject
        //For now create a new node key every time. Want to read it from disk here once we have a storage engine
        node.init();

        //TODO move somewhere more central..
        if (Constants.USE_MOCK_BLOCKCHAIN) {
            wallet = new MockWallet(Constants.getNetwork());
        } else {
            System.out.println("Setting up wallet and downloading blockheaders. This can take up to two minutes on first startup");
            walletAppKit = new WalletAppKit(Constants.getNetwork(), new File("wallet"), "wallet_" + CLIENTID);
            walletAppKit.startAsync().awaitRunning();
            wallet = walletAppKit.wallet();
            wallet.allowSpendingUnconfirmedTransactions();
            wallet.reset();
            wallet.addEventListener(new WalletEventListener() {
                @Override
                public void onCoinsReceived (Wallet wallet, Transaction tx, Coin prevBalance, Coin newBalance) {
                    System.out.println("wallet = " + wallet);
                    System.out.println("tx = " + tx);
                    System.out.println("prevBalance = " + prevBalance);
                    System.out.println("newBalance = " + newBalance);
                }

                @Override
                public void onCoinsSent (Wallet wallet, Transaction tx, Coin prevBalance, Coin newBalance) {
                    System.out.println("wallet = " + wallet);
                    System.out.println("tx = " + tx);
                    System.out.println("prevBalance = " + prevBalance);
                    System.out.println("newBalance = " + newBalance);
                }

                @Override
                public void onReorganize (Wallet wallet) {

                }

                @Override
                public void onTransactionConfidenceChanged (Wallet wallet, Transaction tx) {

                }

                @Override
                public void onWalletChanged (Wallet wallet) {
                }

                @Override
                public void onScriptsChanged (Wallet wallet, List<Script> scripts, boolean isAddingScripts) {

                }

                @Override
                public void onKeysAdded (List<ECKey> keys) {

                }
            });
            System.out.println(wallet.getKeyChainSeed());
        }
        System.out.println(wallet);
        thunderContext = new ThunderContext(wallet, dbHandler, node);

        mainWindow.show();
        controller.onBitcoinSetup();

        thunderContext.startUp(new NullResultCommand());
    }

    private void prepareUI (Stage mainWindow) throws IOException {
        this.mainWindow = mainWindow;
        // Show the crash dialog for any exceptions that we don't handle and that hit the main loop.
        GuiUtils.handleCrashesOnThisThread();

        // Load the GUI. The MainController class will be automagically created and wired up.
        File file = new File("main.fxml");
        URL location = getClass().getResource("main.fxml");
        FXMLLoader loader = new FXMLLoader(location);
        mainUI = loader.load();
        controller = loader.getController();
        // Configure the window with a StackPane so we can overlay things on top of the main UI, and a
        // NotificationBarPane so we can slide messages and progress bars in from the bottom. Note that
        // ordering of the construction and connection matters here, otherwise we get (harmless) CSS error
        // spew to the logs.
        notificationBar = new NotificationBarPane(mainUI);
        mainWindow.setTitle(APP_NAME);
        uiStack = new StackPane();
        Scene scene = new Scene(uiStack);
        TextFieldValidator.configureScene(scene);   // Add CSS that we need.
        scene.getStylesheets().add(getClass().getResource("wallet.css").toString());
        uiStack.getChildren().add(notificationBar);
        mainWindow.setScene(scene);
    }

    private Node stopClickPane = new Pane();

    public class OverlayUI <T> {
        public Node ui;
        public T controller;

        public OverlayUI (Node ui, T controller) {
            this.ui = ui;
            this.controller = controller;
        }

        public void show () {
            checkGuiThread();
            if (currentOverlay == null) {
                uiStack.getChildren().add(stopClickPane);
                uiStack.getChildren().add(ui);
                blurOut(mainUI);
                //darken(mainUI);
                fadeIn(ui);
                zoomIn(ui);
            } else {
                // Do a quick transition between the current overlay and the next.
                // Bug here: we don't pay attention to changes in outsideClickDismisses.
                explodeOut(currentOverlay.ui);
                fadeOutAndRemove(uiStack, currentOverlay.ui);
                uiStack.getChildren().add(ui);
                ui.setOpacity(0.0);
                fadeIn(ui, 100);
                zoomIn(ui, 100);
            }
            currentOverlay = this;
        }

        public void outsideClickDismisses () {
            stopClickPane.setOnMouseClicked((ev) -> done());
        }

        public void done () {
            checkGuiThread();
            if (ui == null) {
                return;  // In the middle of being dismissed and got an extra click.
            }
            explodeOut(ui);
            fadeOutAndRemove(uiStack, ui, stopClickPane);
            blurIn(mainUI);
            this.ui = null;
            this.controller = null;
            currentOverlay = null;
        }
    }

    @Nullable
    private OverlayUI currentOverlay;

    public <T> OverlayUI<T> overlayUI (Node node, T controller) {
        checkGuiThread();
        OverlayUI<T> pair = new OverlayUI<T>(node, controller);
        // Auto-magically set the overlayUI member, if it's there.
        try {
            controller.getClass().getField("overlayUI").set(controller, pair);
        } catch (IllegalAccessException | NoSuchFieldException ignored) {
        }
        pair.show();
        return pair;
    }

    /**
     * Loads the FXML file with the given name, blurs out the main UI and puts this one on top.
     */
    public <T> OverlayUI<T> overlayUI (String name) {
        try {
            checkGuiThread();
            // Load the UI from disk.
            URL location = GuiUtils.getResource(name);
            FXMLLoader loader = new FXMLLoader(location);
            Pane ui = loader.load();
            T controller = loader.getController();
            OverlayUI<T> pair = new OverlayUI<T>(ui, controller);
            // Auto-magically set the overlayUI member, if it's there.
            try {
                if (controller != null) {
                    controller.getClass().getField("overlayUI").set(controller, pair);
                }
            } catch (IllegalAccessException | NoSuchFieldException ignored) {
                ignored.printStackTrace();
            }
            pair.show();
            return pair;
        } catch (IOException e) {
            throw new RuntimeException(e);  // Can't happen.
        }
    }

    /**
     * Loads the FXML file with the given name, blurs out the main UI and puts this one on top.
     */
    public <T> OverlayUI<T> overlayUI (Pane ui, T controller) {
        checkGuiThread();
        OverlayUI<T> pair = new OverlayUI<T>(ui, controller);
        // Auto-magically set the overlayUI member, if it's there.
        try {
            if (controller != null) {
                controller.getClass().getField("overlayUI").set(controller, pair);
            }
        } catch (IllegalAccessException | NoSuchFieldException ignored) {
            ignored.printStackTrace();
        }
        pair.show();
        return pair;
    }

    @Override
    public void stop () throws Exception {

        try {
            if (walletAppKit != null) {
                walletAppKit.stopAsync().awaitTerminated(10, TimeUnit.SECONDS);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        // Forcibly terminate the JVM because Orchid likes to spew non-daemon threads everywhere.
        Runtime.getRuntime().exit(0);
    }

    public static void main (String[] args) {

        try {
            int id = Integer.parseInt(args[0]);
            CLIENTID = id;
        } catch (Exception e) {
            try {
                REQUEST = args[0];
            } catch (Exception f) {
            }

        }

        launch(args);
    }
}