package Common.Visual;
import Common.CommonConstants;
import Common.Database.Objects.DBObject;
import Common.Database.Objects.Grid.TableVisualData;
import Common.Database.Tables.DBTable;
import Common.Database.Tables.DataSet;
import Common.Database.Tables.FKBehaviour;
import Common.MainModule_;
import Common.Passes.PassCode_;
import Common.Utils.TextLog;
import Common.Utils.Utils_;
import Common.Visual.Menus.DataMenuBar;
import Common.Visual.Menus.TableMenu;
import Common.Visual.Tables.*;
import Common.Visual.Tables.Grid.GridAnchestor;
import Common.Visual.Windows.Dialog.DBObjectDialog;

import javax.swing.*;
import javax.swing.table.TableColumn;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Collections;
import java.util.Comparator;
import java.util.Vector;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public abstract class DataSetControlForm<D extends DBObject> extends ControlForm<DataTable> {
    protected DataSet<?, D> dataSource; //источник данных
    protected D current = null; //заменить все обращения к мейн модулю.
    protected DataMenuBar bar = null; //верхняя панель меню
    protected int current_row_i; //индекс текущей строки.
    protected boolean events_on = true;
    protected String colNamesAndSizes = "";
    protected Vector<DBObjectFilter_<D>> allFilters = new Vector<>();
    Vector<ColumnInfo<D>> columns = new Vector<>(); //информация о столбцах и их оформлении
    MatchesCounter counter_ui = null;
    //--
    Object savedCurrentKey = null;
    Vector<Object> savedSelectedKeys = new Vector<>();
    public DataSetControlForm(DataSet<?, D> dataSource_in, JPanel mountPanel_in) {
        super(DataTable.class, mountPanel_in);
        dataSource = dataSource_in;
        //--
        columns.clear();
        columns.add(createPKColumn());
        if (hasCheckBox())
            columns.add(createCheckBoxColummn());
        createColumns();
        //--
        createFilters();
        //--
        if (hasMenuBar()) {
            try {
                if (!MainModule_.instance.getUI().menuBars.containsKey(dataSource.getClass())) {
                    bar = createMenuBar();
                    if (hasCheckBox())
                        bar.createSelectionButtons(dataSource);
                    MainModule_.instance.getUI().menuBars.put(dataSource.getClass(), bar);
                } else {
                    bar = MainModule_.instance.getUI().menuBars.get(dataSource.getClass());
                    bar.setDataSource(dataSource);
                }
                mountPanel.add(bar, BorderLayout.NORTH);
                //--
                counter_ui = (count -> bar.countLabel.setText(String.valueOf(count)));
                //--фильтры всегда в конец бара.
                //  bar.add(new JSeparator());
                for (FilterFlag filter : getFilters(FilterFlag.class)) {
                    bar.add(filter.getControl());
                }
                for (JMenu filter : getFilters(DataSetFiltersMenu.class)) {
                    bar.addMenus(filter);
                }
                //------------------------
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }
    //--
    public DataMenuBar getMenuBar() {
        return bar;
    }
    protected boolean isPKVisible() {
        return true;
    }
    protected ColumnInfo<D> createPKColumn() {
        return new ColumnInfo<D>(dataSource.getPKName()) {
            @Override
            public Object getFieldAt(D object) {
                return object.getPK();
            }
            @Override
            public boolean isVisible() {
                return isPKVisible();
            }
        };
    }
    protected boolean hasCheckBox() {
        return true;
    }
    protected ColumnInfo<D> createCheckBoxColummn() {
        return new ColumnInfo<D>("") {
            @Override
            public Object getFieldAt(D object) {
                return object.isSelected();
            }
            @Override
            public int getMinWidth() {
                return 25;
            }
            @Override
            public int getMaxWidth() {
                return 25;
            }
            @Override
            public Class getRendererClass() {
                return DBObjectSelectionRenderer.class;
            }
            @Override
            public Class getEditorClass() {
                return DBObjectSelector.class;
            }
        };
    }
    <M> Vector<M> getFilters(Class<M> f) {
        Vector<M> res = new Vector<>();
        for (DBObjectFilter_ filter_ : allFilters) {
            //либо М, либо наследует от М
            if (filter_.getClass().equals(f) || filter_.getClass().getSuperclass().equals(f)) {
                res.add((M) filter_);
            }
        }
        return res;
    }
    protected final void AddFilters(DBObjectFilter_<D>... new_filters) {
        Collections.addAll(allFilters, new_filters);
    }
    protected final void AddColumns(ColumnInfo<D>... new_columns) {
        Collections.addAll(columns, new_columns);
    }
    public ColumnInfo<D> getColumnInfo(int i) {
        return columns.get(i);
    }
    //--
    void SaveColumns() {
        if (MainModule_.instance.getDb() != null) {
            try {
                if (needsCurrent()) {
                    Vector<String> widths = IntStream.range(0, columns.size()).mapToObj(i -> String.valueOf(control.getColumnModel().getColumn(i).getWidth())).collect(Collectors.toCollection(Vector::new));
                    String packed = String.join("|", widths);
                    TableVisualData tableVisualData;
                    if (MainModule_.instance.getDb().tablesVisualData.containsKey(getCurrentName())) {
                        tableVisualData = MainModule_.instance.getDb().tablesVisualData.get(getCurrentName());
                    } else {
                        tableVisualData = new TableVisualData(getCurrentName());
                        MainModule_.instance.getDb().Insert(tableVisualData);
                    }
                    tableVisualData.sizes = packed;
                    MainModule_.instance.getDb().Update(tableVisualData);
                }
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }
    Vector<String> getHeaders() {
        return columns.stream().map(ColumnInfo::getName).collect(Collectors.toCollection(Vector::new));
    }
    protected Comparator<D> getDefaultComparator() {
        return null;
    }
    Vector<Object> getVisibleKeys() {
        Comparator comparator = getDefaultComparator();
        Vector<Object> res_keys = new Vector<>();
        if (comparator == null) {
            for (D object : dataSource.Data.values()) {
                if (isObjectVisible(object))
                    res_keys.add(object.getPK());
            }
        } else {
            Vector<D> raw = new Vector<>();
            for (D object : dataSource.Data.values()) {
                if (isObjectVisible(object))
                    raw.add(object);
            }
            raw.sort(comparator);
            for (D object : raw)
                res_keys.add(object.getPK());
        }
        return res_keys;
    }
    @Override
    protected void createControl() {
        GridAnchestor table_data_model = new GridAnchestor(getHeaders(), getVisibleKeys()) {
            @SuppressWarnings("unchecked")
            @Override
            public Object getValueAt(int rowIndex, int columnIndex) {
                Object key = data.get(rowIndex);
                D object = dataSource.get((key));
                return columns.get(columnIndex).getFieldAt(object);
            }
            @Override
            public boolean isCellEditable(int row, int col) {
                return columns.get(col).isEditable();
            }
            //------------------------------------------------------------------------------------
            @Override
            public void setValueAt(Object value, int row, int col) {
                fireTableCellUpdated(row, col);
            }
        };
        control = new DataTable(table_data_model) {
            @Override
            public TableMenu CreateMenu() {
                return new TableMenu(this);
            }
            //строго говоря эта штука нужна только для рендереров и едиторов клеток.
            @Override
            public DBObject getRowObject(int rowIndex) {
                //вот так делать НЕЛЬЗЯ. модель только для внутреннего пользования
                // Object key = table_data_model.data.get(rowIndex);
                //из таблицы можно пользоваться только getValueAt
                //иначе сортировка не будет работать.
                Object key = getValueAt(rowIndex, 0);
                return dataSource.get(key);
            }
            //-----------------------------NEW-------------------------------------
            @Override
            public void CorrectColumnsSizes() {
                if ((MainModule_.instance.getDb() != null)
                        && needsCurrent()
                        && MainModule_.instance.getDb().tablesVisualData.containsKey(getCurrentName())) {
                    if (!getColumnsProfile().equalsIgnoreCase(colNamesAndSizes)) {
                        TableVisualData grid = MainModule_.instance.getDb().tablesVisualData.get(getCurrentName());
                        String[] data = grid.sizes.split("\\|");
                        for (int i = 0; i < columns.size(); ++i) {
                            if (i <= (data.length - 1)) {
                                int width = Integer.parseInt(data[i]);
                                getColumnModel().getColumn(i).setPreferredWidth(width);
                                getColumnModel().getColumn(i).setWidth(width);
                            }
                        }
                    }
                } else
                    super.CorrectColumnsSizes(); //обычный авторазмер.
            }
            public String getColumnsProfile() {
                String res = "";
                for (int i = 0; i < getColumnModel().getColumnCount(); i++) {
                    if (i > 0) res += ",";
                    TableColumn column = getColumnModel().getColumn(i);
                    res += column.getHeaderValue();
                    res += ":";
                    res += column.getWidth();
                }
                return res;
            }
            @Override
            public void Init() {
                for (int i = 0; i < columns.size(); i++) {
                    ColumnInfo columnInfo = columns.get(i);
                    if (columnInfo.isVisible()) {
                        if (columnInfo.hasRenderer())
                            getColumnModel().getColumn(i).setCellRenderer(MainModule_.instance.getUI().getTableRenderer(columnInfo.getRendererClass()));
                        if (columnInfo.hasEditor())
                            getColumnModel().getColumn(i).setCellEditor(MainModule_.instance.getUI().getTableEditor(columnInfo.getEditorClass()));
                        if (columnInfo.hasMaxWidth())
                            getColumnModel().getColumn((i)).setMaxWidth(columnInfo.getMaxWidth());
                        if (columnInfo.hasMinWidth())
                            getColumnModel().getColumn((i)).setMinWidth(columnInfo.getMinWidth());
                    } else {
                        getColumnModel().getColumn(i).setMinWidth(0);
                        getColumnModel().getColumn(i).setMaxWidth(0);
                    }
                }
                //обновление в БД при ручном изменении размера столбиков.--------->>
                getTableHeader().addMouseListener(new MouseAdapter() {
                    public void mouseReleased(MouseEvent arg0) {
                        String new_colNamesAndSizes = getColumnsProfile();
                        // check if changed, if yes, persist...
                        if (!colNamesAndSizes.equals(new_colNamesAndSizes)) {
                            colNamesAndSizes = new_colNamesAndSizes;
                            SaveColumns();
                        }
                    }
                });
                //------------------------->>
            }
        };
        if (needsCurrent()) {
            current_row_i = CommonConstants.Nan;
            ListSelectionModel selModel = control.getSelectionModel();
            selModel.addListSelectionListener(e -> {
                int row = control.getSelectedRow();
                if ((row >= 0)) {
                    if (row != current_row_i) {
                        current_row_i = row;
                        setCurrent((D) control.getRowObject(row));
                        if (events_on) {
                            try {
                                ShowCurrentObject();
                            } catch (Exception ex) {
                                Utils_.MainLog.PrintException(ex);
                            }
                        }
                    }
                } else {
                    current_row_i = CommonConstants.Nan;
                    dropCurrent();
                    if (events_on) {
                        try {
                            ShowNoCurrentObject();
                        } catch (Exception ex) {
                            Utils_.MainLog.PrintException(ex);
                        }
                    }
                }
            });
            //двойной клик мыши.------------------------------------------------------
            control.addMouseListener(new MouseAdapter() {
                @Override
                public void mouseClicked(MouseEvent e) {
                    if ((e.getClickCount() == 2) && (getCurrent() != null)) {
                        try {
                            MouseAction2();
                        } catch (Exception ex) {
                            Utils_.MainLog.PrintException(ex);
                        }
                    }
                }
            });
            control.addKeyListener(new KeyAdapter() {
                                       @Override
                                       public void keyPressed(KeyEvent e) {
                                           switch (e.getKeyCode()) {
                                               case KeyEvent.VK_DELETE:
                                                   if (getDeletePassCode() != null) {
                                                       MainModule_.instance.getPass(getDeletePassCode()).Do();
                                                   }
                                                   break;
                                               case KeyEvent.VK_ENTER:
                                                   try {
                                                       MouseAction2();
                                                   } catch (Exception ex) {
                                                       Utils_.MainLog.PrintException(ex);
                                                   }
                                                   break;
                                           }
                                       }
                                   }
            );
            //----------------------------------------------------------------------------
            //при переотображении таблицы скидываем текущий объект!!
            dropCurrent();
            try {
                ShowNoCurrentObject();
            } catch (Exception e) {
                Utils_.MainLog.PrintException(e);
            }
        }
        for (HeaderTextFilter filter : getFilters(HeaderTextFilter.class))
            filter.Mount(getControl());
    }
    protected DataMenuBar createMenuBar() {
        return new DataMenuBar(dataSource.getPluralDescription());
    }
    protected void createFilters() {
    }
    protected abstract void createColumns();
    boolean ApplyFilters(D object) {
        for (DBObjectFilter_ filterInterface : allFilters) {
            if (!filterInterface.Validate(object))
                return false;
        }
        return true;
    }
    @Override
    protected void redrawControl() {
        control.CorrectSizes();
    }
    protected void ShowCurrentObject() throws Exception {
        if (dataSource instanceof DBTable) {
            DBTable table = (DBTable) dataSource;
            for (Class dep : table.getFKDependencies().keySet()) {
                FKBehaviour behaviour = table.getFKDependencies().get(dep);
                switch (behaviour.ui) {
                    case ACTIVE:
                        table.getDb().getTable(dep).ShowUI();
                        break;
                    case PASSIVE:
                        break;
                }
            }
        }
    }
    protected void ShowNoCurrentObject() throws Exception {
        if (dataSource instanceof DBTable) {
            DBTable table = (DBTable) dataSource;
            for (Class dep : table.getFKDependencies().keySet()) {
                FKBehaviour behaviour = table.getFKDependencies().get(dep);
                switch (behaviour.ui) {
                    case ACTIVE:
                        table.getDb().getTable(dep).ClearUI();
                        break;
                    case PASSIVE:
                        break;
                }
            }
        }
    }
    protected void MouseAction2() throws Exception {
    }
    //-
    protected boolean hasMenuBar() {
        return true;
    }
    public void dropCurrent() {
        current = null;
    }
    @SuppressWarnings("unchecked")
    protected DBObjectDialog getDialog() {
        return null;
    }
    protected boolean isObjectEditable(D object) {
        return true;
    }
    //БАЗОВЫЙ ФУНКЦИОНАЛ ФОРМЫ
    @Override
    public void Show() {
        for (DBObjectFilter_ filter_ : allFilters) filter_.DropMatchesCount();
        super.Show();
        if (counter_ui != null) counter_ui.ShowMatchesCount(getRowCount());
        for (DBObjectFilter_ filter_ : allFilters) filter_.ShowMatchesCount();
    }
    public void Show(Object pk) {
        Show();
        SetCurrentByPK(pk);
    }
    @Override
    public void Clear() {
        super.Clear();
        if (counter_ui != null) counter_ui.ShowNoMatches();
    }
    public int getRowCount() {
        return control.getRowCount();
    }
    //ТЕКУЩИЕ И ВЫБРАННЫЕ ОБЪЕКТЫ
    public void SaveLastCurrent() {
        savedCurrentKey = null;
        savedSelectedKeys.clear();
        if (needsCurrent() && (getCurrent() != null)) {
            savedCurrentKey = getCurrent().getPK();
        }
        savedSelectedKeys = getAllSelectedKeys();
    }
    public void RestoreLastCurrent() {
        for (Object key : savedSelectedKeys) {
            if (dataSource.containsKey(key))
                dataSource.get(key).Select(true);
        }
        if ((savedCurrentKey != null) && (dataSource.containsKey(savedCurrentKey))) {
            SetCurrentByPK(savedCurrentKey);
        }
    }
    public boolean isObjectVisible(D object) {
        return ApplyFilters(object);
    }
    public int getSelectedCount() {
        return (int) dataSource.Data.values().stream().filter(d -> isObjectVisible(d) && d.isSelected()).count();
    }
    public Vector<D> getSelectedItems() {
        return dataSource.Data.values().stream().filter(d -> isObjectVisible(d) && d.isSelected()).collect(Collectors.toCollection(Vector::new));
    }
    public Vector<Object> getSelectedKeys() {
        return dataSource.Data.values().stream().filter(d -> isObjectVisible(d) && d.isSelected()).map(d -> d.getPK()).collect(Collectors.toCollection(Vector::new));
    }
    //в том числе и невидимые. нужно для сохранения галок при перезакачке бд.
    public Vector<Object> getAllSelectedKeys() {
        return dataSource.Data.values().stream().filter(DBObject::isSelected).map(d -> d.getPK()).collect(Collectors.toCollection(Vector::new));
    }
    public boolean CheckCurrent(TextLog log) {
        if (current == null) {
            log.Writeln_(dataSource.getSingleDescription() + " не выбран(а)");
            return false;
        }
        return true;
    }
    public boolean matchCurrentID(int id_in) {
        return (current != null) && (((int) current.getPK()) == id_in);
    }
    public boolean CheckSelectedOrCurrent(TextLog log) {
        if ((getSelectedCount() == 0) && (!needsCurrent() || (getCurrent() == null))) {
            log.Writeln_(dataSource.getPluralDescription() + ":");
            log.Writeln_("Отсутствуют отмеченные объекты, или текущий объект!");
            return false;
        }
        return true;
    }
    protected boolean needsCurrent() {
        return true;
    } //нужно ли отслеживать текущий объект.
    protected String getCurrentName() {
        return dataSource.d.getSimpleName();
    }
    public D getCurrent() {
        return current;
    }
    public D setCurrent(D object) {
        return current = object;
    }
    public Vector<D> getSelectedOrCurrent() {
        Vector<D> res = new Vector<>();
        if (getSelectedCount() > 0)
            res = getSelectedItems();
        else {
            if (needsCurrent() && (getCurrent() != null)) {
                res.add(getCurrent());
            }
        }
        return res;
    }
    public Vector<Object> getSelectedOrCurrentKeys() {
        Vector<Object> res = new Vector<>();
        if (getSelectedCount() > 0)
            res = getSelectedKeys();
        else {
            if (needsCurrent() && (getCurrent() != null)) {
                res.add(getCurrent().getPK());
            }
        }
        return res;
    }
    public void SetCurrentByPK(Object pk) {
        if (isShown())
            control.SelectRowByPK(pk);
    }
    public void ClearSelection() {
        if (isShown()) control.clearSelection(); //строка сбросится сама. благодаря сбросу события выбора
    }
    public void SelectAll(boolean flag) {
        for (D object : dataSource.Data.values()) {
            if (isObjectVisible(object))
                object.Select(flag);
        }
        RedrawControl();
    }
    //ДИАЛОГИ
    public boolean ShowAddObjectDialog(D object) {
        return getDialog().ShowDialog(dataSource.getSingleDescription() + ": добавление", object);
    }
    public boolean ShowEditObjectDialog(D object) {
        DBObjectDialog dialog = getDialog();
        dialog.edit = true;
        dialog.SetEditLimits();
        String title = dataSource.getSingleDescription() + ": ";
        if (isObjectEditable(object)) {
            title += "редактирование";
        } else {
            title += "просмотр";
            dialog.SetReadonly();
            dialog.BlockButtons();
        }
        return dialog.ShowDialog(title, object);
    }
    public boolean ShowDeleteObjectDialog(D object) {
        return UI.Warning(dataSource.getSingleDescription() + " " + object.getBDialogName() + " будет удален(а)");
    }
    public boolean ShowDeleteObjectsDialog(int toDeleteCount) {
        return UI.Warning(dataSource.getPluralDescription() + " в количестве " + toDeleteCount + " будут удалены)");
    }
    public PassCode_ getDeletePassCode() {
        return null;
    }
    public Object getCurrentPK(Object nanValue) {
        return current == null ? nanValue : current.getPK();
    }
    public boolean canModifyCurrent(TextLog Log) {
        return CheckCurrent(Log);
    }
    public void SelectKeys(Vector<?> keys_in) {
        SelectAll(false);
        for (Object key_ : keys_in)
            dataSource.get(key_).Select(true);
        if (!keys_in.isEmpty())
            Show(keys_in.lastElement());
    }
}
