/*****************************************************************
 *   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;

import java.awt.Component;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.util.Iterator;
import java.util.Vector;

import javax.swing.WindowConstants;

import org.apache.cayenne.modeler.action.ExitAction;
import org.apache.cayenne.modeler.action.OpenProjectAction;
import org.apache.cayenne.modeler.dialog.validator.ValidatorDialog;
import org.apache.cayenne.modeler.editor.EditorView;
import org.apache.cayenne.modeler.pref.ComponentGeometry;
import org.apache.cayenne.modeler.pref.FSPath;
import org.apache.cayenne.modeler.util.CayenneController;
import org.apache.cayenne.modeler.util.RecentFileMenu;
import org.apache.cayenne.pref.Domain;
import org.apache.cayenne.project.Project;
import org.apache.cayenne.project.validator.Validator;

/**
 * Controller of the main application frame.
 * 
 * @author Andrus Adamchik
 */
public class CayenneModelerController extends CayenneController {

    protected ProjectController projectController;

    protected CayenneModelerFrame frame;
    protected File initialProject;

    public CayenneModelerController(Application application, File initialProject) {
        super(application);

        this.initialProject = initialProject;
        this.frame = new CayenneModelerFrame(application.getActionManager());
        this.projectController = new ProjectController(this);
    }

    public Component getView() {
        return frame;
    }

    public ProjectController getProjectController() {
        return projectController;
    }

    public FSPath getLastEOModelDirectory() {
        // find start directory in preferences

        FSPath path = (FSPath) getViewDomain()
                .getDetail("lastEOMDir", FSPath.class, true);

        if (path.getPath() == null) {
            path.setPath(getLastDirectory().getPath());
        }

        return path;
    }

    protected void initBindings() {
        frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);

        frame.addWindowListener(new WindowAdapter() {

            public void windowClosing(WindowEvent e) {
                ((ExitAction) getApplication().getAction(ExitAction.getActionName()))
                        .exit();
            }
        });

        Domain prefDomain = application.getPreferenceDomain().getSubdomain(
                frame.getClass());
        ComponentGeometry geometry = ComponentGeometry.getPreference(prefDomain);
        geometry.bind(frame, 650, 550, 30);
    }

    public void startupAction() {
        initBindings();
        frame.setVisible(true);

        // open project
        if (initialProject != null) {
            OpenProjectAction openAction = (OpenProjectAction) getApplication()
                    .getAction(OpenProjectAction.getActionName());
            openAction.openProject(initialProject);
        }
    }

    public void projectModifiedAction() {
        String title = (projectController.getProject().isLocationUndefined())
                ? "[New]"
                : projectController.getProject().getMainFile().getAbsolutePath();

        frame.setTitle("* - " + ModelerConstants.TITLE + " - " + title);
    }

    public void projectSavedAction() {
        projectController.setDirty(false);
        updateStatus("Project saved...");
        frame.setTitle(ModelerConstants.TITLE
                + " - "
                + projectController.getProject().getMainFile().getAbsolutePath());
    }

    /**
     * Action method invoked on project closing.
     */
    public void projectClosedAction() {
        // --- update view
        frame.setView(null);

        // repaint is needed, since sometimes there is a
        // trace from menu left on the screen
        frame.repaint();
        frame.setTitle(ModelerConstants.TITLE);

        projectController.setProject(null);

        projectController.reset();
        application.getActionManager().projectClosed();

        updateStatus("Project Closed...");
    }

    /**
     * Handles project opening control. Updates main frame, then delegates control to
     * child controllers.
     */
    public void projectOpenedAction(Project project) {

        projectController.setProject(project);

        frame.setView(new EditorView(projectController));

        projectController.projectOpened();
        application.getActionManager().projectOpened();

        // do status update AFTER the project is actually opened...
        if (project.isLocationUndefined()) {
            updateStatus("New project created...");
            frame.setTitle(ModelerConstants.TITLE + "- [New]");
        }
        else {
            updateStatus("Project opened...");
            frame.setTitle(ModelerConstants.TITLE
                    + " - "
                    + project.getMainFile().getAbsolutePath());
        }

        // update preferences
        if (!project.isLocationUndefined()) {
            getLastDirectory().setDirectory(project.getProjectDirectory());
            RecentFileMenu recentFileMenu = frame.getRecentFileMenu();
            recentFileMenu.rebuildFromPreferences();
            recentFileMenu.setEnabled(recentFileMenu.getMenuComponentCount() > 0);
        }

        // --- check for load errors
        if (project.getLoadStatus().hasFailures()) {
            // mark project as unsaved
            project.setModified(true);
            projectController.setDirty(true);

            // show warning dialog
            ValidatorDialog.showDialog(frame, new Validator(project, project
                    .getLoadStatus()));
        }

    }

    /** Adds path to the list of last opened projects in preferences. */
    public void addToLastProjListAction(String path) {
        ModelerPreferences pref = ModelerPreferences.getPreferences();
        Vector arr = pref.getVector(ModelerPreferences.LAST_PROJ_FILES);
        // Add proj path to the preferences
        // Prevent duplicate entries.
        if (arr.contains(path)) {
            arr.remove(path);
        }

        arr.insertElementAt(path, 0);
        while (arr.size() > 4) {
            arr.remove(arr.size() - 1);
        }

        pref.remove(ModelerPreferences.LAST_PROJ_FILES);
        Iterator iter = arr.iterator();
        while (iter.hasNext()) {
            pref.addProperty(ModelerPreferences.LAST_PROJ_FILES, iter.next());
        }
    }

    /**
     * Performs status bar update with a message. Message will dissappear in 6 seconds.
     */
    public void updateStatus(String message) {
        frame.getStatus().setText(message);

        // start message cleanup thread that would remove the message after X seconds
        if (message != null && message.trim().length() > 0) {
            Thread cleanup = new ExpireThread(message, 6);
            cleanup.start();
        }
    }

    class ExpireThread extends Thread {

        protected int seconds;
        protected String message;

        public ExpireThread(String message, int seconds) {
            this.seconds = seconds;
            this.message = message;
        }

        public void run() {
            try {
                sleep(seconds * 1000);
            }
            catch (InterruptedException e) {
                // ignore exception
            }

            if (message.equals(frame.getStatus().getText())) {
                updateStatus(null);
            }
        }
    }
}
