/*
 * JointPriorDialog.java
 *
 * Copyright (c) 2002-2015 Alexei Drummond, Andrew Rambaut and Marc Suchard
 *
 * This file is part of BEAST.
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership and licensing.
 *
 * BEAST is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 *  BEAST 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with BEAST; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA  02110-1301  USA
 */

/*
 * PriorDialog.java
 *
 * @author Marc A. Suchard
 */

package dr.app.beauti.priorsPanel;

import dr.app.beauti.ComboBoxRenderer;
import dr.app.beauti.components.hpm.HierarchicalModelComponentOptions;
import dr.app.beauti.components.linkedparameters.LinkedParameter;
import dr.app.beauti.options.BeautiOptions;
import dr.app.beauti.options.Parameter;
import dr.app.beauti.options.TraitData;
import dr.app.beauti.traitspanel.CreateTraitDialog;
import dr.app.beauti.types.PriorType;
import dr.app.gui.components.RealNumberField;
import dr.app.gui.table.TableEditorStopper;
import dr.app.util.OSType;
import jam.panels.ActionPanel;
import jam.panels.OptionsPanel;

import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.border.LineBorder;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.plaf.BorderUIResource;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableColumn;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.util.*;
import java.util.List;

/**
 * A dialog which acts as a base for linking parameters together under joint priors such
 * as hierarchical models.
 *
 * @author Andrew Rambaut
 * @author Marc A. Suchard
 */
public class JointPriorDialog implements AbstractPriorDialog {

    private static final int MINIMUM_TABLE_WIDTH = 120;
    private static final int MINIMUM_TABLE_HEIGHT = 160;
    private static final int PREFERRED_TABLE_WIDTH = 180;
    private static final int PREFERRED_TABLE_HEIGHT = 320;

    private JFrame frame;

    private final JTable parametersTable;
    private final ParametersTableModel parametersTableModel;

    private JTextField nameField = new JTextField();
    private JPanel panel;

    private final PriorSettingsPanel priorSettingsPanel;

    private Parameter parameter;

    final private BeautiOptions options;

    private SelectParametersDialog selectParametersDialog = null;
    private List<Parameter> compatibleParameterList;
    private List<Parameter> dependentParameterList;

    public JointPriorDialog(JFrame frame, BeautiOptions options) {
        this.frame = frame;

        this.options = options;

        priorSettingsPanel = new PriorSettingsPanel(frame);

        nameField.setColumns(30);

        parametersTableModel = new ParametersTableModel();
//        TableSorter sorter = new TableSorter(traitsTableModel);
//        traitsTable = new JTable(sorter);
//        sorter.setTableHeader(traitsTable.getTableHeader());
        parametersTable = new JTable(parametersTableModel);

        parametersTable.getTableHeader().setReorderingAllowed(false);
        parametersTable.getTableHeader().setResizingAllowed(false);
//        traitsTable.getTableHeader().setDefaultRenderer(
//                new HeaderRenderer(SwingConstants.LEFT, new Insets(0, 4, 0, 4)));

        parametersTable.getSelectionModel().setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
        parametersTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
            public void valueChanged(ListSelectionEvent evt) {
                parametersSelectionChanged();
            }
        });

    }

    private void parametersSelectionChanged() {
        int selRow = parametersTable.getSelectedRow();
        if (selRow >= 0) {
            removeParameterAction.setEnabled(true);
        }

        if (dependentParameterList.size() <= 0) {
            removeParameterAction.setEnabled(false);
        }
    }

    public boolean validateModelName() {
        return validateModelName(nameField.getText());
    }

    private boolean validateModelName(String name) {
//        System.err.println("Validating: " + modelName);
        // check that the name is valid

        if (name.equals(parameter.getName())) {
            return true;
        }

        if (name.trim().length() == 0) {
            Toolkit.getDefaultToolkit().beep();
            return false;
        }

        // check that a parameter with this name doesn't exist
        if (parameterExists(name)) {
            JOptionPane.showMessageDialog(frame,
                    "A parameter with this name already exists.",
                    "Linked parameter error",
                    JOptionPane.WARNING_MESSAGE);
            return false;
        }

        // check that a model with this name doesn't exist
        if (modelExists(name)) {
            JOptionPane.showMessageDialog(frame,
                    "A model with this name already exists.",
                    "Linked parameter error",
                    JOptionPane.WARNING_MESSAGE);
            return false;
        }

        return true;
    }

    private boolean parameterExists(String name) {
        for (Parameter parameter : options.selectParameters()) {
            if (parameter.getName().equals(name)) {
                return true;
            }
        }
        return false;
    }

    private boolean modelExists(String modelName) {
        HierarchicalModelComponentOptions comp = (HierarchicalModelComponentOptions)
                options.getComponentOptions(HierarchicalModelComponentOptions.class);
        return comp.modelExists(modelName);
    }

    public int showDialog() {
        panel = new JPanel(new GridBagLayout());

        double lower = Double.NEGATIVE_INFINITY;
        double upper = Double.POSITIVE_INFINITY;

        if (parameter.isZeroOne) {
            lower = 0.0;
            upper = 1.0;
        } else if (parameter.isNonNegative) {
            lower = 0.0;
        }

        panel = new JPanel(new GridBagLayout());

        setupComponents();

        JOptionPane optionPane = new JOptionPane(panel,
                JOptionPane.PLAIN_MESSAGE,
                JOptionPane.OK_CANCEL_OPTION,
                null,
                null,
                null);
        optionPane.setBorder(new EmptyBorder(12, 12, 12, 12));

        final JDialog dialog = optionPane.createDialog(frame, "Linked Parameter Setup");

        priorSettingsPanel.setDialog(dialog);
        priorSettingsPanel.setParameter(parameter);

        if (OSType.isMac()) {
            dialog.setMinimumSize(new Dimension(dialog.getBounds().width, 300));
        } else {
            Toolkit tk = Toolkit.getDefaultToolkit();
            Dimension d = tk.getScreenSize();
            if (d.height < 700 && panel.getHeight() > 450) {
                dialog.setSize(new Dimension(panel.getWidth() + 100, 550));
            } else {
                // setSize because optionsPanel is shrunk in dialog
                dialog.setSize(new Dimension(panel.getWidth() + 100, panel.getHeight() + 100));
            }

//            System.out.println("panel width = " + panel.getWidth());
//            System.out.println("panel height = " + panel.getHeight());
        }

        dialog.pack();
        dialog.setResizable(true);
        dialog.setVisible(true);

        int result = JOptionPane.CANCEL_OPTION;
        Integer value = (Integer) optionPane.getValue();
        if (value != null && value != -1) {
            result = value;
        }

        return result;
    }

    public String getName() {
        return nameField.getText();
    }

    public List<Parameter> getDependentParameterList() {
        return dependentParameterList;
    }

    private void setupComponents() {
        panel.removeAll();

        JScrollPane scrollPane1 = new JScrollPane(parametersTable,
                JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
                JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
        scrollPane1.setOpaque(false);

        ActionPanel actionPanel1 = new ActionPanel(false);
        actionPanel1.setAddAction(addParameterAction);
        actionPanel1.setRemoveAction(removeParameterAction);
        actionPanel1.setAddToolTipText("Use this button to add an existing parameter to the prior");
        actionPanel1.setRemoveToolTipText("Use this button to remove a parameter from the prior");

        removeParameterAction.setEnabled(false);

        JPanel controlPanel1 = new JPanel(new FlowLayout(FlowLayout.LEFT));
        controlPanel1.setOpaque(false);
        controlPanel1.add(actionPanel1);

        JPanel panel1 = new JPanel(new BorderLayout(0, 0));
        panel1.setOpaque(false);
        panel1.add(new JLabel("Linked parameters:"), BorderLayout.NORTH);
        panel1.add(scrollPane1, BorderLayout.CENTER);

        // removing the control panel for now. Not sure whether we really want adding and
        // removing of parameteres in this dialog.
//        panel1.add(controlPanel1, BorderLayout.SOUTH);
        panel1.setSize(new Dimension(PREFERRED_TABLE_WIDTH, PREFERRED_TABLE_HEIGHT));
        panel1.setPreferredSize(new Dimension(PREFERRED_TABLE_WIDTH, PREFERRED_TABLE_HEIGHT));
        panel1.setMinimumSize(new Dimension(MINIMUM_TABLE_WIDTH, MINIMUM_TABLE_HEIGHT));

        OptionsPanel optionsPanel = new OptionsPanel(0,6);
        if (parameter.getName() != null) {
            nameField.setText(parameter.getName());
        } else {
            nameField.setText("Untitled");
        }
        optionsPanel.addComponentWithLabel("Unique Name: ", nameField);
//        optionsPanel.addComponentWithLabel("Initial Value: ", initialField);

        panel.setOpaque(false);
        panel.setBorder(new BorderUIResource.EmptyBorderUIResource(new Insets(12, 12, 12, 12)));

        GridBagConstraints c = new GridBagConstraints();
        c.gridx = 0;
        c.gridy = 0;
        c.weightx = 0;
        c.weighty = 0;
        c.anchor = GridBagConstraints.PAGE_START;
        c.fill = GridBagConstraints.HORIZONTAL;
        c.gridwidth = 2;
        panel.add(optionsPanel, c);

        c.gridx = 0;
        c.gridy = 1;
        c.anchor = GridBagConstraints.PAGE_START;
        c.fill = GridBagConstraints.VERTICAL;
        c.gridwidth = 1;
        panel.add(panel1, c);

        c.gridx = 1;
        c.gridy = 1;
        c.weightx = 1;
        c.weighty = 1;
        c.fill = GridBagConstraints.BOTH;
        c.anchor = GridBagConstraints.PAGE_START;
        c.gridwidth = GridBagConstraints.REMAINDER;
        panel.add(priorSettingsPanel, c);

    }

    public void getArguments(Parameter parameter) {
        priorSettingsPanel.getArguments(parameter);
    }

    public boolean hasInvalidInput(boolean showError) {
        return priorSettingsPanel.hasInvalidInput(showError);
    }

    public boolean addParameter() {
        if (selectParametersDialog == null) {
            selectParametersDialog = new SelectParametersDialog(frame);
        }
        List<Parameter> availableParameters = new ArrayList<Parameter>(compatibleParameterList);
        availableParameters.removeAll(dependentParameterList);
        int result = selectParametersDialog.showDialog("Select parameter to add to this Linked Parameter", availableParameters);
        if (result == JOptionPane.OK_OPTION) {
            Parameter parameter = selectParametersDialog.getSelectedParameter();
            dependentParameterList.add(parameter);
            parametersTableModel.fireTableDataChanged();
        } else if (result == JOptionPane.CANCEL_OPTION) {
            return false;
        }

        return true;
    }

    private void removeSelectedParameters() {
        int[] selRows = parametersTable.getSelectedRows();
        List<Parameter> parametersToRemove = new ArrayList<Parameter>();
        for (int row : selRows) {
            parametersToRemove.add((Parameter)parametersTable.getValueAt(row, 0));
        }
        removeParameters(parametersToRemove);
    }

    private void removeParameters(List<Parameter> parametersToRemove) {
        for (Parameter parameter : parametersToRemove) {
            dependentParameterList.remove(parameter);
        }
        parametersTableModel.fireTableDataChanged();
    }

    private AddParameterAction addParameterAction = new AddParameterAction();

    public void setLinkedParameter(LinkedParameter linkedParameter) {
        parameter = linkedParameter.getArgumentParameter();
    }

    public void setDependentParameterList(List<Parameter> dependentParameterList) {
        this.dependentParameterList = dependentParameterList;
    }

    public void setCompatibleParameterList(List<Parameter> compatibleParameterList) {
        this.compatibleParameterList = compatibleParameterList;
    }

    public class AddParameterAction extends AbstractAction {

        public AddParameterAction() {
            super("Add parameter");
        }

        public void actionPerformed(ActionEvent ae) {
            addParameter();
        }
    }

    AbstractAction removeParameterAction = new AbstractAction() {
        public void actionPerformed(ActionEvent ae) {
            removeSelectedParameters();
        }
    };


    class ParametersTableModel extends AbstractTableModel {

        private static final long serialVersionUID = -6707994233020715574L;
        String[] columnNames = {"Parameter"};

        public ParametersTableModel() {
        }

        public int getColumnCount() {
            return columnNames.length;
        }

        public int getRowCount() {
            if (dependentParameterList == null) {
                return 0;
            }
            return dependentParameterList.size();
        }

        public Object getValueAt(int row, int col) {
            switch (col) {
                case 0:
                    return dependentParameterList.get(row);
            }
            return null;
        }

        public String getColumnName(int column) {
            return columnNames[column];
        }

        public Class getColumnClass(int c) {
            return getValueAt(0, c).getClass();
        }

        public String toString() {
            StringBuffer buffer = new StringBuffer();

            buffer.append(getColumnName(0));
            for (int j = 1; j < getColumnCount(); j++) {
                buffer.append("\t");
                buffer.append(getColumnName(j));
            }
            buffer.append("\n");

            for (int i = 0; i < getRowCount(); i++) {
                buffer.append(getValueAt(i, 0));
                for (int j = 1; j < getColumnCount(); j++) {
                    buffer.append("\t");
                    buffer.append(getValueAt(i, j));
                }
                buffer.append("\n");
            }

            return buffer.toString();
        }
    }


}