/*****************************************************************
 *   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
 *
 *    http://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.dialog.db;

import java.awt.Component;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Iterator;

import javax.sql.DataSource;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.WindowConstants;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import org.apache.cayenne.access.DbGenerator;
import org.apache.cayenne.dba.DbAdapter;
import org.apache.cayenne.map.DataMap;
import org.apache.cayenne.modeler.Application;
import org.apache.cayenne.modeler.ProjectController;
import org.apache.cayenne.modeler.dialog.ValidationResultBrowser;
import org.apache.cayenne.modeler.pref.DBConnectionInfo;
import org.apache.cayenne.modeler.pref.DBGeneratorDefaults;
import org.apache.cayenne.modeler.util.CayenneController;
import org.apache.cayenne.swing.BindingBuilder;
import org.apache.cayenne.swing.ObjectBinding;
import org.apache.cayenne.validation.ValidationResult;

/**
 * @author Andrei Adamchik
 */
public class DBGeneratorOptions extends CayenneController {

    protected DBGeneratorOptionsView view;
    protected ObjectBinding[] optionBindings;
    protected ObjectBinding sqlBinding;

    protected DBConnectionInfo connectionInfo;
    protected DataMap dataMap;
    protected DBGeneratorDefaults generatorDefaults;
    protected DbGenerator generator;
    protected String textForSQL;

    protected TableSelectorController tables;

    public DBGeneratorOptions(ProjectController parent, String title,
            DBConnectionInfo connectionInfo, DataMap dataMap) {
        super(parent);

        this.dataMap = dataMap;
        this.tables = new TableSelectorController(parent);
        this.view = new DBGeneratorOptionsView(tables.getView());
        this.connectionInfo = connectionInfo;
        this.generatorDefaults = (DBGeneratorDefaults) parent
                .getPreferenceDomainForProject()
                .getDetail("DbGenerator", DBGeneratorDefaults.class, true);

        this.view.setTitle(title);
        initController();

        tables.updateTables(dataMap);
        prepareGenerator();
        generatorDefaults.configureGenerator(generator);
        createSQL();
        refreshView();
    }

    public Component getView() {
        return view;
    }

    public DBGeneratorDefaults getGeneratorDefaults() {
        return generatorDefaults;
    }

    public String getTextForSQL() {
        return textForSQL;
    }

    protected void initController() {

        BindingBuilder builder = new BindingBuilder(
                getApplication().getBindingFactory(),
                this);

        sqlBinding = builder.bindToTextArea(view.getSql(), "textForSQL");

        optionBindings = new ObjectBinding[5];
        optionBindings[0] = builder.bindToStateChangeAndAction(view.getCreateFK(),
                "generatorDefaults.createFK",
                "refreshSQLAction()");
        optionBindings[1] = builder.bindToStateChangeAndAction(view.getCreatePK(),
                "generatorDefaults.createPK",
                "refreshSQLAction()");
        optionBindings[2] = builder.bindToStateChangeAndAction(view.getCreateTables(),
                "generatorDefaults.createTables",
                "refreshSQLAction()");
        optionBindings[3] = builder.bindToStateChangeAndAction(view.getDropPK(),
                "generatorDefaults.dropPK",
                "refreshSQLAction()");
        optionBindings[4] = builder.bindToStateChangeAndAction(view.getDropTables(),
                "generatorDefaults.dropTables",
                "refreshSQLAction()");

        builder.bindToAction(view.getGenerateButton(), "generateSchemaAction()");
        builder.bindToAction(view.getSaveSqlButton(), "storeSQLAction()");
        builder.bindToAction(view.getCancelButton(), "closeAction()");

        // refresh SQL if different tables were selected
        view.getTabs().addChangeListener(new ChangeListener() {

            public void stateChanged(ChangeEvent e) {
                if (view.getTabs().getSelectedIndex() == 0) {
                    // this assumes that some tables where checked/unchecked... not very
                    // efficient
                    refreshGeneratorAction();
                }
            }
        });
    }

    /**
     * Creates new internal DbGenerator instance.
     */
    protected void prepareGenerator() {
        try {
            DbAdapter adapter = connectionInfo.makeAdapter(getApplication()
                    .getClassLoadingService());
            this.generator = new DbGenerator(adapter, dataMap, tables.getExcludedTables());
            this.generatorDefaults.adjustForAdapter(adapter);
        }
        catch (Exception ex) {
            reportError("Error loading adapter", ex);
        }
    }

    /**
     * Returns SQL statements generated for selected schema generation options.
     */
    protected void createSQL() {
        // convert them to string representation for display
        StringBuffer buf = new StringBuffer();
        Iterator it = generator.configuredStatements().iterator();
        String batchTerminator = generator.getAdapter().getBatchTerminator();

        String lineEnd = (batchTerminator != null)
                ? "\n" + batchTerminator + "\n\n"
                : "\n\n";

        while (it.hasNext()) {
            buf.append(it.next()).append(lineEnd);
        }

        textForSQL = buf.toString();
    }

    protected void refreshView() {

        for (int i = 0; i < optionBindings.length; i++) {
            optionBindings[i].updateView();
        }

        sqlBinding.updateView();
    }

    // ===============
    //    Actions
    // ===============

    /**
     * Starts options dialog.
     */
    public void startupAction() {
        view.pack();
        view.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
        view.setModal(true);
        makeCloseableOnEscape();
        centerView();
        view.setVisible(true);
    }

    public void refreshGeneratorAction() {
        prepareGenerator();
        refreshSQLAction();
    }

    /**
     * Updates a text area showing generated SQL.
     */
    public void refreshSQLAction() {
        // sync generator with defaults, make SQL, then sync the view...
        generatorDefaults.configureGenerator(generator);
        createSQL();
        sqlBinding.updateView();
    }

    /**
     * Performs configured schema operations via DbGenerator.
     */
    public void generateSchemaAction() {
        refreshGeneratorAction();

        // sanity check...
        if (generator.isEmpty(true)) {
            JOptionPane.showMessageDialog(getView(), "Nothing to generate.");
            return;
        }

        try {
            DataSource dataSource = connectionInfo.makeDataSource(getApplication()
                    .getClassLoadingService());
            generator.runGenerator(dataSource);

            ValidationResult failures = generator.getFailures();

            if (failures == null || !failures.hasFailures()) {
                JOptionPane.showMessageDialog(getView(), "Schema Generation Complete.");
            }
            else {
                new ValidationResultBrowser(this)
                        .startupAction("Schema Generation Complete",
                                "Schema generation finished. The following problem(s) were ignored.",
                                failures);
            }
        }
        catch (Throwable th) {
            reportError("Schema Generation Error", th);
        }
    }

    /**
     * Allows user to save generated SQL in a file.
     */
    public void storeSQLAction() {
        JFileChooser fc = new JFileChooser();
        fc.setDialogType(JFileChooser.SAVE_DIALOG);
        fc.setDialogTitle("Save SQL Script");

        File projectDir = Application.getProject().getProjectDirectory();

        if (projectDir != null) {
            fc.setCurrentDirectory(projectDir);
        }

        if (fc.showSaveDialog(getView()) == JFileChooser.APPROVE_OPTION) {
            refreshGeneratorAction();

            try {
                File file = fc.getSelectedFile();
                FileWriter fw = new FileWriter(file);
                PrintWriter pw = new PrintWriter(fw);
                pw.print(textForSQL);
                pw.flush();
                pw.close();
            }
            catch (IOException ex) {
                reportError("Error Saving SQL", ex);
            }
        }
    }

    public void closeAction() {
        view.dispose();
    }
}
