package org.jabref.gui.mergeentries.threewaymerge;

import java.util.ArrayList;
import java.util.List;

import javafx.scene.control.ScrollPane;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.RowConstraints;
import javafx.scene.layout.VBox;
import javafx.stage.Screen;

import org.jabref.gui.StateManager;
import org.jabref.gui.mergeentries.threewaymerge.fieldsmerger.FieldMergerFactory;
import org.jabref.gui.mergeentries.threewaymerge.toolbar.ThreeWayMergeToolbar;
import org.jabref.gui.preferences.GuiPreferences;
import org.jabref.logic.l10n.Localization;
import org.jabref.model.entry.BibEntry;
import org.jabref.model.entry.field.Field;
import org.jabref.model.entry.field.FieldProperty;

public class ThreeWayMergeView extends VBox {
    public static final int GRID_COLUMN_MIN_WIDTH = 250;

    public static final String LEFT_DEFAULT_HEADER = Localization.lang("Left Entry");
    public static final String RIGHT_DEFAULT_HEADER = Localization.lang("Right Entry");

    private final ColumnConstraints fieldNameColumnConstraints = new ColumnConstraints(150);
    private final ColumnConstraints leftEntryColumnConstraints = new ColumnConstraints(GRID_COLUMN_MIN_WIDTH, 256, Double.MAX_VALUE);
    private final ColumnConstraints rightEntryColumnConstraints = new ColumnConstraints(GRID_COLUMN_MIN_WIDTH, 256, Double.MAX_VALUE);
    private final ColumnConstraints mergedEntryColumnConstraints = new ColumnConstraints(GRID_COLUMN_MIN_WIDTH, 256, Double.MAX_VALUE);
    private final ThreeWayMergeToolbar toolbar;
    private final ThreeWayMergeHeaderView headerView;
    private final ScrollPane scrollPane;
    private final GridPane mergeGridPane;

    private final ThreeWayMergeViewModel viewModel;
    private final List<FieldRowView> fieldRows = new ArrayList<>();

    private final GuiPreferences preferences;
    private StateManager stateManager;

    private final FieldMergerFactory fieldMergerFactory;
    private final String keywordSeparator;

    public ThreeWayMergeView(BibEntry leftEntry, BibEntry rightEntry, String leftHeader, String rightHeader, GuiPreferences preferences, StateManager stateManager) {
        this.preferences = preferences;
        this.stateManager = stateManager;

        viewModel = new ThreeWayMergeViewModel(
                safeClone(leftEntry, rightEntry),
                safeClone(rightEntry, leftEntry),
                leftHeader,
                rightHeader);
        this.fieldMergerFactory = new FieldMergerFactory(preferences.getBibEntryPreferences());
        this.keywordSeparator = preferences.getBibEntryPreferences().getKeywordSeparator().toString();

        mergeGridPane = new GridPane();
        scrollPane = new ScrollPane();
        headerView = new ThreeWayMergeHeaderView(leftHeader, rightHeader);
        toolbar = new ThreeWayMergeToolbar();

        initializeColumnConstraints();
        initializeMergeGridPane();
        initializeScrollPane();
        initializeHeaderView();
        initializeToolbar();

        this.setPrefHeight(Screen.getPrimary().getBounds().getHeight() * 0.76);
        this.setPrefWidth(Screen.getPrimary().getBounds().getWidth() * 0.97);

        getChildren().addAll(toolbar, headerView, scrollPane);

        getStyleClass().add("three-way-merge");
    }

    public ThreeWayMergeView(BibEntry leftEntry, BibEntry rightEntry, GuiPreferences preferences, StateManager stateManager) {
        this(leftEntry, rightEntry, LEFT_DEFAULT_HEADER, RIGHT_DEFAULT_HEADER, preferences, stateManager);
    }

    private void initializeToolbar() {
        toolbar.setOnSelectLeftEntryValuesButtonClicked(this::selectLeftEntryValues);
        toolbar.setOnSelectRightEntryValuesButtonClicked(this::selectRightEntryValues);

        toolbar.showDiffProperty().addListener(e -> updateDiff());
        toolbar.diffViewProperty().addListener(e -> updateDiff());
        toolbar.diffHighlightingMethodProperty().addListener(e -> updateDiff());
        toolbar.hideEqualFieldsProperty().addListener(e -> showOrHideEqualFields());

        updateDiff();
        showOrHideEqualFields();
    }

    private void showOrHideEqualFields() {
        for (FieldRowView row : fieldRows) {
            if (toolbar.shouldHideEqualFields()) {
                if (row.hasEqualLeftAndRightValues()) {
                    row.hide();
                }
            } else {
                row.show();
            }
        }
    }

    private void updateDiff() {
        if (toolbar.shouldShowDiffs()) {
            for (FieldRowView row : fieldRows) {
                if ("Groups".equals(row.getFieldNameCell().getText()) && (row.getLeftValueCell().getText().contains(keywordSeparator) || row.getRightValueCell().getText().contains(keywordSeparator))) {
                    row.showDiff(new ShowDiffConfig(toolbar.getDiffView(), new GroupDiffMode(keywordSeparator)));
                } else {
                    row.showDiff(new ShowDiffConfig(toolbar.getDiffView(), toolbar.getDiffHighlightingMethod()));
                }
            }
        } else {
            fieldRows.forEach(FieldRowView::hideDiff);
        }
    }

    private void initializeHeaderView() {
        headerView.getColumnConstraints().addAll(fieldNameColumnConstraints,
                leftEntryColumnConstraints,
                rightEntryColumnConstraints,
                mergedEntryColumnConstraints);
    }

    private void initializeScrollPane() {
        scrollPane.setFitToWidth(true);
        scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
        scrollPane.setContent(mergeGridPane);
    }

    private void initializeColumnConstraints() {
        fieldNameColumnConstraints.setHgrow(Priority.NEVER);
        leftEntryColumnConstraints.setHgrow(Priority.ALWAYS);
        rightEntryColumnConstraints.setHgrow(Priority.ALWAYS);
        mergedEntryColumnConstraints.setHgrow(Priority.ALWAYS);
    }

    private void initializeMergeGridPane() {
        mergeGridPane.getColumnConstraints().addAll(fieldNameColumnConstraints, leftEntryColumnConstraints, rightEntryColumnConstraints, mergedEntryColumnConstraints);

        for (int fieldIndex = 0; fieldIndex < viewModel.numberOfVisibleFields(); fieldIndex++) {
            addRow(fieldIndex);

            mergeGridPane.getRowConstraints().add(new RowConstraints());
        }
    }

    private Field getFieldAtIndex(int index) {
        return viewModel.getVisibleFields().get(index);
    }

    private void addRow(int fieldIndex) {
        Field field = getFieldAtIndex(fieldIndex);

        FieldRowView fieldRow;
        if (field.getProperties().contains(FieldProperty.PERSON_NAMES)) {
            fieldRow = new PersonsNameFieldRowView(field, getLeftEntry(), getRightEntry(), getMergedEntry(), fieldMergerFactory, preferences, stateManager, fieldIndex);
        } else {
            fieldRow = new FieldRowView(field, getLeftEntry(), getRightEntry(), getMergedEntry(), fieldMergerFactory, preferences, stateManager, fieldIndex);
        }

        fieldRows.add(fieldIndex, fieldRow);

        mergeGridPane.add(fieldRow.getFieldNameCell(), 0, fieldIndex);
        mergeGridPane.add(fieldRow.getLeftValueCell(), 1, fieldIndex);
        mergeGridPane.add(fieldRow.getRightValueCell(), 2, fieldIndex);
        mergeGridPane.add(fieldRow.getMergedValueCell(), 3, fieldIndex);
    }

    public BibEntry getMergedEntry() {
        return viewModel.getMergedEntry();
    }

    public void setLeftHeader(String leftHeader) {
        headerView.setLeftHeader(leftHeader);
    }

    public void setRightHeader(String rightHeader) {
        headerView.setRightHeader(rightHeader);
    }

    public void selectLeftEntryValues() {
        fieldRows.forEach(FieldRowView::selectLeftValue);
    }

    public void selectRightEntryValues() {
        fieldRows.forEach(FieldRowView::selectRightValue);
    }

    public void showDiff(ShowDiffConfig diffConfig) {
        toolbar.setDiffView(diffConfig.diffView());
        toolbar.setDiffHighlightingMethod(diffConfig.diffHighlightingMethod());
        toolbar.setShowDiff(true);
    }

    public BibEntry getLeftEntry() {
        return viewModel.getLeftEntry();
    }

    public BibEntry getRightEntry() {
        return viewModel.getRightEntry();
    }

    public void saveConfiguration() {
        toolbar.saveToolbarConfiguration();
    }

    public void autoSelectBetterFields() {
        for (FieldRowView row : fieldRows) {
            row.autoSelectBetterValue();
        }
    }

    private static BibEntry safeClone(BibEntry primary, BibEntry peer) {
        if (primary != null) {
            return new BibEntry(primary);
        }
        BibEntry placeholder = peer != null && peer.getType() != null
                               ? new BibEntry(peer.getType())
                               : new BibEntry();
        if (peer != null) {
            peer.getCitationKey().ifPresent(placeholder::withCitationKey);
        }
        return placeholder;
    }
}
