/*
 * 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.dao.governance;

import bisq.desktop.Navigation;
import bisq.desktop.components.HyperlinkWithIcon;
import bisq.desktop.components.InputTextField;
import bisq.desktop.components.TitledGroupBg;
import bisq.desktop.main.MainView;
import bisq.desktop.main.dao.DaoView;
import bisq.desktop.main.dao.bonding.BondingView;
import bisq.desktop.main.dao.bonding.bonds.BondsView;
import bisq.desktop.util.FormBuilder;
import bisq.desktop.util.GUIUtil;
import bisq.desktop.util.Layout;
import bisq.desktop.util.validation.BsqValidator;
import bisq.desktop.util.validation.RegexValidator;

import bisq.common.config.BaseCurrencyNetwork;
import bisq.core.dao.DaoFacade;
import bisq.core.dao.governance.bond.Bond;
import bisq.core.dao.governance.bond.role.BondedRole;
import bisq.core.dao.governance.param.Param;
import bisq.core.dao.governance.proposal.ProposalType;
import bisq.core.dao.governance.proposal.param.ChangeParamInputValidator;
import bisq.core.dao.governance.proposal.param.ChangeParamValidator;
import bisq.core.dao.state.model.blockchain.BaseTx;
import bisq.core.dao.state.model.blockchain.Tx;
import bisq.core.dao.state.model.governance.Ballot;
import bisq.core.dao.state.model.governance.BondedRoleType;
import bisq.core.dao.state.model.governance.ChangeParamProposal;
import bisq.core.dao.state.model.governance.CompensationProposal;
import bisq.core.dao.state.model.governance.ConfiscateBondProposal;
import bisq.core.dao.state.model.governance.EvaluatedProposal;
import bisq.core.dao.state.model.governance.GenericProposal;
import bisq.core.dao.state.model.governance.Proposal;
import bisq.core.dao.state.model.governance.ProposalVoteResult;
import bisq.core.dao.state.model.governance.ReimbursementProposal;
import bisq.core.dao.state.model.governance.RemoveAssetProposal;
import bisq.core.dao.state.model.governance.Role;
import bisq.core.dao.state.model.governance.RoleProposal;
import bisq.core.dao.state.model.governance.Vote;
import bisq.core.locale.CurrencyUtil;
import bisq.core.locale.Res;
import bisq.core.user.Preferences;
import bisq.core.util.coin.BsqFormatter;
import bisq.core.util.validation.InputValidator;

import bisq.asset.Asset;

import bisq.common.util.Tuple3;

import org.bitcoinj.core.Coin;

import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.TextField;
import javafx.scene.control.TextInputControl;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.VBox;

import javafx.beans.value.ChangeListener;

import javafx.collections.FXCollections;

import javafx.util.StringConverter;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

import javax.annotation.Nullable;

import static bisq.desktop.util.FormBuilder.*;
import static com.google.common.base.Preconditions.checkNotNull;

@SuppressWarnings({"ConstantConditions", "StatementWithEmptyBody"})
@Slf4j
public class ProposalDisplay {
    private final GridPane gridPane;
    private final BsqFormatter bsqFormatter;
    private final DaoFacade daoFacade;

    // Nullable because if we are in result view mode (readonly) we don't need to set the input validator)
    @Nullable
    private final ChangeParamValidator changeParamValidator;
    private final Navigation navigation;
    private final Preferences preferences;

    @Nullable
    private TextField proposalFeeTextField, comboBoxValueTextField, requiredBondForRoleTextField;
    private TextField proposalTypeTextField, myVoteTextField, voteResultTextField;
    public InputTextField nameTextField;
    public InputTextField linkInputTextField;
    @Nullable
    public InputTextField requestedBsqTextField, paramValueTextField;
    @Nullable
    public ComboBox<Param> paramComboBox;
    @Nullable
    public ComboBox<Bond> confiscateBondComboBox;
    @Nullable
    public ComboBox<BondedRoleType> bondedRoleTypeComboBox;
    @Nullable
    public ComboBox<Asset> assetComboBox;

    @Getter
    private int gridRow;
    private HyperlinkWithIcon linkHyperlinkWithIcon;
    private HyperlinkWithIcon txHyperlinkWithIcon;
    private int gridRowStartIndex;
    private final List<Runnable> inputChangedListeners = new ArrayList<>();
    @Getter
    private List<TextInputControl> inputControls = new ArrayList<>();
    @Getter
    private List<ComboBox<?>> comboBoxes = new ArrayList<>();
    private final ChangeListener<Boolean> focusOutListener;
    private final ChangeListener<Object> inputListener;
    private ChangeListener<Param> paramChangeListener;
    private ChangeListener<BondedRoleType> requiredBondForRoleListener;
    private TitledGroupBg myVoteTitledGroup;
    private VBox linkWithIconContainer, comboBoxValueContainer, myVoteBox, voteResultBox;
    private int votingBoxRowSpan;

    private Optional<Runnable> navigateHandlerOptional = Optional.empty();

    public ProposalDisplay(GridPane gridPane,
                           BsqFormatter bsqFormatter,
                           DaoFacade daoFacade,
                           @Nullable ChangeParamValidator changeParamValidator,
                           Navigation navigation,
                           @Nullable Preferences preferences) {
        this.gridPane = gridPane;
        this.bsqFormatter = bsqFormatter;
        this.daoFacade = daoFacade;
        this.changeParamValidator = changeParamValidator;
        this.navigation = navigation;
        this.preferences = preferences;

        // focusOutListener = observable -> inputChangedListeners.forEach(Runnable::run);

        focusOutListener = (observable, oldValue, newValue) -> {
            if (oldValue && !newValue)
                inputChangedListeners.forEach(Runnable::run);
        };
        inputListener = (observable, oldValue, newValue) -> inputChangedListeners.forEach(Runnable::run);
    }

    public void addInputChangedListener(Runnable listener) {
        inputChangedListeners.add(listener);
    }

    public void removeInputChangedListener(Runnable listener) {
        inputChangedListeners.remove(listener);
    }

    public void createAllFields(String title, int gridRowStartIndex, double top, ProposalType proposalType,
                                boolean isMakeProposalScreen) {
        createAllFields(title, gridRowStartIndex, top, proposalType, isMakeProposalScreen, null);
    }

    public void createAllFields(String title, int gridRowStartIndex, double top, ProposalType proposalType,
                                boolean isMakeProposalScreen, String titledGroupStyle) {
        removeAllFields();
        this.gridRowStartIndex = gridRowStartIndex;
        this.gridRow = gridRowStartIndex;
        int titledGroupBgRowSpan = 5;

        switch (proposalType) {
            case COMPENSATION_REQUEST:
            case REIMBURSEMENT_REQUEST:
            case CONFISCATE_BOND:
            case REMOVE_ASSET:
                break;
            case CHANGE_PARAM:
            case BONDED_ROLE:
                titledGroupBgRowSpan = 6;
                break;
            case GENERIC:
                titledGroupBgRowSpan = 4;
                break;
        }

        TitledGroupBg titledGroupBg = addTitledGroupBg(gridPane, gridRow, titledGroupBgRowSpan, title, top);

        if (titledGroupStyle != null) titledGroupBg.getStyleClass().add(titledGroupStyle);

        double proposalTypeTop;

        if (top == Layout.GROUP_DISTANCE_WITHOUT_SEPARATOR) {
            proposalTypeTop = Layout.COMPACT_FIRST_ROW_AND_GROUP_DISTANCE_WITHOUT_SEPARATOR;
        } else if (top == Layout.GROUP_DISTANCE) {
            proposalTypeTop = Layout.FIRST_ROW_AND_GROUP_DISTANCE;
        } else if (top == 0) {
            proposalTypeTop = Layout.FIRST_ROW_DISTANCE;
        } else {
            proposalTypeTop = Layout.FIRST_ROW_DISTANCE + top;
        }

        proposalTypeTextField = addTopLabelTextField(gridPane, gridRow,
                Res.get("dao.proposal.display.type"), proposalType.getDisplayName(), proposalTypeTop).second;

        nameTextField = addInputTextField(gridPane, ++gridRow, Res.get("dao.proposal.display.name"));
        if (isMakeProposalScreen)
            nameTextField.setValidator(new InputValidator());
        inputControls.add(nameTextField);

        linkInputTextField = addInputTextField(gridPane, ++gridRow,
                Res.get("dao.proposal.display.link"));
        linkInputTextField.setPromptText(Res.get("dao.proposal.display.link.prompt"));
        if (isMakeProposalScreen) {
            RegexValidator validator = new RegexValidator();
            if (proposalType == ProposalType.COMPENSATION_REQUEST) {
                validator.setPattern("https://bisq.network/dao-compensation/\\d+");
                linkInputTextField.setText("https://bisq.network/dao-compensation/#");
            } else if (proposalType == ProposalType.REIMBURSEMENT_REQUEST) {
                validator.setPattern("https://bisq.network/dao-reimbursement/\\d+");
                linkInputTextField.setText("https://bisq.network/dao-reimbursement/#");
            } else {
                validator.setPattern("https://bisq.network/dao-proposals/\\d+");
                linkInputTextField.setText("https://bisq.network/dao-proposals/#");
            }
            linkInputTextField.setValidator(validator);
        }
        inputControls.add(linkInputTextField);

        Tuple3<Label, HyperlinkWithIcon, VBox> tuple = addTopLabelHyperlinkWithIcon(gridPane, gridRow,
                Res.get("dao.proposal.display.link"), "", "", 0);
        linkHyperlinkWithIcon = tuple.second;
        linkWithIconContainer = tuple.third;
        // TODO HyperlinkWithIcon does not scale automatically (button base, -> make anchorpane as base)
        linkHyperlinkWithIcon.prefWidthProperty().bind(nameTextField.widthProperty());

        linkWithIconContainer.setVisible(false);
        linkWithIconContainer.setManaged(false);

        if (!isMakeProposalScreen) {
            Tuple3<Label, HyperlinkWithIcon, VBox> uidTuple = addTopLabelHyperlinkWithIcon(gridPane, ++gridRow,
                    Res.get("dao.proposal.display.txId"), "", "", 0);
            txHyperlinkWithIcon = uidTuple.second;
            // TODO HyperlinkWithIcon does not scale automatically (button base, -> make anchorPane as base)
            txHyperlinkWithIcon.prefWidthProperty().bind(nameTextField.widthProperty());
        }

        int comboBoxValueTextFieldIndex = -1;
        switch (proposalType) {
            case COMPENSATION_REQUEST:
            case REIMBURSEMENT_REQUEST:
                requestedBsqTextField = addInputTextField(gridPane, ++gridRow,
                        Res.get("dao.proposal.display.requestedBsq"));
                checkNotNull(requestedBsqTextField, "requestedBsqTextField must not be null");
                inputControls.add(requestedBsqTextField);

                if (isMakeProposalScreen) {
                    BsqValidator bsqValidator = new BsqValidator(bsqFormatter);
                    if (proposalType == ProposalType.COMPENSATION_REQUEST) {
                        bsqValidator.setMinValue(daoFacade.getMinCompensationRequestAmount());
                        bsqValidator.setMaxValue(daoFacade.getMaxCompensationRequestAmount());
                    } else if (proposalType == ProposalType.REIMBURSEMENT_REQUEST) {
                        bsqValidator.setMinValue(daoFacade.getMinReimbursementRequestAmount());
                        bsqValidator.setMaxValue(daoFacade.getMaxReimbursementRequestAmount());
                    }
                    requestedBsqTextField.setValidator(bsqValidator);
                }
                break;
            case CHANGE_PARAM:
                checkNotNull(gridPane, "gridPane must not be null");
                paramComboBox = FormBuilder.addComboBox(gridPane, ++gridRow,
                        Res.get("dao.proposal.display.paramComboBox.label"));
                comboBoxValueTextFieldIndex = gridRow;
                checkNotNull(paramComboBox, "paramComboBox must not be null");
                List<Param> list = Arrays.stream(Param.values())
                        .filter(e -> e != Param.UNDEFINED && e != Param.PHASE_UNDEFINED)
                        .collect(Collectors.toList());
                paramComboBox.setItems(FXCollections.observableArrayList(list));
                paramComboBox.setConverter(new StringConverter<>() {
                    @Override
                    public String toString(Param param) {
                        return param != null ? param.getDisplayString() : "";
                    }

                    @Override
                    public Param fromString(String string) {
                        return null;
                    }
                });
                comboBoxes.add(paramComboBox);
                paramValueTextField = addInputTextField(gridPane, ++gridRow,
                        Res.get("dao.proposal.display.paramValue"));

                inputControls.add(paramValueTextField);

                paramChangeListener = (observable, oldValue, newValue) -> {
                    if (newValue != null) {
                        paramValueTextField.clear();
                        String currentValue = bsqFormatter.formatParamValue(newValue, daoFacade.getParamValue(newValue));
                        paramValueTextField.setPromptText(Res.get("dao.param.currentValue", currentValue));
                        if (changeParamValidator != null && isMakeProposalScreen) {
                            ChangeParamInputValidator validator = new ChangeParamInputValidator(newValue, changeParamValidator);
                            paramValueTextField.setValidator(validator);
                        }
                    }
                };
                paramComboBox.getSelectionModel().selectedItemProperty().addListener(paramChangeListener);
                break;
            case BONDED_ROLE:
                bondedRoleTypeComboBox = FormBuilder.addComboBox(gridPane, ++gridRow,
                        Res.get("dao.proposal.display.bondedRoleComboBox.label"));
                comboBoxValueTextFieldIndex = gridRow;
                checkNotNull(bondedRoleTypeComboBox, "bondedRoleTypeComboBox must not be null");
                List<BondedRoleType> bondedRoleTypes = Arrays.stream(BondedRoleType.values())
                        .filter(e -> e != BondedRoleType.UNDEFINED)
                        .collect(Collectors.toList());
                bondedRoleTypeComboBox.setItems(FXCollections.observableArrayList(bondedRoleTypes));
                bondedRoleTypeComboBox.setConverter(new StringConverter<>() {
                    @Override
                    public String toString(BondedRoleType bondedRoleType) {
                        return bondedRoleType != null ? bondedRoleType.getDisplayString() : "";
                    }

                    @Override
                    public BondedRoleType fromString(String string) {
                        return null;
                    }
                });
                comboBoxes.add(bondedRoleTypeComboBox);
                requiredBondForRoleTextField = addTopLabelReadOnlyTextField(gridPane, ++gridRow,
                        Res.get("dao.proposal.display.requiredBondForRole.label")).second;

                requiredBondForRoleListener = (observable, oldValue, newValue) -> {
                    if (newValue != null) {
                        requiredBondForRoleTextField.setText(bsqFormatter.formatCoinWithCode(Coin.valueOf(daoFacade.getRequiredBond(newValue))));
                    }
                };
                bondedRoleTypeComboBox.getSelectionModel().selectedItemProperty().addListener(requiredBondForRoleListener);

                break;
            case CONFISCATE_BOND:
                confiscateBondComboBox = FormBuilder.addComboBox(gridPane, ++gridRow,
                        Res.get("dao.proposal.display.confiscateBondComboBox.label"));
                comboBoxValueTextFieldIndex = gridRow;
                checkNotNull(confiscateBondComboBox, "confiscateBondComboBox must not be null");

                confiscateBondComboBox.setItems(FXCollections.observableArrayList(daoFacade.getAllActiveBonds()));
                confiscateBondComboBox.setConverter(new StringConverter<>() {
                    @Override
                    public String toString(Bond bond) {
                        String details = " (" + Res.get("dao.bond.table.column.lockupTxId") + ": " + bond.getLockupTxId() + ")";
                        if (bond instanceof BondedRole) {
                            return bond.getBondedAsset().getDisplayString() + details;
                        } else {
                            return Res.get("dao.bond.bondedReputation") + details;
                        }
                    }

                    @Override
                    public Bond fromString(String string) {
                        return null;
                    }
                });
                comboBoxes.add(confiscateBondComboBox);
                break;
            case GENERIC:
                break;
            case REMOVE_ASSET:
                assetComboBox = FormBuilder.addComboBox(gridPane, ++gridRow,
                        Res.get("dao.proposal.display.assetComboBox.label"));
                comboBoxValueTextFieldIndex = gridRow;
                checkNotNull(assetComboBox, "assetComboBox must not be null");
                List<Asset> assetList = CurrencyUtil.getSortedAssetStream()
                        .filter(e -> !e.getTickerSymbol().equals("BSQ"))
                        .collect(Collectors.toList());
                assetComboBox.setItems(FXCollections.observableArrayList(assetList));
                assetComboBox.setConverter(new StringConverter<>() {
                    @Override
                    public String toString(Asset asset) {
                        return asset != null ? CurrencyUtil.getNameAndCode(asset.getTickerSymbol()) : "";
                    }

                    @Override
                    public Asset fromString(String string) {
                        return null;
                    }
                });
                comboBoxes.add(assetComboBox);
                break;
        }

        if (comboBoxValueTextFieldIndex > -1) {
            Tuple3<Label, TextField, VBox> tuple3 = addTopLabelReadOnlyTextField(gridPane, comboBoxValueTextFieldIndex,
                    Res.get("dao.proposal.display.option"));
            comboBoxValueTextField = tuple3.second;
            comboBoxValueContainer = tuple3.third;
            comboBoxValueContainer.setVisible(false);
            comboBoxValueContainer.setManaged(false);
        }

        if (isMakeProposalScreen) {
            proposalFeeTextField = addTopLabelTextField(gridPane, ++gridRow, Res.get("dao.proposal.display.proposalFee")).second;
            proposalFeeTextField.setText(bsqFormatter.formatCoinWithCode(daoFacade.getProposalFee(daoFacade.getChainHeight())));
        }

        votingBoxRowSpan = 4;

        myVoteTitledGroup = addTitledGroupBg(gridPane, ++gridRow, 4, Res.get("dao.proposal.myVote.title"), Layout.COMPACT_FIRST_ROW_DISTANCE);

        Tuple3<Label, TextField, VBox> tuple3 = addTopLabelTextField(gridPane, ++gridRow, Res.get("dao.proposal.display.myVote"), Layout.COMPACT_FIRST_ROW_DISTANCE);

        myVoteBox = tuple3.third;
        setMyVoteBoxVisibility(false);

        myVoteTextField = tuple3.second;

        tuple3 = addTopLabelReadOnlyTextField(gridPane, ++gridRow, Res.get("dao.proposal.display.voteResult"));

        voteResultBox = tuple3.third;
        voteResultBox.setVisible(false);
        voteResultBox.setManaged(false);

        voteResultTextField = tuple3.second;

        addListeners();
    }

    public void applyBallot(@Nullable Ballot ballot) {
        String myVote = Res.get("dao.proposal.display.myVote.ignored");
        boolean isNotNull = ballot != null;
        Vote vote = isNotNull ? ballot.getVote() : null;
        if (vote != null) {
            myVote = vote.isAccepted() ? Res.get("dao.proposal.display.myVote.accepted") :
                    Res.get("dao.proposal.display.myVote.rejected");
        }
        myVoteTextField.setText(myVote);

        setMyVoteBoxVisibility(isNotNull);
    }

    public void applyEvaluatedProposal(@Nullable EvaluatedProposal evaluatedProposal) {

        boolean isEvaluatedProposalNotNull = evaluatedProposal != null;
        if (isEvaluatedProposalNotNull) {
            String result = evaluatedProposal.isAccepted() ? Res.get("dao.proposal.voteResult.success") :
                    Res.get("dao.proposal.voteResult.failed");
            ProposalVoteResult proposalVoteResult = evaluatedProposal.getProposalVoteResult();
            String threshold = (proposalVoteResult.getThreshold() / 100D) + "%";
            String requiredThreshold = (daoFacade.getRequiredThreshold(evaluatedProposal.getProposal()) * 100D) + "%";
            String quorum = bsqFormatter.formatCoinWithCode(Coin.valueOf(proposalVoteResult.getQuorum()));
            String requiredQuorum = bsqFormatter.formatCoinWithCode(daoFacade.getRequiredQuorum(evaluatedProposal.getProposal()));
            String summary = Res.get("dao.proposal.voteResult.summary", result,
                    threshold, requiredThreshold, quorum, requiredQuorum);
            voteResultTextField.setText(summary);
        }
        voteResultBox.setVisible(isEvaluatedProposalNotNull);
        voteResultBox.setManaged(isEvaluatedProposalNotNull);
    }

    public void applyBallotAndVoteWeight(@Nullable Ballot ballot, long merit, long stake) {
        applyBallotAndVoteWeight(ballot, merit, stake, true);
    }

    public void applyBallotAndVoteWeight(@Nullable Ballot ballot, long merit, long stake, boolean ballotIncluded) {
        boolean ballotIsNotNull = ballot != null;
        boolean hasVoted = stake > 0;
        if (hasVoted) {
            String myVote = Res.get("dao.proposal.display.myVote.ignored");
            Vote vote = ballotIsNotNull ? ballot.getVote() : null;
            if (vote != null) {
                myVote = vote.isAccepted() ? Res.get("dao.proposal.display.myVote.accepted") :
                        Res.get("dao.proposal.display.myVote.rejected");
            }

            String voteIncluded = ballotIncluded ? "" : " - " + Res.get("dao.proposal.display.myVote.unCounted");
            String meritString = bsqFormatter.formatCoinWithCode(Coin.valueOf(merit));
            String stakeString = bsqFormatter.formatCoinWithCode(Coin.valueOf(stake));
            String weight = bsqFormatter.formatCoinWithCode(Coin.valueOf(merit + stake));
            String myVoteSummary = Res.get("dao.proposal.myVote.summary", myVote,
                    weight, meritString, stakeString, voteIncluded);
            myVoteTextField.setText(myVoteSummary);

            GridPane.setRowSpan(myVoteTitledGroup, votingBoxRowSpan - 1);
        }

        boolean show = ballotIsNotNull && hasVoted;
        setMyVoteBoxVisibility(show);
    }

    public void setIsVoteIncludedInResult(boolean isVoteIncludedInResult) {
        if (!isVoteIncludedInResult && myVoteTextField != null && !myVoteTextField.getText().isEmpty()) {
            String text = myVoteTextField.getText();
            myVoteTextField.setText(Res.get("dao.proposal.myVote.invalid") + " - " + text);
            myVoteTextField.getStyleClass().add("error-text");
        }
    }

    public void applyProposalPayload(Proposal proposal) {
        proposalTypeTextField.setText(proposal.getType().getDisplayName());
        nameTextField.setText(proposal.getName());
        linkInputTextField.setVisible(false);
        linkInputTextField.setManaged(false);
        if (linkWithIconContainer != null) {
            linkWithIconContainer.setVisible(true);
            linkWithIconContainer.setManaged(true);
            linkHyperlinkWithIcon.setText(proposal.getLink());
            linkHyperlinkWithIcon.setOnAction(e -> GUIUtil.openWebPage(proposal.getLink()));
        }

        if (txHyperlinkWithIcon != null) {
            txHyperlinkWithIcon.setText(proposal.getTxId());
            txHyperlinkWithIcon.setOnAction(e ->
                    GUIUtil.openTxInBsqBlockExplorer(proposal.getTxId(), preferences));
        }

        if (proposal instanceof CompensationProposal) {
            CompensationProposal compensationProposal = (CompensationProposal) proposal;
            checkNotNull(requestedBsqTextField, "requestedBsqTextField must not be null");
            requestedBsqTextField.setText(bsqFormatter.formatCoinWithCode(compensationProposal.getRequestedBsq()));
        } else if (proposal instanceof ReimbursementProposal) {
            ReimbursementProposal reimbursementProposal = (ReimbursementProposal) proposal;
            checkNotNull(requestedBsqTextField, "requestedBsqTextField must not be null");
            requestedBsqTextField.setText(bsqFormatter.formatCoinWithCode(reimbursementProposal.getRequestedBsq()));
        } else if (proposal instanceof ChangeParamProposal) {
            ChangeParamProposal changeParamProposal = (ChangeParamProposal) proposal;
            checkNotNull(paramComboBox, "paramComboBox must not be null");
            paramComboBox.getSelectionModel().select(changeParamProposal.getParam());
            comboBoxValueTextField.setText(paramComboBox.getConverter().toString(changeParamProposal.getParam()));
            checkNotNull(paramValueTextField, "paramValueTextField must not be null");
            paramValueTextField.setText(bsqFormatter.formatParamValue(changeParamProposal.getParam(), changeParamProposal.getParamValue()));
            String currentValue = bsqFormatter.formatParamValue(changeParamProposal.getParam(),
                    daoFacade.getParamValue(changeParamProposal.getParam()));
            int height = daoFacade.getTx(changeParamProposal.getTxId())
                    .map(BaseTx::getBlockHeight)
                    .orElse(daoFacade.getGenesisBlockHeight());
            String valueAtProposal = bsqFormatter.formatParamValue(changeParamProposal.getParam(),
                    daoFacade.getParamValue(changeParamProposal.getParam(), height));
            paramValueTextField.setPromptText(Res.get("dao.param.currentAndPastValue", currentValue, valueAtProposal));
        } else if (proposal instanceof RoleProposal) {
            RoleProposal roleProposal = (RoleProposal) proposal;
            checkNotNull(bondedRoleTypeComboBox, "bondedRoleComboBox must not be null");
            Role role = roleProposal.getRole();
            bondedRoleTypeComboBox.getSelectionModel().select(role.getBondedRoleType());
            comboBoxValueTextField.setText(bondedRoleTypeComboBox.getConverter().toString(role.getBondedRoleType()));
            requiredBondForRoleTextField.setText(bsqFormatter.formatCoin(Coin.valueOf(daoFacade.getRequiredBond(roleProposal))));
            // TODO maybe show also unlock time?
        } else if (proposal instanceof ConfiscateBondProposal) {
            ConfiscateBondProposal confiscateBondProposal = (ConfiscateBondProposal) proposal;
            checkNotNull(confiscateBondComboBox, "confiscateBondComboBox must not be null");
            daoFacade.getBondByLockupTxId(confiscateBondProposal.getLockupTxId())
                    .ifPresent(bond -> {
                        confiscateBondComboBox.getSelectionModel().select(bond);
                        comboBoxValueTextField.setText(confiscateBondComboBox.getConverter().toString(bond));
                        comboBoxValueTextField.setOnMouseClicked(e -> {
                            navigateHandlerOptional.ifPresent(Runnable::run);
                            navigation.navigateToWithData(bond, MainView.class, DaoView.class, BondingView.class,
                                    BondsView.class);
                        });

                        comboBoxValueTextField.getStyleClass().addAll("hyperlink", "force-underline", "show-hand");
                    });
        } else if (proposal instanceof GenericProposal) {
            // do nothing
        } else if (proposal instanceof RemoveAssetProposal) {
            RemoveAssetProposal removeAssetProposal = (RemoveAssetProposal) proposal;
            checkNotNull(assetComboBox, "assetComboBox must not be null");
            CurrencyUtil.findAsset(removeAssetProposal.getTickerSymbol(), BaseCurrencyNetwork.BTC_MAINNET)
                    .ifPresent(asset -> {
                        assetComboBox.getSelectionModel().select(asset);
                        comboBoxValueTextField.setText(assetComboBox.getConverter().toString(asset));
                    });
        }
        int chainHeight = daoFacade.getTx(proposal.getTxId()).map(Tx::getBlockHeight).orElse(daoFacade.getChainHeight());
        if (proposalFeeTextField != null)
            proposalFeeTextField.setText(bsqFormatter.formatCoinWithCode(daoFacade.getProposalFee(chainHeight)));
    }

    private void addListeners() {
        inputControls.stream()
                .filter(Objects::nonNull).forEach(inputControl -> {
            inputControl.textProperty().addListener(inputListener);
            inputControl.focusedProperty().addListener(focusOutListener);
        });
        comboBoxes.stream()
                .filter(Objects::nonNull)
                .forEach(comboBox -> comboBox.getSelectionModel().selectedItemProperty().addListener(inputListener));
    }

    public void removeListeners() {
        inputControls.stream()
                .filter(Objects::nonNull).forEach(inputControl -> {
            inputControl.textProperty().removeListener(inputListener);
            inputControl.focusedProperty().removeListener(focusOutListener);
        });
        comboBoxes.stream()
                .filter(Objects::nonNull)
                .forEach(comboBox -> comboBox.getSelectionModel().selectedItemProperty().removeListener(inputListener));

        if (paramComboBox != null && paramChangeListener != null)
            paramComboBox.getSelectionModel().selectedItemProperty().removeListener(paramChangeListener);

        if (bondedRoleTypeComboBox != null && requiredBondForRoleListener != null)
            bondedRoleTypeComboBox.getSelectionModel().selectedItemProperty().removeListener(requiredBondForRoleListener);
    }

    public void clearForm() {
        inputControls.stream().filter(Objects::nonNull).forEach(TextInputControl::clear);

        if (linkHyperlinkWithIcon != null)
            linkHyperlinkWithIcon.clear();

        comboBoxes.stream().filter(Objects::nonNull).forEach(comboBox -> comboBox.getSelectionModel().clearSelection());
    }

    public void setEditable(boolean isEditable) {
        inputControls.stream().filter(Objects::nonNull).forEach(e -> e.setEditable(isEditable));
        comboBoxes.stream().filter(Objects::nonNull).forEach(comboBox -> {
            comboBox.setVisible(isEditable);
            comboBox.setManaged(isEditable);

            if (comboBoxValueContainer != null) {
                comboBoxValueContainer.setVisible(!isEditable);
                comboBoxValueContainer.setManaged(!isEditable);
            }
        });

        linkInputTextField.setVisible(true);
        linkInputTextField.setManaged(true);

        if (linkWithIconContainer != null) {
            linkWithIconContainer.setVisible(false);
            linkWithIconContainer.setManaged(false);
            linkHyperlinkWithIcon.setOnAction(null);
        }
    }

    public void removeAllFields() {
        if (gridRow > 0) {
            clearForm();
            GUIUtil.removeChildrenFromGridPaneRows(gridPane, gridRowStartIndex, gridRow);
            gridRow = gridRowStartIndex;
        }

        if (linkHyperlinkWithIcon != null)
            linkHyperlinkWithIcon.prefWidthProperty().unbind();

        inputControls.clear();
        comboBoxes.clear();
    }

    public void onNavigate(Runnable navigateHandler) {
        navigateHandlerOptional = Optional.of(navigateHandler);
    }

    public int incrementAndGetGridRow() {
        return ++gridRow;
    }

    @SuppressWarnings("Duplicates")
    public ScrollPane getView() {
        ScrollPane scrollPane = new ScrollPane();
        scrollPane.setFitToWidth(true);
        scrollPane.setFitToHeight(true);

        AnchorPane anchorPane = new AnchorPane();
        scrollPane.setContent(anchorPane);

        gridPane.setHgap(5);
        gridPane.setVgap(5);

        ColumnConstraints columnConstraints1 = new ColumnConstraints();
        columnConstraints1.setPercentWidth(100);

        gridPane.getColumnConstraints().addAll(columnConstraints1);

        AnchorPane.setBottomAnchor(gridPane, 10d);
        AnchorPane.setRightAnchor(gridPane, 10d);
        AnchorPane.setLeftAnchor(gridPane, 10d);
        AnchorPane.setTopAnchor(gridPane, 10d);
        anchorPane.getChildren().add(gridPane);

        return scrollPane;
    }

    private void setMyVoteBoxVisibility(boolean visibility) {
        myVoteTitledGroup.setVisible(visibility);
        myVoteTitledGroup.setManaged(visibility);
        myVoteBox.setVisible(visibility);
        myVoteBox.setManaged(visibility);
    }
}