/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package br.com.ctecinf.orm.swing;

import br.com.ctecinf.combobox.ComboBoxModel;
import br.com.ctecinf.Config;
import br.com.ctecinf.Daruma;
import br.com.ctecinf.orm.Column;
import br.com.ctecinf.orm.Model;
import br.com.ctecinf.Database;
import br.com.ctecinf.Utils;
import br.com.ctecinf.orm.NullModel;
import br.com.ctecinf.autocomplete.AutoCompleteField;
import br.com.ctecinf.combobox.ComboBox;
import br.com.ctecinf.swing.Fields;
import br.com.ctecinf.swing.GridPanel;
import br.com.ctecinf.swing.Image;
import br.com.ctecinf.swing.OptionPane;
import br.com.ctecinf.swing.PleaseWaitDialog;
import br.com.ctecinf.text.PasswordFormatter;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.Map;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFormattedTextField;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JToolBar;
import javax.swing.border.BevelBorder;
import javax.swing.plaf.metal.MetalLookAndFeel;

/**
 *
 * @author Cássio Conceição
 * @param <T>
 * @since 03/07/2019
 * @version 201907
 * @see http://ctecinf.com.br/
 */
public class FormPanelController<T extends Model> extends JPanel implements Serializable {

    private Fields fields;
    private JComponent pane;
    private GridPanel lastPanel;

    private T controller;

    private int row;
    private int colIndex;
    private JToolBar buttonBar;
    private JFrame frame;

    /**
     * Cria formulário sem abas
     *
     * @param controller
     */
    public FormPanelController(T controller) {
        this(null, controller, 1);
    }

    /**
     * Cria formulário sem abas
     *
     * @param controller
     * @param numCols
     */
    public FormPanelController(T controller, int numCols) {
        this(null, controller, numCols);
    }

    /**
     * Cria formulário com abas
     *
     * @param title
     * @param controller
     */
    public FormPanelController(String title, T controller) {
        this(title, controller, 1);
    }

    /**
     * Cria formulário com abas
     *
     * @param title
     * @param controller
     * @param numCols
     */
    public FormPanelController(String title, T controller, int numCols) {

        super(new BorderLayout());

        if (title == null) {
            this.pane = new GridPanel(numCols);
            lastPanel = (GridPanel) this.pane;
        } else {
            this.pane = new JTabbedPane();
            this.addTab(title, numCols);
        }

        this.fields = new Fields();
        this.controller = controller;

        initUI();
    }

    private void initUI() {

        colIndex = 0;

        buttonBar = new JToolBar();
        buttonBar.setFloatable(false);
        buttonBar.setLayout(new FlowLayout(FlowLayout.RIGHT));

        addAction("Salvar", Image.parse(Image.SAVE), (ActionEvent ae) -> {

            new PleaseWaitDialog<Boolean>() {

                @Override
                public Boolean exec() throws Exception {
                    return FormPanelController.this.save(fields.getValues());
                }

                @Override
                public void end(Boolean result) {

                    if (result) {
                        OptionPane.success(controller.getClass().getSimpleName() + "[" + controller + "] salvo com sucesso.");
                    }
                }

            }.start();
        });

        addAction("Apagar", Image.parse(Image.TRASH), (ActionEvent ae) -> {

            if (OptionPane.confirm("Deseja realmente excluír registro?")) {

                new PleaseWaitDialog<Boolean>() {

                    @Override
                    public Boolean exec() throws Exception {
                        return FormPanelController.this.delete();
                    }

                    @Override
                    public void end(Boolean result) {

                        if (result) {

                            if (frame != null) {
                                frame.dispose();
                            }

                            OptionPane.success(controller.getClass().getSimpleName() + "[" + controller + "] apagado com sucesso.");
                        }
                    }
                }.start();
            }
        });

        //Implementar
        addAction("Imprimir", Image.parse(Image.PRINT), (ActionEvent ae) -> {

            new PleaseWaitDialog<Boolean>() {

                @Override
                public Boolean exec() throws Exception {
                    return FormPanelController.this.print();
                }

                @Override
                public void end(Boolean result) {
                    if (result) {
                        OptionPane.success("Impressão concluída.");
                    }
                }

            }.start();
        });

        add(buttonBar, BorderLayout.NORTH);

        if (pane instanceof JTabbedPane) {
            add(pane, BorderLayout.CENTER);
        } else {
            add(new JScrollPane(pane), BorderLayout.CENTER);
        }
    }

    /**
     * Carrega dados no formulário
     */
    public void setFieldsValues() {

        for (java.lang.reflect.Field field : controller.getClass().getDeclaredFields()) {

            String name = null;

            if (fields.getComponents().containsKey(field.getName())) {
                name = field.getName();
            }

            if (name != null) {

                field.setAccessible(true);

                try {
                    setValue(name, field.get(controller));
                } catch (IllegalArgumentException | IllegalAccessException ex) {
                    System.err.println(ex);
                }
            }
        }
    }

    public JComponent getForm() {
        return pane;
    }

    public Fields getFields() {
        return fields;
    }

    public <T extends JComponent> T getComponent(String name) {
        return fields.getComponent(name);
    }

    public void setValue(String name, Object value) {
        fields.setValue(name, value);
    }

    public Object getValue(String name) {
        return fields.getValue(name);
    }

    public Map<String, Object> getValues() {
        return fields.getValues();
    }

    public void cleanFields() {
        fields.cleanValues();
    }

    public T getController() {
        return controller;
    }

    public final void addTab(String title, int numColumns) {

        if (pane instanceof JTabbedPane) {

            GridPanel panel = new GridPanel(numColumns);
            panel.setEnterForward(isEnterForward());

            lastPanel = panel;

            ((JTabbedPane) pane).add(title, new JScrollPane(panel));

            row = 1;
            colIndex = 0;
        }
    }

    public void newRow() {
        row++;
        colIndex = 0;
    }

    public void addLine(Object field) {
        GridPanel panel = lastPanel;
        addColumn(field, 0, panel.getNumColumns());
    }

    public void addColumn(Object... field) {
        for (Object obj : field) {
            addColumn(obj, colIndex, 1);
        }
    }

    public void addColumn(Object field, int col) {
        addColumn(field, col, 1);
    }

    public void addColumn(Object field, int col, int numColsOcupped) {
        addColumn(field, col, numColsOcupped, true);
    }

    public void addColumn(Object field, int col, int numColsOcupped, boolean fill) {

        java.lang.reflect.Field classField = null;

        JComponent component = field instanceof JComponent ? (JComponent) field : null;

        try {

            if (component != null) {

                if (component.getName() != null) {
                    classField = controller.getClass().getDeclaredField(component.getName());
                }

                fields.put(component);

            } else if (field instanceof String) {
                classField = controller.getClass().getDeclaredField(field.toString());
            }

        } catch (NoSuchFieldException | SecurityException ex) {
            System.err.println(ex);
        }

        if (classField != null) {

            String label = Database.java2Database(classField.getName()).replace("_", " ").toUpperCase();
            boolean notNull = false;

            if (classField.isAnnotationPresent(Column.class)) {

                Column column = classField.getAnnotation(Column.class);

                if (!column.label().isEmpty()) {
                    label = column.label();
                }

                notNull = column.isNotNull();

                if (component == null) {

                    if (!column.join().isAssignableFrom(NullModel.class)) {

                        if (column.isAutoComplete()) {
                            try {
                                component = new AutoCompleteField(classField.getName(), ((Model) classField.getType().newInstance()).getAutoCompleteModel());
                            } catch (InstantiationException | IllegalAccessException ex) {
                                System.err.println(ex);
                            }
                        } else {
                            try {

                                ComboBoxModel model = ((Model) classField.getType().newInstance()).getComboBoxModel();
                                component = new ComboBox(model);
                                component.setName(classField.getName());

                            } catch (InstantiationException | IllegalAccessException ex) {
                                System.err.println(ex);
                            }
                        }

                        if (component != null) {
                            fields.put(component);
                        }

                    } else if (column.isCrypt()) {

                        JFormattedTextField c = new JFormattedTextField(new PasswordFormatter());
                        c.setName(classField.getName());

                        fields.put(c);

                    } else if (!column.mask().isEmpty()) {
                        fields.create(classField.getName(), null, column.mask());
                    } else if (column.defaultValues().length > 0) {
                        fields.create(classField.getName(), column.defaultValues());
                    } else {
                        fields.create(classField.getName(), column.type());
                    }

                    component = fields.getComponent(field.toString());
                }
            }

            if (component != null) {
                fields.setLabel(component.getName(), label, notNull);
            }
        }

        add(component, row, col, numColsOcupped, fill);
    }

    public int getRow() {
        return row;
    }

    public int getColumn() {
        return colIndex;
    }

    public void setRow(int row) {
        this.row = row;
    }

    public void addRequiredFieldLabel() {
        JLabel labelMsg = new JLabel("* Campo(s) obrigatório(s).");
        labelMsg.setForeground(Color.RED);
        addTitle(labelMsg);
    }

    public void addBreakLine() {
        addTitle("");
    }

    public void addTitle(String title) {

        JLabel label = new JLabel(title, JLabel.CENTER);
        label.setOpaque(true);
        label.setBackground(MetalLookAndFeel.getPrimaryControl());
        label.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createBevelBorder(BevelBorder.RAISED, MetalLookAndFeel.getFocusColor(), MetalLookAndFeel.getControlDarkShadow()), BorderFactory.createEmptyBorder(1, 1, 1, 1)));

        addTitle(label);
    }

    private void addTitle(JLabel label) {
        GridPanel panel = (GridPanel) lastPanel;
        add(label, row, 0, panel.getNumColumns(), true);
        row++;
        colIndex = 0;
    }

    public FormPanelController addAction(String label, ImageIcon icon, ActionListener action) {

        JButton button = new JButton(label, icon);
        button.addActionListener(action);

        buttonBar.add(button);

        return this;
    }

    public FormPanelController<T> getDefaultForm() {
        return getDefaultForm(null);
    }

    public FormPanelController<T> getDefaultForm(String title) {

        addRequiredFieldLabel();

        for (Field field : getController().getClass().getDeclaredFields()) {
            if (field.isAnnotationPresent(Column.class)) {
                addColumn(field.getName());
            }
        }

        if (title != null) {

            createFrame(title);

            addAction("Fechar", Image.parse(Image.CLOSE), (e) -> {
                getFrame().dispose();
            });
        }

        setFieldsValues();

        return this;
    }

    public JFrame getFrame() {
        return frame;
    }

    protected void setLabel(String fieldName, String label) {
        setLabel(fieldName, label, false);
    }

    protected void setLabel(String fieldName, String label, boolean notNull) {
        fields.setLabel(fieldName, label, notNull);
    }

    protected Object[] initValues(String columnName) {
        Object[] values = {};
        return values;
    }

    protected String getMask(String columnName) {
        return "";
    }

    protected boolean isEnterForward() {
        return false;
    }

    protected JFrame createFrame() {
        return createFrame(null);
    }

    protected JFrame createFrame(String title) {

        ImageIcon icon;

        if (!Config.get("logo.path").isEmpty()) {
            icon = new ImageIcon(new File(Config.get("logo.path")).getAbsolutePath());
        } else {
            icon = Image.parse(Image.LOGO, 16);
        }

        frame = new JFrame(title == null ? "" : title);
        frame.setIconImage(icon.getImage());
        frame.setLayout(new BorderLayout());
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        frame.add(this, BorderLayout.CENTER);
        frame.setMinimumSize(new Dimension(600, 400));
        frame.pack();
        frame.setLocationRelativeTo(null);

        return frame;
    }

    protected boolean save(Map<String, Object> params) throws Exception {

        controller.setValues(params);

        try {
            return controller.save() != null;
        } finally {
            if (frame != null) {
                frame.dispose();
            }
        }
    }

    protected boolean delete() throws Exception {
        controller.setValues(fields.getValues());
        return controller.delete();
    }

    protected boolean print() throws Exception {

        Daruma p = new Daruma();

        p.head();
        p.linhaCorte();

        p.tamanhoFonte(2);
        p.negrito("Dados cadastro");
        p.novaLinha();
        p.novaLinha();

        for (Field field : getController().getClass().getDeclaredFields()) {
            if (field.isAnnotationPresent(Column.class)) {
                field.setAccessible(true);
                Object value = field.get(getController());
                p.leftLine(Daruma.bold(Utils.ascii(getController().getLabel(field.getName()), false)) + ": " + (value == null ? "" : value));
            }
        }

        p.saltarLinhas(5);

        return true;
    }

    protected void add(JComponent c, int row, int col, int numColsOcup, boolean fill) {

        GridPanel panel = lastPanel;
        panel.add(c, col, row, numColsOcup, (1.00 / panel.getNumColumns()), fill);

        this.colIndex = col + numColsOcup;

        if (this.colIndex >= panel.getNumColumns()) {
            this.row++;
            this.colIndex = 0;
        }
    }
}
