<commit_before>/*
 * This file is part of Bisq.
 *
 * Bisq is free software: you can redistribute it and/or modify it
 * under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or (at
 * your option) any later version.
 *
 * Bisq is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
 * License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with Bisq. If not, see <http://www.gnu.org/licenses/>.
 */

package bisq.desktop.main;

import bisq.desktop.Navigation;
import bisq.desktop.common.view.CachingViewLoader;
import bisq.desktop.common.view.FxmlView;
import bisq.desktop.common.view.InitializableView;
import bisq.desktop.common.view.View;
import bisq.desktop.common.view.ViewLoader;
import bisq.desktop.components.AutoTooltipButton;
import bisq.desktop.components.AutoTooltipLabel;
import bisq.desktop.components.AutoTooltipToggleButton;
import bisq.desktop.components.BusyAnimation;
import bisq.desktop.main.account.AccountView;
import bisq.desktop.main.dao.DaoView;
import bisq.desktop.main.funds.FundsView;
import bisq.desktop.main.market.MarketView;
import bisq.desktop.main.market.offerbook.OfferBookChartView;
import bisq.desktop.main.offer.BuyOfferView;
import bisq.desktop.main.offer.SellOfferView;
import bisq.desktop.main.overlays.popups.Popup;
import bisq.desktop.main.portfolio.PortfolioView;
import bisq.desktop.main.settings.SettingsView;
import bisq.desktop.main.shared.PriceFeedComboBoxItem;
import bisq.desktop.main.support.SupportView;
import bisq.desktop.util.DisplayUtils;
import bisq.desktop.util.Transitions;

import bisq.core.dao.monitoring.DaoStateMonitoringService;
import bisq.core.locale.GlobalSettings;
import bisq.core.locale.LanguageUtil;
import bisq.core.locale.Res;
import bisq.core.provider.price.MarketPrice;

import bisq.common.BisqException;
import bisq.common.Timer;
import bisq.common.UserThread;
import bisq.common.app.Version;
import bisq.common.util.Tuple2;
import bisq.common.util.Utilities;

import javax.inject.Inject;

import com.jfoenix.controls.JFXBadge;
import com.jfoenix.controls.JFXComboBox;
import com.jfoenix.controls.JFXProgressBar;

import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.Separator;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.ToggleGroup;
import javafx.scene.control.Tooltip;
import javafx.scene.image.ImageView;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.text.TextAlignment;

import javafx.geometry.Insets;
import javafx.geometry.NodeOrientation;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;

import javafx.beans.binding.ObjectBinding;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;

import java.text.DecimalFormat;
import java.text.NumberFormat;

import java.util.Date;
import java.util.Locale;

import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

import org.jetbrains.annotations.NotNull;

import static javafx.scene.layout.AnchorPane.setBottomAnchor;
import static javafx.scene.layout.AnchorPane.setLeftAnchor;
import static javafx.scene.layout.AnchorPane.setRightAnchor;
import static javafx.scene.layout.AnchorPane.setTopAnchor;

@FxmlView
@Slf4j
public class MainView extends InitializableView<StackPane, MainViewModel>
        implements DaoStateMonitoringService.Listener {
    // If after 30 sec we have not got connected we show "open network settings" button
    private final static int SHOW_TOR_SETTINGS_DELAY_SEC = 90;
    private Label versionLabel;
    @Setter
    private Runnable onUiReadyHandler;

    public static StackPane getRootContainer() {
        return MainView.rootContainer;
    }

    public static void blurLight() {
        transitions.blur(MainView.rootContainer, Transitions.DEFAULT_DURATION, -0.6, false, 5);
    }

    public static void blurUltraLight() {
        transitions.blur(MainView.rootContainer, Transitions.DEFAULT_DURATION, -0.6, false, 2);
    }

    public static void darken() {
        transitions.darken(MainView.rootContainer, Transitions.DEFAULT_DURATION, false);
    }

    public static void removeEffect() {
        transitions.removeEffect(MainView.rootContainer);
    }

    private static Transitions transitions;
    private static StackPane rootContainer;


    private final ViewLoader viewLoader;
    private final Navigation navigation;

    private final ToggleGroup navButtons = new ToggleGroup();
    private ChangeListener<String> walletServiceErrorMsgListener;
    private ChangeListener<String> btcSyncIconIdListener;
    private ChangeListener<String> splashP2PNetworkErrorMsgListener;
    private ChangeListener<String> splashP2PNetworkIconIdListener;
    private ChangeListener<Boolean> splashP2PNetworkVisibleListener;
    private BusyAnimation splashP2PNetworkBusyAnimation;
    private Label splashP2PNetworkLabel;
    private ProgressBar btcSyncIndicator, p2pNetworkProgressBar;
    private Label btcSplashInfo;
    private Popup p2PNetworkWarnMsgPopup, btcNetworkWarnMsgPopup;
    private final DaoStateMonitoringService daoStateMonitoringService;

    @Inject
    public MainView(MainViewModel model,
                    CachingViewLoader viewLoader,
                    Navigation navigation,
                    Transitions transitions,
                    DaoStateMonitoringService daoStateMonitoringService) {
        super(model);
        this.viewLoader = viewLoader;
        this.navigation = navigation;
        MainView.transitions = transitions;
        this.daoStateMonitoringService = daoStateMonitoringService;
    }

    @Override
    protected void initialize() {
        MainView.rootContainer = root;
        if (LanguageUtil.isDefaultLanguageRTL())
            MainView.rootContainer.setNodeOrientation(NodeOrientation.RIGHT_TO_LEFT);

        ToggleButton marketButton = new NavButton(MarketView.class, Res.get("mainView.menu.market").toUpperCase());
        ToggleButton buyButton = new NavButton(BuyOfferView.class, Res.get("mainView.menu.buyBtc").toUpperCase());
        ToggleButton sellButton = new NavButton(SellOfferView.class, Res.get("mainView.menu.sellBtc").toUpperCase());
        ToggleButton portfolioButton = new NavButton(PortfolioView.class, Res.get("mainView.menu.portfolio").toUpperCase());
        ToggleButton fundsButton = new NavButton(FundsView.class, Res.get("mainView.menu.funds").toUpperCase());

        ToggleButton supportButton = new NavButton(SupportView.class, Res.get("mainView.menu.support"));
        ToggleButton settingsButton = new NavButton(SettingsView.class, Res.get("mainView.menu.settings"));
        ToggleButton accountButton = new NavButton(AccountView.class, Res.get("mainView.menu.account"));
        ToggleButton daoButton = new NavButton(DaoView.class, Res.get("mainView.menu.dao"));

        JFXBadge portfolioButtonWithBadge = new JFXBadge(portfolioButton);
        JFXBadge supportButtonWithBadge = new JFXBadge(supportButton);
        JFXBadge daoButtonWithBadge = new JFXBadge(daoButton);
        daoButtonWithBadge.getStyleClass().add("new");
        JFXBadge accountButtonWithBadge = new JFXBadge(accountButton);
        accountButtonWithBadge.getStyleClass().add("new");

        Locale locale = GlobalSettings.getLocale();
        DecimalFormat currencyFormat = (DecimalFormat) NumberFormat.getNumberInstance(locale);
        currencyFormat.setMinimumFractionDigits(2);
        currencyFormat.setMaximumFractionDigits(2);

        root.sceneProperty().addListener((observable, oldValue, newValue) -> {
            if (newValue != null) {
                newValue.addEventHandler(KeyEvent.KEY_RELEASED, keyEvent -> {
                    if (Utilities.isAltOrCtrlPressed(KeyCode.DIGIT1, keyEvent)) {
                        marketButton.fire();
                    } else if (Utilities.isAltOrCtrlPressed(KeyCode.DIGIT2, keyEvent)) {
                        buyButton.fire();
                    } else if (Utilities.isAltOrCtrlPressed(KeyCode.DIGIT3, keyEvent)) {
                        sellButton.fire();
                    } else if (Utilities.isAltOrCtrlPressed(KeyCode.DIGIT4, keyEvent)) {
                        portfolioButton.fire();
                    } else if (Utilities.isAltOrCtrlPressed(KeyCode.DIGIT5, keyEvent)) {
                        fundsButton.fire();
                    } else if (Utilities.isAltOrCtrlPressed(KeyCode.DIGIT6, keyEvent)) {
                        supportButton.fire();
                    } else if (Utilities.isAltOrCtrlPressed(KeyCode.DIGIT7, keyEvent)) {
                        settingsButton.fire();
                    } else if (Utilities.isAltOrCtrlPressed(KeyCode.DIGIT8, keyEvent)) {
                        accountButton.fire();
                    } else if (Utilities.isAltOrCtrlPressed(KeyCode.DIGIT9, keyEvent)) {
                        if (daoButton.isVisible())
                            daoButton.fire();
                    }
                });
            }
        });


        Tuple2<ComboBox<PriceFeedComboBoxItem>, VBox> marketPriceBox = getMarketPriceBox();
        ComboBox<PriceFeedComboBoxItem> priceComboBox = marketPriceBox.first;

        priceComboBox.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) ->
                model.setPriceFeedComboBoxItem(newValue));
        ChangeListener<PriceFeedComboBoxItem> selectedPriceFeedItemListener = (observable, oldValue, newValue) -> {
            if (newValue != null)
                priceComboBox.getSelectionModel().select(newValue);

        };
        model.getSelectedPriceFeedComboBoxItemProperty().addListener(selectedPriceFeedItemListener);
        priceComboBox.setItems(model.getPriceFeedComboBoxItems());

        Tuple2<Label, VBox> availableBalanceBox = getBalanceBox(Res.get("mainView.balance.available"));
        availableBalanceBox.first.textProperty().bind(model.getAvailableBalance());
        availableBalanceBox.first.setPrefWidth(100);
        availableBalanceBox.first.tooltipProperty().bind(new ObjectBinding<>() {
            {
                bind(model.getAvailableBalance());
                bind(model.getMarketPrice());
            }

            @Override
            protected Tooltip computeValue() {
                String tooltipText = Res.get("mainView.balance.available");
                try {
                    String preferredTradeCurrency = model.getPreferences().getPreferredTradeCurrency().getCode();
                    double availableBalance = Double.parseDouble(
                            model.getAvailableBalance().getValue().replace("BTC", ""));
                    double marketPrice = Double.parseDouble(model.getMarketPrice(preferredTradeCurrency).getValue());
                    tooltipText += "\n" + currencyFormat.format(availableBalance * marketPrice) +
                            " " + preferredTradeCurrency;
                } catch (NullPointerException | NumberFormatException e) {
                    // Either the balance or market price is not available yet
                }
                return new Tooltip(tooltipText);
            }
        });

        Tuple2<Label, VBox> reservedBalanceBox = getBalanceBox(Res.get("mainView.balance.reserved.short"));
        reservedBalanceBox.first.textProperty().bind(model.getReservedBalance());
        reservedBalanceBox.first.tooltipProperty().bind(new ObjectBinding<>() {
            {
                bind(model.getReservedBalance());
                bind(model.getMarketPrice());
            }

            @Override
            protected Tooltip computeValue() {
                String tooltipText = Res.get("mainView.balance.reserved");
                try {
                    String preferredTradeCurrency = model.getPreferences().getPreferredTradeCurrency().getCode();
                    double reservedBalance = Double.parseDouble(
                            model.getReservedBalance().getValue().replace("BTC", ""));
                    double marketPrice = Double.parseDouble(model.getMarketPrice(preferredTradeCurrency).getValue());
                    tooltipText += "\n" + currencyFormat.format(reservedBalance * marketPrice) +
                            " " + preferredTradeCurrency;
                } catch (NullPointerException | NumberFormatException e) {
                    // Either the balance or market price is not available yet
                }
                return new Tooltip(tooltipText);
            }
        });

        Tuple2<Label, VBox> lockedBalanceBox = getBalanceBox(Res.get("mainView.balance.locked.short"));
        lockedBalanceBox.first.textProperty().bind(model.getLockedBalance());
        lockedBalanceBox.first.tooltipProperty().bind(new ObjectBinding<>() {
            {
                bind(model.getLockedBalance());
                bind(model.getMarketPrice());
            }

            @Override
            protected Tooltip computeValue() {
                String tooltipText = Res.get("mainView.balance.locked");
                try {
                    String preferredTradeCurrency = model.getPreferences().getPreferredTradeCurrency().getCode();
                    double lockedBalance = Double.parseDouble(
                            model.getLockedBalance().getValue().replace("BTC", ""));
                    double marketPrice = Double.parseDouble(model.getMarketPrice(preferredTradeCurrency).getValue());
                    tooltipText += "\n" + currencyFormat.format(lockedBalance * marketPrice) +
                            " " + preferredTradeCurrency;
                } catch (NullPointerException | NumberFormatException e) {
                    // Either the balance or market price is not available yet
                }
                return new Tooltip(tooltipText);
            }
        });

        HBox primaryNav = new HBox(marketButton, getNavigationSeparator(), buyButton,
                getNavigationSeparator(), sellButton, getNavigationSeparator(),
                portfolioButtonWithBadge, getNavigationSeparator(), fundsButton);

        primaryNav.setAlignment(Pos.CENTER);
        primaryNav.getStyleClass().add("nav-primary");
        HBox.setHgrow(primaryNav, Priority.SOMETIMES);

        HBox secondaryNav = new HBox(supportButtonWithBadge, getNavigationSeparator(),
                settingsButton, getNavigationSeparator(), accountButtonWithBadge,
                getNavigationSeparator(), daoButtonWithBadge);
        secondaryNav.getStyleClass().add("nav-secondary");
        HBox.setHgrow(secondaryNav, Priority.SOMETIMES);

        secondaryNav.setAlignment(Pos.CENTER);

        HBox priceAndBalance = new HBox(marketPriceBox.second, getNavigationSeparator(), availableBalanceBox.second,
                getNavigationSeparator(), reservedBalanceBox.second, getNavigationSeparator(), lockedBalanceBox.second);
        priceAndBalance.setMaxHeight(41);

        priceAndBalance.setAlignment(Pos.CENTER);
        priceAndBalance.setSpacing(9);
        priceAndBalance.getStyleClass().add("nav-price-balance");

        HBox navPane = new HBox(primaryNav, secondaryNav,
                getNavigationSpacer(), priceAndBalance) {{
            setLeftAnchor(this, 0d);
            setRightAnchor(this, 0d);
            setTopAnchor(this, 0d);
            setPadding(new Insets(0, 0, 0, 0));
            getStyleClass().add("top-navigation");
        }};
        navPane.setAlignment(Pos.CENTER_LEFT);

        AnchorPane contentContainer = new AnchorPane() {{
            getStyleClass().add("content-pane");
            setLeftAnchor(this, 0d);
            setRightAnchor(this, 0d);
            setTopAnchor(this, 57d);
            setBottomAnchor(this, 0d);
        }};

        AnchorPane applicationContainer = new AnchorPane(navPane, contentContainer) {{
            setId("application-container");
        }};

        BorderPane baseApplicationContainer = new BorderPane(applicationContainer) {{
            setId("base-content-container");
        }};
        baseApplicationContainer.setBottom(createFooter());

        setupBadge(portfolioButtonWithBadge, model.getNumPendingTrades(), model.getShowPendingTradesNotification());
        setupBadge(supportButtonWithBadge, model.getNumOpenSupportTickets(), model.getShowOpenSupportTicketsNotification());
        setupBadge(daoButtonWithBadge, new SimpleStringProperty(Res.get("shared.new")), model.getShowDaoUpdatesNotification());
        setupBadge(accountButtonWithBadge, new SimpleStringProperty(Res.get("shared.new")), model.getShowAccountUpdatesNotification());

        navigation.addListener(viewPath -> {
            if (viewPath.size() != 2 || viewPath.indexOf(MainView.class) != 0)
                return;

            Class<? extends View> viewClass = viewPath.tip();
            View view = viewLoader.load(viewClass);
            contentContainer.getChildren().setAll(view.getRoot());

            try {
            	navButtons.getToggles().stream()
                    .filter(toggle -> toggle instanceof NavButton)
                    .filter(button -> viewClass == ((NavButton) button).viewClass)
                    .findFirst()
                    .orElseThrow(() -> new BisqException("No button matching %s found", viewClass))
                    .setSelected(true);
            } catch (BisqException e) {
                navigation.navigateTo(MainView.class, MarketView.class, OfferBookChartView.class);
			}
        });

        VBox splashScreen = createSplashScreen();

        root.getChildren().addAll(baseApplicationContainer, splashScreen);

        model.getShowAppScreen().addListener((ov, oldValue, newValue) -> {
            if (newValue) {

                navigation.navigateToPreviousVisitedView();

                transitions.fadeOutAndRemove(splashScreen, 1500, actionEvent -> disposeSplashScreen());
            }
        });

        daoStateMonitoringService.addListener(this);

        // Delay a bit to give time for rendering the splash screen
        UserThread.execute(() -> onUiReadyHandler.run());
    }

    ///////////////////////////////////////////////////////////////////////////////////////////
    // DaoStateMonitoringService.Listener
    ///////////////////////////////////////////////////////////////////////////////////////////

    @Override
    public void onChangeAfterBatchProcessing() {
    }

    @Override
    public void onCheckpointFail() {
        new Popup().attention(Res.get("dao.monitor.daoState.checkpoint.popup"))
                .useShutDownButton()
                .show();
    }


    ///////////////////////////////////////////////////////////////////////////////////////////
    // Helpers
    ///////////////////////////////////////////////////////////////////////////////////////////

    @NotNull
    private Separator getNavigationSeparator() {
        final Separator separator = new Separator(Orientation.VERTICAL);
        HBox.setHgrow(separator, Priority.ALWAYS);
        separator.setMaxHeight(22);
        separator.setMaxWidth(Double.MAX_VALUE);
        return separator;
    }

    @NotNull
    private Region getNavigationSpacer() {
        final Region spacer = new Region();
        HBox.setHgrow(spacer, Priority.ALWAYS);
        return spacer;
    }

    private Tuple2<Label, VBox> getBalanceBox(String text) {
        Label balanceDisplay = new Label();
        balanceDisplay.getStyleClass().add("nav-balance-display");

        Label label = new Label(text);
        label.getStyleClass().add("nav-balance-label");
        label.maxWidthProperty().bind(balanceDisplay.widthProperty());
        label.setPadding(new Insets(0, 0, 0, 0));
        VBox vBox = new VBox();
        vBox.setAlignment(Pos.CENTER_LEFT);
        vBox.getChildren().addAll(balanceDisplay, label);
        return new Tuple2<>(balanceDisplay, vBox);
    }

    private ListCell<PriceFeedComboBoxItem> getPriceFeedComboBoxListCell() {
        return new ListCell<>() {
            @Override
            protected void updateItem(PriceFeedComboBoxItem item, boolean empty) {
                super.updateItem(item, empty);

                if (!empty && item != null) {
                    textProperty().bind(item.displayStringProperty);
                } else {
                    textProperty().unbind();
                }
            }
        };
    }

    private Tuple2<ComboBox<PriceFeedComboBoxItem>, VBox> getMarketPriceBox() {

        VBox marketPriceBox = new VBox();
        marketPriceBox.setAlignment(Pos.CENTER_LEFT);

        ComboBox<PriceFeedComboBoxItem> priceComboBox = new JFXComboBox<>();
        priceComboBox.setVisibleRowCount(12);
        priceComboBox.setFocusTraversable(false);
        priceComboBox.setId("price-feed-combo");
        priceComboBox.setPadding(new Insets(0, -4, -4, 0));
        priceComboBox.setCellFactory(p -> getPriceFeedComboBoxListCell());
        ListCell<PriceFeedComboBoxItem> buttonCell = getPriceFeedComboBoxListCell();
        buttonCell.setId("price-feed-combo");
        priceComboBox.setButtonCell(buttonCell);

        Label marketPriceLabel = new Label();

        updateMarketPriceLabel(marketPriceLabel);

        marketPriceLabel.getStyleClass().add("nav-balance-label");
        marketPriceLabel.setPadding(new Insets(-2, 0, 4, 9));

        marketPriceBox.getChildren().addAll(priceComboBox, marketPriceLabel);

        model.getMarketPriceUpdated().addListener((observable, oldValue, newValue) ->
                updateMarketPriceLabel(marketPriceLabel));

        return new Tuple2<>(priceComboBox, marketPriceBox);
    }

    @NotNull
    private String getPriceProvider() {
        return model.getIsFiatCurrencyPriceFeedSelected().get() ? "BitcoinAverage" : "Poloniex";
    }

    private void updateMarketPriceLabel(Label label) {
        if (model.getIsPriceAvailable().get()) {
            if (model.getIsExternallyProvidedPrice().get()) {
                label.setText(Res.get("mainView.marketPriceWithProvider.label", "Bisq Price Index"));
                label.setTooltip(new Tooltip(getPriceProviderTooltipString()));
            } else {
                label.setText(Res.get("mainView.marketPrice.bisqInternalPrice"));
                final Tooltip tooltip = new Tooltip(Res.get("mainView.marketPrice.tooltip.bisqInternalPrice"));
                label.setTooltip(tooltip);
            }
        } else {
            label.setText("");
            label.setTooltip(null);
        }
    }

    @NotNull
    private String getPriceProviderTooltipString() {

        String selectedCurrencyCode = model.getPriceFeedService().getCurrencyCode();
        MarketPrice selectedMarketPrice = model.getPriceFeedService().getMarketPrice(selectedCurrencyCode);

        return Res.get("mainView.marketPrice.tooltip",
                "Bisq Price Index for " + selectedCurrencyCode,
                "",
                DisplayUtils.formatTime(new Date(selectedMarketPrice.getTimestampSec())),
                model.getPriceFeedService().getProviderNodeAddress());
    }

    private VBox createSplashScreen() {
        VBox vBox = new VBox();
        vBox.setAlignment(Pos.CENTER);
        vBox.setSpacing(10);
        vBox.setId("splash");<commit_msg>Fix null pointer in case if selectedMarketPrice is null<commit_after>/*
 * This file is part of Bisq.
 *
 * Bisq is free software: you can redistribute it and/or modify it
 * under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or (at
 * your option) any later version.
 *
 * Bisq is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
 * License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with Bisq. If not, see <http://www.gnu.org/licenses/>.
 */

package bisq.desktop.main;

import bisq.desktop.Navigation;
import bisq.desktop.common.view.CachingViewLoader;
import bisq.desktop.common.view.FxmlView;
import bisq.desktop.common.view.InitializableView;
import bisq.desktop.common.view.View;
import bisq.desktop.common.view.ViewLoader;
import bisq.desktop.components.AutoTooltipButton;
import bisq.desktop.components.AutoTooltipLabel;
import bisq.desktop.components.AutoTooltipToggleButton;
import bisq.desktop.components.BusyAnimation;
import bisq.desktop.main.account.AccountView;
import bisq.desktop.main.dao.DaoView;
import bisq.desktop.main.funds.FundsView;
import bisq.desktop.main.market.MarketView;
import bisq.desktop.main.market.offerbook.OfferBookChartView;
import bisq.desktop.main.offer.BuyOfferView;
import bisq.desktop.main.offer.SellOfferView;
import bisq.desktop.main.overlays.popups.Popup;
import bisq.desktop.main.portfolio.PortfolioView;
import bisq.desktop.main.settings.SettingsView;
import bisq.desktop.main.shared.PriceFeedComboBoxItem;
import bisq.desktop.main.support.SupportView;
import bisq.desktop.util.DisplayUtils;
import bisq.desktop.util.Transitions;

import bisq.core.dao.monitoring.DaoStateMonitoringService;
import bisq.core.locale.GlobalSettings;
import bisq.core.locale.LanguageUtil;
import bisq.core.locale.Res;
import bisq.core.provider.price.MarketPrice;

import bisq.common.BisqException;
import bisq.common.Timer;
import bisq.common.UserThread;
import bisq.common.app.Version;
import bisq.common.util.Tuple2;
import bisq.common.util.Utilities;

import javax.inject.Inject;

import com.jfoenix.controls.JFXBadge;
import com.jfoenix.controls.JFXComboBox;
import com.jfoenix.controls.JFXProgressBar;

import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.Separator;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.ToggleGroup;
import javafx.scene.control.Tooltip;
import javafx.scene.image.ImageView;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.text.TextAlignment;

import javafx.geometry.Insets;
import javafx.geometry.NodeOrientation;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;

import javafx.beans.binding.ObjectBinding;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;

import java.text.DecimalFormat;
import java.text.NumberFormat;

import java.util.Date;
import java.util.Locale;

import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

import org.jetbrains.annotations.NotNull;

import static javafx.scene.layout.AnchorPane.setBottomAnchor;
import static javafx.scene.layout.AnchorPane.setLeftAnchor;
import static javafx.scene.layout.AnchorPane.setRightAnchor;
import static javafx.scene.layout.AnchorPane.setTopAnchor;

@FxmlView
@Slf4j
public class MainView extends InitializableView<StackPane, MainViewModel>
        implements DaoStateMonitoringService.Listener {
    // If after 30 sec we have not got connected we show "open network settings" button
    private final static int SHOW_TOR_SETTINGS_DELAY_SEC = 90;
    private Label versionLabel;
    @Setter
    private Runnable onUiReadyHandler;

    public static StackPane getRootContainer() {
        return MainView.rootContainer;
    }

    public static void blurLight() {
        transitions.blur(MainView.rootContainer, Transitions.DEFAULT_DURATION, -0.6, false, 5);
    }

    public static void blurUltraLight() {
        transitions.blur(MainView.rootContainer, Transitions.DEFAULT_DURATION, -0.6, false, 2);
    }

    public static void darken() {
        transitions.darken(MainView.rootContainer, Transitions.DEFAULT_DURATION, false);
    }

    public static void removeEffect() {
        transitions.removeEffect(MainView.rootContainer);
    }

    private static Transitions transitions;
    private static StackPane rootContainer;


    private final ViewLoader viewLoader;
    private final Navigation navigation;

    private final ToggleGroup navButtons = new ToggleGroup();
    private ChangeListener<String> walletServiceErrorMsgListener;
    private ChangeListener<String> btcSyncIconIdListener;
    private ChangeListener<String> splashP2PNetworkErrorMsgListener;
    private ChangeListener<String> splashP2PNetworkIconIdListener;
    private ChangeListener<Boolean> splashP2PNetworkVisibleListener;
    private BusyAnimation splashP2PNetworkBusyAnimation;
    private Label splashP2PNetworkLabel;
    private ProgressBar btcSyncIndicator, p2pNetworkProgressBar;
    private Label btcSplashInfo;
    private Popup p2PNetworkWarnMsgPopup, btcNetworkWarnMsgPopup;
    private final DaoStateMonitoringService daoStateMonitoringService;

    @Inject
    public MainView(MainViewModel model,
                    CachingViewLoader viewLoader,
                    Navigation navigation,
                    Transitions transitions,
                    DaoStateMonitoringService daoStateMonitoringService) {
        super(model);
        this.viewLoader = viewLoader;
        this.navigation = navigation;
        MainView.transitions = transitions;
        this.daoStateMonitoringService = daoStateMonitoringService;
    }

    @Override
    protected void initialize() {
        MainView.rootContainer = root;
        if (LanguageUtil.isDefaultLanguageRTL())
            MainView.rootContainer.setNodeOrientation(NodeOrientation.RIGHT_TO_LEFT);

        ToggleButton marketButton = new NavButton(MarketView.class, Res.get("mainView.menu.market").toUpperCase());
        ToggleButton buyButton = new NavButton(BuyOfferView.class, Res.get("mainView.menu.buyBtc").toUpperCase());
        ToggleButton sellButton = new NavButton(SellOfferView.class, Res.get("mainView.menu.sellBtc").toUpperCase());
        ToggleButton portfolioButton = new NavButton(PortfolioView.class, Res.get("mainView.menu.portfolio").toUpperCase());
        ToggleButton fundsButton = new NavButton(FundsView.class, Res.get("mainView.menu.funds").toUpperCase());

        ToggleButton supportButton = new NavButton(SupportView.class, Res.get("mainView.menu.support"));
        ToggleButton settingsButton = new NavButton(SettingsView.class, Res.get("mainView.menu.settings"));
        ToggleButton accountButton = new NavButton(AccountView.class, Res.get("mainView.menu.account"));
        ToggleButton daoButton = new NavButton(DaoView.class, Res.get("mainView.menu.dao"));

        JFXBadge portfolioButtonWithBadge = new JFXBadge(portfolioButton);
        JFXBadge supportButtonWithBadge = new JFXBadge(supportButton);
        JFXBadge daoButtonWithBadge = new JFXBadge(daoButton);
        daoButtonWithBadge.getStyleClass().add("new");
        JFXBadge accountButtonWithBadge = new JFXBadge(accountButton);
        accountButtonWithBadge.getStyleClass().add("new");

        Locale locale = GlobalSettings.getLocale();
        DecimalFormat currencyFormat = (DecimalFormat) NumberFormat.getNumberInstance(locale);
        currencyFormat.setMinimumFractionDigits(2);
        currencyFormat.setMaximumFractionDigits(2);

        root.sceneProperty().addListener((observable, oldValue, newValue) -> {
            if (newValue != null) {
                newValue.addEventHandler(KeyEvent.KEY_RELEASED, keyEvent -> {
                    if (Utilities.isAltOrCtrlPressed(KeyCode.DIGIT1, keyEvent)) {
                        marketButton.fire();
                    } else if (Utilities.isAltOrCtrlPressed(KeyCode.DIGIT2, keyEvent)) {
                        buyButton.fire();
                    } else if (Utilities.isAltOrCtrlPressed(KeyCode.DIGIT3, keyEvent)) {
                        sellButton.fire();
                    } else if (Utilities.isAltOrCtrlPressed(KeyCode.DIGIT4, keyEvent)) {
                        portfolioButton.fire();
                    } else if (Utilities.isAltOrCtrlPressed(KeyCode.DIGIT5, keyEvent)) {
                        fundsButton.fire();
                    } else if (Utilities.isAltOrCtrlPressed(KeyCode.DIGIT6, keyEvent)) {
                        supportButton.fire();
                    } else if (Utilities.isAltOrCtrlPressed(KeyCode.DIGIT7, keyEvent)) {
                        settingsButton.fire();
                    } else if (Utilities.isAltOrCtrlPressed(KeyCode.DIGIT8, keyEvent)) {
                        accountButton.fire();
                    } else if (Utilities.isAltOrCtrlPressed(KeyCode.DIGIT9, keyEvent)) {
                        if (daoButton.isVisible())
                            daoButton.fire();
                    }
                });
            }
        });


        Tuple2<ComboBox<PriceFeedComboBoxItem>, VBox> marketPriceBox = getMarketPriceBox();
        ComboBox<PriceFeedComboBoxItem> priceComboBox = marketPriceBox.first;

        priceComboBox.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) ->
                model.setPriceFeedComboBoxItem(newValue));
        ChangeListener<PriceFeedComboBoxItem> selectedPriceFeedItemListener = (observable, oldValue, newValue) -> {
            if (newValue != null)
                priceComboBox.getSelectionModel().select(newValue);

        };
        model.getSelectedPriceFeedComboBoxItemProperty().addListener(selectedPriceFeedItemListener);
        priceComboBox.setItems(model.getPriceFeedComboBoxItems());

        Tuple2<Label, VBox> availableBalanceBox = getBalanceBox(Res.get("mainView.balance.available"));
        availableBalanceBox.first.textProperty().bind(model.getAvailableBalance());
        availableBalanceBox.first.setPrefWidth(100);
        availableBalanceBox.first.tooltipProperty().bind(new ObjectBinding<>() {
            {
                bind(model.getAvailableBalance());
                bind(model.getMarketPrice());
            }

            @Override
            protected Tooltip computeValue() {
                String tooltipText = Res.get("mainView.balance.available");
                try {
                    String preferredTradeCurrency = model.getPreferences().getPreferredTradeCurrency().getCode();
                    double availableBalance = Double.parseDouble(
                            model.getAvailableBalance().getValue().replace("BTC", ""));
                    double marketPrice = Double.parseDouble(model.getMarketPrice(preferredTradeCurrency).getValue());
                    tooltipText += "\n" + currencyFormat.format(availableBalance * marketPrice) +
                            " " + preferredTradeCurrency;
                } catch (NullPointerException | NumberFormatException e) {
                    // Either the balance or market price is not available yet
                }
                return new Tooltip(tooltipText);
            }
        });

        Tuple2<Label, VBox> reservedBalanceBox = getBalanceBox(Res.get("mainView.balance.reserved.short"));
        reservedBalanceBox.first.textProperty().bind(model.getReservedBalance());
        reservedBalanceBox.first.tooltipProperty().bind(new ObjectBinding<>() {
            {
                bind(model.getReservedBalance());
                bind(model.getMarketPrice());
            }

            @Override
            protected Tooltip computeValue() {
                String tooltipText = Res.get("mainView.balance.reserved");
                try {
                    String preferredTradeCurrency = model.getPreferences().getPreferredTradeCurrency().getCode();
                    double reservedBalance = Double.parseDouble(
                            model.getReservedBalance().getValue().replace("BTC", ""));
                    double marketPrice = Double.parseDouble(model.getMarketPrice(preferredTradeCurrency).getValue());
                    tooltipText += "\n" + currencyFormat.format(reservedBalance * marketPrice) +
                            " " + preferredTradeCurrency;
                } catch (NullPointerException | NumberFormatException e) {
                    // Either the balance or market price is not available yet
                }
                return new Tooltip(tooltipText);
            }
        });

        Tuple2<Label, VBox> lockedBalanceBox = getBalanceBox(Res.get("mainView.balance.locked.short"));
        lockedBalanceBox.first.textProperty().bind(model.getLockedBalance());
        lockedBalanceBox.first.tooltipProperty().bind(new ObjectBinding<>() {
            {
                bind(model.getLockedBalance());
                bind(model.getMarketPrice());
            }

            @Override
            protected Tooltip computeValue() {
                String tooltipText = Res.get("mainView.balance.locked");
                try {
                    String preferredTradeCurrency = model.getPreferences().getPreferredTradeCurrency().getCode();
                    double lockedBalance = Double.parseDouble(
                            model.getLockedBalance().getValue().replace("BTC", ""));
                    double marketPrice = Double.parseDouble(model.getMarketPrice(preferredTradeCurrency).getValue());
                    tooltipText += "\n" + currencyFormat.format(lockedBalance * marketPrice) +
                            " " + preferredTradeCurrency;
                } catch (NullPointerException | NumberFormatException e) {
                    // Either the balance or market price is not available yet
                }
                return new Tooltip(tooltipText);
            }
        });

        HBox primaryNav = new HBox(marketButton, getNavigationSeparator(), buyButton,
                getNavigationSeparator(), sellButton, getNavigationSeparator(),
                portfolioButtonWithBadge, getNavigationSeparator(), fundsButton);

        primaryNav.setAlignment(Pos.CENTER);
        primaryNav.getStyleClass().add("nav-primary");
        HBox.setHgrow(primaryNav, Priority.SOMETIMES);

        HBox secondaryNav = new HBox(supportButtonWithBadge, getNavigationSeparator(),
                settingsButton, getNavigationSeparator(), accountButtonWithBadge,
                getNavigationSeparator(), daoButtonWithBadge);
        secondaryNav.getStyleClass().add("nav-secondary");
        HBox.setHgrow(secondaryNav, Priority.SOMETIMES);

        secondaryNav.setAlignment(Pos.CENTER);

        HBox priceAndBalance = new HBox(marketPriceBox.second, getNavigationSeparator(), availableBalanceBox.second,
                getNavigationSeparator(), reservedBalanceBox.second, getNavigationSeparator(), lockedBalanceBox.second);
        priceAndBalance.setMaxHeight(41);

        priceAndBalance.setAlignment(Pos.CENTER);
        priceAndBalance.setSpacing(9);
        priceAndBalance.getStyleClass().add("nav-price-balance");

        HBox navPane = new HBox(primaryNav, secondaryNav,
                getNavigationSpacer(), priceAndBalance) {{
            setLeftAnchor(this, 0d);
            setRightAnchor(this, 0d);
            setTopAnchor(this, 0d);
            setPadding(new Insets(0, 0, 0, 0));
            getStyleClass().add("top-navigation");
        }};
        navPane.setAlignment(Pos.CENTER_LEFT);

        AnchorPane contentContainer = new AnchorPane() {{
            getStyleClass().add("content-pane");
            setLeftAnchor(this, 0d);
            setRightAnchor(this, 0d);
            setTopAnchor(this, 57d);
            setBottomAnchor(this, 0d);
        }};

        AnchorPane applicationContainer = new AnchorPane(navPane, contentContainer) {{
            setId("application-container");
        }};

        BorderPane baseApplicationContainer = new BorderPane(applicationContainer) {{
            setId("base-content-container");
        }};
        baseApplicationContainer.setBottom(createFooter());

        setupBadge(portfolioButtonWithBadge, model.getNumPendingTrades(), model.getShowPendingTradesNotification());
        setupBadge(supportButtonWithBadge, model.getNumOpenSupportTickets(), model.getShowOpenSupportTicketsNotification());
        setupBadge(daoButtonWithBadge, new SimpleStringProperty(Res.get("shared.new")), model.getShowDaoUpdatesNotification());
        setupBadge(accountButtonWithBadge, new SimpleStringProperty(Res.get("shared.new")), model.getShowAccountUpdatesNotification());

        navigation.addListener(viewPath -> {
            if (viewPath.size() != 2 || viewPath.indexOf(MainView.class) != 0)
                return;

            Class<? extends View> viewClass = viewPath.tip();
            View view = viewLoader.load(viewClass);
            contentContainer.getChildren().setAll(view.getRoot());

            try {
                navButtons.getToggles().stream()
                        .filter(toggle -> toggle instanceof NavButton)
                        .filter(button -> viewClass == ((NavButton) button).viewClass)
                        .findFirst()
                        .orElseThrow(() -> new BisqException("No button matching %s found", viewClass))
                        .setSelected(true);
            } catch (BisqException e) {
                navigation.navigateTo(MainView.class, MarketView.class, OfferBookChartView.class);
            }
        });

        VBox splashScreen = createSplashScreen();

        root.getChildren().addAll(baseApplicationContainer, splashScreen);

        model.getShowAppScreen().addListener((ov, oldValue, newValue) -> {
            if (newValue) {

                navigation.navigateToPreviousVisitedView();

                transitions.fadeOutAndRemove(splashScreen, 1500, actionEvent -> disposeSplashScreen());
            }
        });

        daoStateMonitoringService.addListener(this);

        // Delay a bit to give time for rendering the splash screen
        UserThread.execute(() -> onUiReadyHandler.run());
    }

    ///////////////////////////////////////////////////////////////////////////////////////////
    // DaoStateMonitoringService.Listener
    ///////////////////////////////////////////////////////////////////////////////////////////

    @Override
    public void onChangeAfterBatchProcessing() {
    }

    @Override
    public void onCheckpointFail() {
        new Popup().attention(Res.get("dao.monitor.daoState.checkpoint.popup"))
                .useShutDownButton()
                .show();
    }


    ///////////////////////////////////////////////////////////////////////////////////////////
    // Helpers
    ///////////////////////////////////////////////////////////////////////////////////////////

    @NotNull
    private Separator getNavigationSeparator() {
        final Separator separator = new Separator(Orientation.VERTICAL);
        HBox.setHgrow(separator, Priority.ALWAYS);
        separator.setMaxHeight(22);
        separator.setMaxWidth(Double.MAX_VALUE);
        return separator;
    }

    @NotNull
    private Region getNavigationSpacer() {
        final Region spacer = new Region();
        HBox.setHgrow(spacer, Priority.ALWAYS);
        return spacer;
    }

    private Tuple2<Label, VBox> getBalanceBox(String text) {
        Label balanceDisplay = new Label();
        balanceDisplay.getStyleClass().add("nav-balance-display");

        Label label = new Label(text);
        label.getStyleClass().add("nav-balance-label");
        label.maxWidthProperty().bind(balanceDisplay.widthProperty());
        label.setPadding(new Insets(0, 0, 0, 0));
        VBox vBox = new VBox();
        vBox.setAlignment(Pos.CENTER_LEFT);
        vBox.getChildren().addAll(balanceDisplay, label);
        return new Tuple2<>(balanceDisplay, vBox);
    }

    private ListCell<PriceFeedComboBoxItem> getPriceFeedComboBoxListCell() {
        return new ListCell<>() {
            @Override
            protected void updateItem(PriceFeedComboBoxItem item, boolean empty) {
                super.updateItem(item, empty);

                if (!empty && item != null) {
                    textProperty().bind(item.displayStringProperty);
                } else {
                    textProperty().unbind();
                }
            }
        };
    }

    private Tuple2<ComboBox<PriceFeedComboBoxItem>, VBox> getMarketPriceBox() {

        VBox marketPriceBox = new VBox();
        marketPriceBox.setAlignment(Pos.CENTER_LEFT);

        ComboBox<PriceFeedComboBoxItem> priceComboBox = new JFXComboBox<>();
        priceComboBox.setVisibleRowCount(12);
        priceComboBox.setFocusTraversable(false);
        priceComboBox.setId("price-feed-combo");
        priceComboBox.setPadding(new Insets(0, -4, -4, 0));
        priceComboBox.setCellFactory(p -> getPriceFeedComboBoxListCell());
        ListCell<PriceFeedComboBoxItem> buttonCell = getPriceFeedComboBoxListCell();
        buttonCell.setId("price-feed-combo");
        priceComboBox.setButtonCell(buttonCell);

        Label marketPriceLabel = new Label();

        updateMarketPriceLabel(marketPriceLabel);

        marketPriceLabel.getStyleClass().add("nav-balance-label");
        marketPriceLabel.setPadding(new Insets(-2, 0, 4, 9));

        marketPriceBox.getChildren().addAll(priceComboBox, marketPriceLabel);

        model.getMarketPriceUpdated().addListener((observable, oldValue, newValue) ->
                updateMarketPriceLabel(marketPriceLabel));

        return new Tuple2<>(priceComboBox, marketPriceBox);
    }

    @NotNull
    private String getPriceProvider() {
        return model.getIsFiatCurrencyPriceFeedSelected().get() ? "BitcoinAverage" : "Poloniex";
    }

    private void updateMarketPriceLabel(Label label) {
        if (model.getIsPriceAvailable().get()) {
            if (model.getIsExternallyProvidedPrice().get()) {
                label.setText(Res.get("mainView.marketPriceWithProvider.label", "Bisq Price Index"));
                label.setTooltip(new Tooltip(getPriceProviderTooltipString()));
            } else {
                label.setText(Res.get("mainView.marketPrice.bisqInternalPrice"));
                final Tooltip tooltip = new Tooltip(Res.get("mainView.marketPrice.tooltip.bisqInternalPrice"));
                label.setTooltip(tooltip);
            }
        } else {
            label.setText("");
            label.setTooltip(null);
        }
    }

    @NotNull
    private String getPriceProviderTooltipString() {

        String selectedCurrencyCode = model.getPriceFeedService().getCurrencyCode();
        MarketPrice selectedMarketPrice = model.getPriceFeedService().getMarketPrice(selectedCurrencyCode);

        return Res.get("mainView.marketPrice.tooltip",
                "Bisq Price Index for " + selectedCurrencyCode,
                "",
                selectedMarketPrice != null ? DisplayUtils.formatTime(new Date(selectedMarketPrice.getTimestampSec())) : Res.get("shared.na"),
                model.getPriceFeedService().getProviderNodeAddress());
    }

    private VBox createSplashScreen() {
        VBox vBox = new VBox();
        vBox.setAlignment(Pos.CENTER);
        vBox.setSpacing(10);
        vBox.setId("splash");
{'fraction_non_alphanumeric': 0.11224383430274981, 'fraction_numerical': 0.0034607125732274272, 'mean_word_length': 2.7095352254272678, 'pattern_counts': {'":': 0, '<': 59, '<?xml version=': 0, '>': 85, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 4}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 6, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5826641', 'n_tokens_mistral': 12805, 'n_tokens_neox': 12371, 'n_words': 2230}