/*****************************************************************
 *   Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 *
 *    https://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 ****************************************************************/

package org.apache.cayenne.modeler.editor.dbentity;

import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.util.EventObject;
import javax.swing.BorderFactory;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.JToolBar;

import com.jgoodies.forms.builder.DefaultFormBuilder;
import com.jgoodies.forms.layout.FormLayout;
import org.apache.cayenne.configuration.DataChannelDescriptor;
import org.apache.cayenne.exp.Expression;
import org.apache.cayenne.map.DbAttribute;
import org.apache.cayenne.map.DbEntity;
import org.apache.cayenne.map.event.EntityEvent;
import org.apache.cayenne.modeler.Application;
import org.apache.cayenne.modeler.ProjectController;
import org.apache.cayenne.modeler.action.ActionManager;
import org.apache.cayenne.modeler.action.CreateAttributeAction;
import org.apache.cayenne.modeler.action.CreateObjEntityFromDbAction;
import org.apache.cayenne.modeler.action.CreateRelationshipAction;
import org.apache.cayenne.modeler.action.DbEntityCounterpartAction;
import org.apache.cayenne.modeler.action.DbEntitySyncAction;
import org.apache.cayenne.modeler.editor.ExistingSelectionProcessor;
import org.apache.cayenne.modeler.event.DbEntityDisplayListener;
import org.apache.cayenne.modeler.event.EntityDisplayEvent;
import org.apache.cayenne.modeler.graph.action.ShowGraphEntityAction;
import org.apache.cayenne.modeler.util.ExpressionConvertor;
import org.apache.cayenne.modeler.util.TextAdapter;
import org.apache.cayenne.project.extension.info.ObjectInfo;
import org.apache.cayenne.util.Util;
import org.apache.cayenne.validation.ValidationException;

/**
 * Detail view of the DbEntity properties.
 * 
 */
public class DbEntityTab extends JPanel implements ExistingSelectionProcessor, DbEntityDisplayListener {

    static final String PK_DEFAULT_GENERATOR = "Cayenne-Generated (Default)";
    static final String PK_DB_GENERATOR = "Database-Generated";
    static final String PK_CUSTOM_SEQUENCE_GENERATOR = "Custom Sequence";

    static final String[] PK_GENERATOR_TYPES = { PK_DEFAULT_GENERATOR, PK_DB_GENERATOR, PK_CUSTOM_SEQUENCE_GENERATOR };

    protected ProjectController mediator;

    protected TextAdapter name;
    protected TextAdapter catalog;
    protected TextAdapter schema;
    protected TextAdapter qualifier;
    protected TextAdapter comment;

    protected JLabel catalogLabel;
    protected JLabel schemaLabel;

    protected JComboBox<String> pkGeneratorType;
    protected JPanel pkGeneratorDetail;
    protected CardLayout pkGeneratorDetailLayout;

    private JToolBar toolBar;

    public DbEntityTab(ProjectController mediator) {
        super();
        this.mediator = mediator;

        initView();
        initController();
    }

    private void initView() {

        toolBar = new JToolBar();
        toolBar.setBorder(BorderFactory.createEmptyBorder());
        toolBar.setFloatable(false);
        ActionManager actionManager = Application.getInstance().getActionManager();

        toolBar.add(actionManager.getAction(CreateAttributeAction.class).buildButton(1));
        toolBar.add(actionManager.getAction(CreateRelationshipAction.class).buildButton(3));
        toolBar.addSeparator();

        toolBar.add(actionManager.getAction(CreateObjEntityFromDbAction.class).buildButton(1));
        toolBar.add(actionManager.getAction(DbEntitySyncAction.class).buildButton(2));
        toolBar.add(actionManager.getAction(DbEntityCounterpartAction.class).buildButton(3));
        toolBar.addSeparator();

        toolBar.add(actionManager.getAction(ShowGraphEntityAction.class).buildButton());

        // create widgets
        name = new TextAdapter(new JTextField()) {
            protected void updateModel(String text) {
                setEntityName(text);
            }
        };

        catalogLabel = new JLabel("Catalog:");
        catalog = new TextAdapter(new JTextField()) {
            protected void updateModel(String text) throws ValidationException {
                setCatalog(text);
            }
        };

        schemaLabel = new JLabel("Schema:");
        schema = new TextAdapter(new JTextField()) {
            protected void updateModel(String text) throws ValidationException {
                setSchema(text);
            }
        };

        qualifier = new TextAdapter(new JTextField()) {
            protected void updateModel(String qualifier) {
                setQualifier(qualifier);
            }
        };

        comment = new TextAdapter(new JTextField()) {
            @Override
            protected void updateModel(String text) throws ValidationException {
                setComment(text);
            }
        };

        pkGeneratorType = new JComboBox<>();
        pkGeneratorType.setEditable(false);
        pkGeneratorType.setModel(new DefaultComboBoxModel<>(PK_GENERATOR_TYPES));

        pkGeneratorDetailLayout = new CardLayout();
        pkGeneratorDetail = new JPanel(pkGeneratorDetailLayout);
        pkGeneratorDetail.add(new PKDefaultGeneratorPanel(mediator), PK_DEFAULT_GENERATOR);
        pkGeneratorDetail.add(new PKDBGeneratorPanel(mediator), PK_DB_GENERATOR);
        pkGeneratorDetail.add(new PKCustomSequenceGeneratorPanel(mediator), PK_CUSTOM_SEQUENCE_GENERATOR);

        // assemble
        FormLayout layout = new FormLayout("right:pref, 3dlu, fill:200dlu", "");
        DefaultFormBuilder builder = new DefaultFormBuilder(layout);
        builder.setDefaultDialogBorder();

        builder.appendSeparator("DbEntity Configuration");
        builder.append("DbEntity Name:", name.getComponent());
        builder.append(catalogLabel, catalog.getComponent());
        builder.append(schemaLabel, schema.getComponent());
        builder.append("Qualifier:", qualifier.getComponent());
        builder.append("Comment:", comment.getComponent());

        builder.appendSeparator("Primary Key");
        builder.append("PK Generation Strategy:", pkGeneratorType);

        JPanel mainPanel = new JPanel();
        mainPanel.setLayout(new BorderLayout());

        mainPanel.add(builder.getPanel(), BorderLayout.NORTH);
        mainPanel.add(pkGeneratorDetail, BorderLayout.CENTER);

        setLayout(new BorderLayout());
        add(toolBar, BorderLayout.NORTH);
        add(mainPanel, BorderLayout.CENTER);
    }

    private void initController() {
        mediator.addDbEntityDisplayListener(this);

        pkGeneratorType.addItemListener(e -> {
            pkGeneratorDetailLayout.show(pkGeneratorDetail, (String) pkGeneratorType.getSelectedItem());

            for (int i = 0; i < pkGeneratorDetail.getComponentCount(); i++) {
                if (pkGeneratorDetail.getComponent(i).isVisible()) {

                    DbEntity entity = mediator.getCurrentDbEntity();
                    PKGeneratorPanel panel = (PKGeneratorPanel) pkGeneratorDetail.getComponent(i);
                    panel.onInit(entity);
                    break;
                }
            }
        });
    }

    public void processExistingSelection(EventObject e) {
        EntityDisplayEvent ede = new EntityDisplayEvent(this, mediator.getCurrentDbEntity(),
                mediator.getCurrentDataMap(), (DataChannelDescriptor) mediator.getProject().getRootNode());
        mediator.fireDbEntityDisplayEvent(ede);
    }

    public void currentDbEntityChanged(EntityDisplayEvent e) {
        DbEntity entity = (DbEntity) e.getEntity();

        if (entity == null) {
            return;
        }

        // if entity hasn't changed, still notify PK Generator panels, as entity PK may have changed...
        for (int i = 0; i < pkGeneratorDetail.getComponentCount(); i++) {
            ((PKGeneratorPanel) pkGeneratorDetail.getComponent(i)).setDbEntity(entity);
        }

        name.setText(entity.getName());
        catalog.setText(entity.getCatalog());
        schema.setText(entity.getSchema());
        qualifier.setText(new ExpressionConvertor().valueAsString(entity.getQualifier()));
        comment.setText(getComment(entity));

        String type = PK_DEFAULT_GENERATOR;

        if (entity.getPrimaryKeyGenerator() != null) {
            type = PK_CUSTOM_SEQUENCE_GENERATOR;
        } else {
            for (DbAttribute a : entity.getPrimaryKeys()) {
                if (a.isGenerated()) {
                    type = PK_DB_GENERATOR;
                    break;
                }
            }
        }

        catalogLabel.setEnabled(true);
        catalog.getComponent().setEnabled(true);

        schemaLabel.setEnabled(true);
        schema.getComponent().setEnabled(true);
        pkGeneratorDetail.setVisible(true);
        pkGeneratorType.setVisible(true);

        pkGeneratorType.setSelectedItem(type);
        pkGeneratorDetailLayout.show(pkGeneratorDetail, type);

        if(entity.getDataMap().getMappedEntities(entity).isEmpty()) {
            toolBar.getComponentAtIndex(4).setEnabled(false);
            toolBar.getComponentAtIndex(5).setEnabled(false);
        } else {
            toolBar.getComponentAtIndex(4).setEnabled(true);
            toolBar.getComponentAtIndex(5).setEnabled(true);
        }
    }

    void setEntityName(String newName) {
        if (newName != null && newName.trim().length() == 0) {
            newName = null;
        }

        DbEntity entity = mediator.getCurrentDbEntity();

        if (entity == null || Util.nullSafeEquals(newName, entity.getName())) {
            return;
        }

        if (newName == null) {
            throw new ValidationException("Entity name is required.");
        } else if (entity.getDataMap().getDbEntity(newName) == null) {
            // completely new name, set new name for entity
            EntityEvent e = new EntityEvent(this, entity, entity.getName());
            entity.setName(newName);
            // ProjectUtil.setDbEntityName(entity, newName);
            mediator.fireDbEntityEvent(e);
        } else {
            // there is an entity with the same name
            throw new ValidationException("There is another entity with name '" + newName + "'.");
        }
    }

    void setCatalog(String text) {

        if (text != null && text.trim().length() == 0) {
            text = null;
        }

        DbEntity ent = mediator.getCurrentDbEntity();

        if (ent != null && !Util.nullSafeEquals(ent.getCatalog(), text)) {
            ent.setCatalog(text);
            mediator.fireDbEntityEvent(new EntityEvent(this, ent));
        }
    }

    void setSchema(String text) {

        if (text != null && text.trim().length() == 0) {
            text = null;
        }

        DbEntity ent = mediator.getCurrentDbEntity();

        if (ent != null && !Util.nullSafeEquals(ent.getSchema(), text)) {
            ent.setSchema(text);
            mediator.fireDbEntityEvent(new EntityEvent(this, ent));
        }
    }

    void setQualifier(String qualifier) {

        if (qualifier != null && qualifier.trim().length() == 0) {
            qualifier = null;
        }

        DbEntity ent = mediator.getCurrentDbEntity();

        if (ent != null && !Util.nullSafeEquals(ent.getQualifier(), qualifier)) {
            ExpressionConvertor convertor = new ExpressionConvertor();
            try {
                String oldQualifier = convertor.valueAsString(ent.getQualifier());
                if (!Util.nullSafeEquals(oldQualifier, qualifier)) {
                    Expression exp = (Expression) convertor.stringAsValue(qualifier);
                    ent.setQualifier(exp);
                    mediator.fireDbEntityEvent(new EntityEvent(this, ent));
                }
            } catch (IllegalArgumentException ex) {
                // unparsable qualifier
                throw new ValidationException(ex.getMessage());
            }

        }
    }

    private String getComment(DbEntity entity) {
        return ObjectInfo.getFromMetaData(mediator.getApplication().getMetaData(), entity, ObjectInfo.COMMENT);
    }

    private void setComment(String value) {
        DbEntity entity = mediator.getCurrentDbEntity();

        if(entity == null) {
            return;
        }

        ObjectInfo.putToMetaData(mediator.getApplication().getMetaData(), entity, ObjectInfo.COMMENT, value);
        mediator.fireDbEntityEvent(new EntityEvent(this, entity));
    }
}
