import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumn;
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Vector;

/**
 * Classe permettant la création de fenêtres pour la modification des propriétés des classes.
 * @author V.BOULANGER
 */
public class ClassPropertiesWindow extends JDialog {

    public static final int UPDATE_ATTR = 0;
    public static final int UPDATE_METH = 1;
    private static final int UPDATE_ALL = 2;

    private UmlDiagram _umlDiagram;
    private Class _class;
    private JTextField _nameField;
    private JTable _attributesTable;
    private JScrollPane _scrollAttributes;
    private String[] _attributesColumns = {"Name", "Access modifier", "Type", "Static", "Final", "Abstract", "Synchronised", "Volatile", "Transient"};
    private DefaultTableModel _attributeModel;
    private JTable _methodsTable;
    private JScrollPane _scrollMethods;
    private String[] _methodsColumns = {"C", "Name", "Access modifier", "Type", "Arguments", "Static", "Final", "Abstract", "Synchronised", "Volatile", "Transient"};
    private DefaultTableModel _methodModel;

    /**
     * Constructeur - Crée une instance de la fenêtre de propriétés de classe à partir d'un diagramme et de la classe à modifier.
     * @param umlDiagram Le diagramme qui contient la classe.
     * @param a La classe à modifier.
     */
    public ClassPropertiesWindow(UmlDiagram umlDiagram, Class a){
        _umlDiagram = umlDiagram;
        _class = a;

        //Paramétrage de la fenêtre
        this.setTitle("Edit properties - " + a.getName());
        this.setModal(true);
        this.setSize(new Dimension(800, 375));
        Image img = null;
        try { img = ImageIO.read(AppThinker.class.getResource("img/logoAppThinker.png")); } catch (Exception ex) { }
        this.setIconImage(img);
        this.setResizable(false);
        this.setLocationRelativeTo(null);
        this.setLayout(new BorderLayout());

        //Espace général de la fenêtre
        JPanel generalPanel = new JPanel();
        generalPanel.setLayout(new BoxLayout(generalPanel, BoxLayout.Y_AXIS));

        //Espace de modification du nom
        JPanel namePan = new JPanel();
        namePan.setLayout(new BoxLayout(namePan, BoxLayout.X_AXIS));
        JLabel nameLbl = new JLabel("Name : ");
        _nameField = new JTextField();
        _nameField.setPreferredSize(new Dimension(300, 20));
        _nameField.setText(a.getName());
        namePan.add(nameLbl);
        namePan.add(_nameField);
        generalPanel.add(namePan);

        //Radio bouton pour définir la classe principale
        JRadioButton mainRadio = new JRadioButton("This is the main class");
        mainRadio.setToolTipText("The main class is the entry point of the application. It appears in red on the diagram.");
        mainRadio.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                defineMainClass(a);
                refreshGraphics();
            }
        });
        ButtonGroup bg = new ButtonGroup();
        bg.add(mainRadio);
        if(_umlDiagram.getMainClass() == a) mainRadio.setSelected(true);
        else mainRadio.setSelected(false);

        generalPanel.add(mainRadio);


        JLabel attrLbl = new JLabel("Edit attributes");
        generalPanel.add(attrLbl);

        //Espace de modification des attributs
        JPanel attributesPan = new JPanel();
        attributesPan.setLayout(new BoxLayout(attributesPan, BoxLayout.X_AXIS));

        _attributeModel = new DefaultTableModel(_attributesColumns, 0);
        _attributesTable = new JTable(_attributeModel);

        _scrollAttributes = new JScrollPane(_attributesTable);
        _scrollAttributes.setPreferredSize(new Dimension(350, 100));
        attributesPan.add(_scrollAttributes);

        JPanel attributesTableModifier = new JPanel();
        attributesTableModifier.setLayout(new BoxLayout(attributesTableModifier, BoxLayout.Y_AXIS));
        JButton addAttribute = new JButton(" + ");
        addAttribute.setToolTipText("Add a new attribute.");
        addAttribute.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                addAttribute();
            }
        });
        attributesTableModifier.add(addAttribute);
        JButton removeAttribute = new JButton(" -  ");
        removeAttribute.setToolTipText("Remove the selected attribute.");
        removeAttribute.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                removeAttribute();
            }
        });
        attributesTableModifier.add(removeAttribute);
        JButton upAttribute = new JButton("▲");
        upAttribute.setToolTipText("Go up the selected attribute.");
        upAttribute.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                int selected = _attributesTable.getSelectedRow();
                if(_class.upAttribute(selected)){
                    refreshGraphics();
                    listAttributes();
                    _attributesTable.setRowSelectionInterval(selected - 1, selected - 1);
                }
            }
        });
        attributesTableModifier.add(upAttribute);
        JButton downAttribute = new JButton("▼");
        downAttribute.setToolTipText("Go down the selected attribute.");
        downAttribute.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                int selected = _attributesTable.getSelectedRow();
                if(_class.downAttribute(selected)){
                    refreshGraphics();
                    listAttributes();
                    _attributesTable.setRowSelectionInterval(selected + 1, selected + 1);
                }
            }
        });
        attributesTableModifier.add(downAttribute);
        attributesPan.add(attributesTableModifier);

        generalPanel.add(attributesPan);

        JLabel methLbl = new JLabel("Edit methods");
        generalPanel.add(methLbl);

        //Espace de modification des méthodes
        JPanel methodsPan = new JPanel();
        methodsPan.setLayout(new BoxLayout(methodsPan, BoxLayout.X_AXIS));

        _methodModel = new DefaultTableModel(_methodsColumns, 0);
        _methodsTable = new JTable(_methodModel);

        _methodsTable.addMouseListener(new MouseAdapter() {
            public void mouseClicked(MouseEvent e) {
                Point p = e.getPoint();
                int col = _methodsTable.columnAtPoint(p);
                int row = _methodsTable.rowAtPoint(p);
                if (col == 4) openArgumentsWindow(_class.getMethods().get(row));
            }
        });

        _scrollMethods = new JScrollPane(_methodsTable);
        _scrollMethods.setPreferredSize(new Dimension(350, 100));
        methodsPan.add(_scrollMethods);

        JPanel methodsTableModifier = new JPanel();
        methodsTableModifier.setLayout(new BoxLayout(methodsTableModifier, BoxLayout.Y_AXIS));
        JButton addMethod = new JButton(" + ");
        addMethod.setToolTipText("Add a new method.");
        addMethod.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                addMethod();
            }
        });
        methodsTableModifier.add(addMethod);

        JButton removeMethod = new JButton(" -  ");
        removeMethod.setToolTipText("Remove the selected method.");
        removeMethod.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                removeMethod();
            }
        });
        methodsTableModifier.add(removeMethod);
        JButton upMethod = new JButton("▲");
        upMethod.setToolTipText("Go up the selected method.");
        upMethod.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                int selected = _methodsTable.getSelectedRow();
                if(_class.upMethod(selected)){
                    refreshGraphics();
                    listMethods();
                    _methodsTable.setRowSelectionInterval(selected - 1, selected - 1);
                }
            }
        });
        methodsTableModifier.add(upMethod);
        JButton downMethod = new JButton("▼");
        downMethod.setToolTipText("Go down the selected method.");
        downMethod.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                int selected = _methodsTable.getSelectedRow();
                if(_class.downMethod(selected)){
                    refreshGraphics();
                    listMethods();
                    _methodsTable.setRowSelectionInterval(selected + 1, selected + 1);
                }
            }
        });
        methodsTableModifier.add(downMethod);
        JButton overloadMethod = new JButton(" O ");
        overloadMethod.setToolTipText("Overload this method.");
        overloadMethod.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                overloadMethod();
            }
        });
        methodsTableModifier.add(overloadMethod);

        methodsPan.add(methodsTableModifier);

        generalPanel.add(methodsPan);

        this.add(generalPanel, BorderLayout.CENTER);

        //Import des attributs et méthodes dans les tableaux
        this.listAttributes();
        this.listMethods();

        this.addWindowListener(new WindowListener() {
            @Override
            public void windowOpened(WindowEvent e) {

            }

            //On enregistre à la fermeture de la fenêtre
            @Override
            public void windowClosing(WindowEvent e) {
                saveClass();
                refreshGraphics();
                dispose();
            }

            @Override
            public void windowClosed(WindowEvent e) {

            }

            @Override
            public void windowIconified(WindowEvent e) {

            }

            @Override
            public void windowDeiconified(WindowEvent e) {

            }

            @Override
            public void windowActivated(WindowEvent e) {

            }

            @Override
            public void windowDeactivated(WindowEvent e) {

            }
        });

        this.setVisible(true);
    }

    public void defineMainClass(Class a){
        _umlDiagram.setMainClass(a);
        refreshGraphics();
    }

    /**
     * Ouvre une fenêtre d'édition des arguments pour la méthode sélectionnée
     * @param m La méthode dans laquelle seront modifiés les arguments.
     */
    public void openArgumentsWindow(Method m){
        ArgumentsPropertiesWindow argsWin = new ArgumentsPropertiesWindow(this, _umlDiagram, m);
    }

    /**
     * Récupère la classe en cours d'édition.
     * @return La classe en cours d'édition.
     */
    public Class getEditingClass(){
        return _class;
    }

    /**
     * Ajoute un attribut à la classe et rafraîchit la liste des attributs.
     */
    public void addAttribute(){
        this.saveAttributes();
        _class.addAttribute(new Attribute());
        this.listAttributes();
        refreshGraphics();
    }

    /**
     * Retire un attribut à la classe et rafraîchit la liste des attributs.
     */
    public void removeAttribute(){
        int i = _attributesTable.getSelectedRow();
        if(i != -1){
            this.saveAttributes();
            _class.removeAttribute(i);
            this.listAttributes();
            refreshGraphics();
        }
    }

    /**
     * Ajoute une méthode à la classe et rafraîchit la liste des méthodes.
     */
    public void addMethod(){
        this.saveMethods();
        _class.addMethod(new Method());
        this.listMethods();
        refreshGraphics();
    }

    /**
     * Ajoute une méthode à la classe et rafraîchit la liste des méthodes.
     * @param index Le rang d'insertion de la méthode.
     * @param m La méthode à insérer.
     */
    public void addMethod(int index, Method m){
        this.saveMethods();
        _class.addMethod(index, m);
        this.listMethods();
        refreshGraphics();
    }

    /**
     * Retire une méthode à la classe et rafraîchit la liste des méthodes.
     */
    public void removeMethod(){
        int i = _methodsTable.getSelectedRow();
        if(i != -1){
            this.saveMethods();
            _class.removeMethod(i);
            this.listMethods();
            refreshGraphics();
        }
    }

    /**
     * On surcharge la méthode avec un argument en plus qui peut être modifié par la suite.
     */
    public void overloadMethod(){
        int index = _methodsTable.getSelectedRow();
        if(index != -1){
            Method m = _class.getMethods().get(index);
            java.util.List<Argument> args = new ArrayList<Argument>();
            //On ajoute un argument de plus que la méthode à surcharger
            args.addAll(m.getArguments());
            args.add(new Argument());
            Method m1 = new Method(m.getAccess(), m.getType(), m.getName(), args, m.isConstructor(), m.isStatic(), m.isFinal(), m.isAbstract(), m.isSynchronized(), m.isVolatile(), m.isTransient());
            this.addMethod(index + 1, m1);
            this.openArgumentsWindow(m1);
        }
    }

    /**
     * Affiche la liste des attributs de la classe dans le tableau
     */
    public void listAttributes(){
        //Import des attributs dans la table
        _attributeModel.setDataVector((Object[][]) null, _attributesColumns);
        for(Attribute attr : _class.getAttributes()){
            String access = (attr.getAccess() == "+") ? "PUBLIC" : (attr.getAccess() == "#") ? "PROTECTED" : "PRIVATE";
            _attributeModel.addRow(new Object[]{attr.getName(), access, attr.getType(), attr.isStatic(), attr.isFinal(), attr.isAbstract(), attr.isSynchronized(), attr.isVolatile(), attr.isTransient()});
        }
        //On ajoute les contrôles pour chaque colonne
        String[] access = { "PRIVATE", "PUBLIC", "PROTECTED" };
        JComboBox accessComboBox = new JComboBox(access);
        accessComboBox.setEditable(true);
        _attributesTable.getColumn(_attributesColumns[1]).setCellEditor(new DefaultCellEditor(accessComboBox));
        String[] types = { "boolean", "char", "byte", "short", "int", "long", "float", "double", "String" };
        JComboBox typeComboBox = new JComboBox(types);
        typeComboBox.setEditable(true);
        _attributesTable.getColumn(_attributesColumns[2]).setCellEditor(new DefaultCellEditor(typeComboBox));
        _attributesTable.getColumn(_attributesColumns[3]).setCellEditor(new DefaultCellEditor(new JCheckBox()));
        _attributesTable.getColumn(_attributesColumns[4]).setCellEditor(new DefaultCellEditor(new JCheckBox()));
        _attributesTable.getColumn(_attributesColumns[5]).setCellEditor(new DefaultCellEditor(new JCheckBox()));
        _attributesTable.getColumn(_attributesColumns[6]).setCellEditor(new DefaultCellEditor(new JCheckBox()));
        _attributesTable.getColumn(_attributesColumns[7]).setCellEditor(new DefaultCellEditor(new JCheckBox()));
        _attributesTable.getColumn(_attributesColumns[8]).setCellEditor(new DefaultCellEditor(new JCheckBox()));
    }

    /**
     * Affiche la liste des méthodes de la classe dans le tableau.
     */
    public void listMethods(){
        //Import des méthodes dans la table
        _methodModel.setDataVector((Object[][]) null, _methodsColumns);
        for(Method meth : _class.getMethods()){
            String access = (meth.getAccess() == "-") ? "PRIVATE" : (meth.getAccess() == "#") ? "PROTECTED" : "PUBLIC";
            _methodModel.addRow(new Object[]{meth.isConstructor(), meth.getName(), access, meth.getType(), "[EDIT]", meth.isStatic(), meth.isFinal(), meth.isAbstract(), meth.isSynchronized(), meth.isVolatile(), meth.isTransient()});
        }

        //On ajoute les contrôles pour chaque colonne
        String[] access = { "PRIVATE", "PUBLIC", "PROTECTED" };
        JComboBox accessComboBox = new JComboBox(access);
        accessComboBox.setEditable(true);
        _methodsTable.getColumn(_methodsColumns[0]).setCellEditor(new DefaultCellEditor(new JCheckBox()));
        _methodsTable.getColumn(_methodsColumns[2]).setCellEditor(new DefaultCellEditor(accessComboBox));
        String[] types = { "boolean", "char", "byte", "short", "int", "long", "float", "double", "String" };
        JComboBox typeComboBox = new JComboBox(types);
        typeComboBox.setEditable(true);
        _methodsTable.getColumn(_methodsColumns[3]).setCellEditor(new DefaultCellEditor(typeComboBox));
        JTextField argsField = new JTextField();
        argsField.setEnabled(false);
        _methodsTable.getColumn(_methodsColumns[4]).setCellEditor(new DefaultCellEditor(argsField));
        _methodsTable.getColumn(_methodsColumns[5]).setCellEditor(new DefaultCellEditor(new JCheckBox()));
        _methodsTable.getColumn(_methodsColumns[6]).setCellEditor(new DefaultCellEditor(new JCheckBox()));
        _methodsTable.getColumn(_methodsColumns[7]).setCellEditor(new DefaultCellEditor(new JCheckBox()));
        _methodsTable.getColumn(_methodsColumns[8]).setCellEditor(new DefaultCellEditor(new JCheckBox()));
        _methodsTable.getColumn(_methodsColumns[9]).setCellEditor(new DefaultCellEditor(new JCheckBox()));
        _methodsTable.getColumn(_methodsColumns[10]).setCellEditor(new DefaultCellEditor(new JCheckBox()));
    }

    /**
     * Sauvegarde les attributs pour la classe en cours.
     */
    public void saveAttributes(){
        //Enregistrement des attributs
        _attributesTable.editCellAt(0,0);
        for(int i = 0; i <= _attributeModel.getRowCount()-1; i++){
            Vector vect = (Vector)_attributeModel.getDataVector().elementAt(i);
            String access = vect.get(1).toString();
            _class.getAttributes().get(i).setName(vect.get(0).toString());
            _class.getAttributes().get(i).setAccess((access == "PUBLIC") ? Attribute.PUBLIC : (access == "PROTECTED") ? Attribute.PROTECTED : Attribute.PRIVATE);
            _class.getAttributes().get(i).setType(vect.get(2).toString());
            _class.getAttributes().get(i).setStatic((boolean) vect.get(3));
            _class.getAttributes().get(i).setFinal((boolean) vect.get(4));
            _class.getAttributes().get(i).setAbstract((boolean) vect.get(5));
            _class.getAttributes().get(i).setSynchronized((boolean) vect.get(6));
            _class.getAttributes().get(i).setVolatile((boolean) vect.get(7));
            _class.getAttributes().get(i).setTransient((boolean) vect.get(8));
        }
    }

    /**
     * Sauvegarde les méthodes pour la classe en cours.
     */
    public void saveMethods(){
        //Enregistrement des méthodes
        _methodsTable.editCellAt(0,0);
        for(int i = 0; i <= _methodModel.getRowCount()-1; i++){
            Vector vect = (Vector)_methodModel.getDataVector().elementAt(i);
            String access = vect.get(2).toString();
            _class.getMethods().get(i).setConstructor((boolean)vect.get(0));
            _class.getMethods().get(i).setName(vect.get(1).toString());
            _class.getMethods().get(i).setAccess((access == "PRIVATE") ? Method.PRIVATE : (access == "PROTECTED") ? Method.PROTECTED : Method.PUBLIC);
            _class.getMethods().get(i).setType(vect.get(3).toString());
            _class.getMethods().get(i).setStatic((boolean) vect.get(5));
            _class.getMethods().get(i).setFinal((boolean) vect.get(6));
            _class.getMethods().get(i).setAbstract((boolean) vect.get(7));
            _class.getMethods().get(i).setSynchronized((boolean) vect.get(8));
            _class.getMethods().get(i).setVolatile((boolean) vect.get(9));
            _class.getMethods().get(i).setTransient((boolean) vect.get(10));
        }
    }

    /**
     * Sauvegarde l'ensemble de la classe.
     */
    public void saveClass(){
        //Changement du nom de la classe
        _class.setName(_nameField.getText());
        //Sauvegarde des attributs et méthodes
        saveAttributes();
        saveMethods();
    }

    /**
     * Rafraîchit la classe graphiquement.
     */
    public void refreshGraphics(){
        this._class.computeMinSize();
        _umlDiagram.repaint();
    }
}
