package bndtools.wizards.project;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.File;
import java.io.IOException;
import java.text.MessageFormat;

import org.bndtools.utils.swt.SWTUtil;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.internal.ui.JavaPlugin;
import org.eclipse.jdt.ui.JavaUI;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.DirectoryDialog;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;

import aQute.bnd.build.Workspace;
import bndtools.Plugin;
import bndtools.central.Central;

public class ProjectLocationGroup {

    private static final String PROP_LOCATION = "location";
    private static final String PROP_STATUS = "status";
    private static final String DIALOGSTORE_LAST_EXTERNAL_LOC = JavaUI.ID_PLUGIN + ".last.external.project"; //$NON-NLS-1$

    private final PropertyChangeSupport propSupport = new PropertyChangeSupport(this);

    private final IPath workspaceLocation;

    private boolean useBndWorkspace = true;
    private String projectName;
    private IPath location;
    private String externalPath;
    private boolean programmaticChange;
    private IStatus status = Status.OK_STATUS;

    private Composite container;
    private Button btnUseBndWorkspace;
    private Label lblOtherLocation;
    private Text txtLocation;
    private Button btnBrowse;

    public ProjectLocationGroup(@SuppressWarnings("unused") String title) {
        this.workspaceLocation = findWorkspaceLocation();
        this.location = workspaceLocation;
    }

    private static IPath findWorkspaceLocation() {

        try {
            for (IProject iproj : ResourcesPlugin.getWorkspace()
                .getRoot()
                .getProjects()) {
                if (iproj.getName()
                    .equals(Workspace.CNFDIR)) {
                    IPath rawLocation = iproj.getRawLocation();
                    if (rawLocation == null)
                        break;

                    return iproj.getRawLocation()
                        .removeLastSegments(1);
                }
            }
        } catch (Exception e) {
            // nice try
        }

        IPath p = Platform.getLocation();

        Workspace ws;
        try {
            ws = Central.getWorkspace();
        } catch (Exception e) {
            ws = null;
            for (IProject iproj : ResourcesPlugin.getWorkspace()
                .getRoot()
                .getProjects()) {
                if (iproj.getName()
                    .equals(Workspace.CNFDIR)) {
                    return iproj.getRawLocation()
                        .removeLastSegments(1);
                }
            }
        }

        if (ws != null && !ws.isDefaultWorkspace())
            p = Path.fromOSString(ws.getBase()
                .getAbsolutePath());

        return p;
    }

    /**
     * @wbp.parser.entryPoint
     */
    public Control createControl(Composite parent) {
        container = new Composite(parent, SWT.NONE);
        container.setLayout(new GridLayout(3, false));

        btnUseBndWorkspace = new Button(container, SWT.CHECK);
        btnUseBndWorkspace.setText("Use bnd workspace location");
        btnUseBndWorkspace.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 3, 1));

        lblOtherLocation = new Label(container, SWT.NONE);
        lblOtherLocation.setText("Location:");

        txtLocation = new Text(container, SWT.BORDER);
        txtLocation.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
        txtLocation.setText("[WorkspaceLocation]");

        btnBrowse = new Button(container, SWT.NONE);
        btnBrowse.setText("Browse...");
        btnBrowse.setLayoutData(getButtonLayoutData(btnBrowse));

        updateUI();

        txtLocation.addModifyListener(new ModifyListener() {
            @Override
            public void modifyText(ModifyEvent e) {
                IPath oldValue = getLocation();
                if (!useBndWorkspace) {
                    externalPath = txtLocation.getText();
                }
                IPath newValue = getLocation();
                if (!programmaticChange) {
                    propSupport.firePropertyChange(PROP_LOCATION, oldValue, newValue);
                    validate();
                }
            }
        });
        btnUseBndWorkspace.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                IPath oldValue = getLocation();
                useBndWorkspace = btnUseBndWorkspace.getSelection();
                IPath newValue = getLocation();
                if (!programmaticChange) {
                    propSupport.firePropertyChange(PROP_LOCATION, oldValue, newValue);
                    updateUI();
                    validate();
                }
            }
        });
        btnBrowse.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                DirectoryDialog dialog = new DirectoryDialog(container.getShell());
                dialog.setMessage("Choose a directory for the project contents:");
                String directoryName = txtLocation.getText()
                    .trim();
                if (directoryName == null || directoryName.length() == 0) {
                    String previous = JavaPlugin.getDefault()
                        .getDialogSettings()
                        .get(DIALOGSTORE_LAST_EXTERNAL_LOC);
                    if (previous != null)
                        directoryName = previous;
                }

                assert (directoryName != null);

                if (directoryName.length() > 0) {
                    File path = new File(directoryName);
                    if (path.exists())
                        dialog.setFilterPath(directoryName);
                }

                String selected = dialog.open();
                if (selected != null) {
                    IPath path = new Path(selected);
                    if (projectName != null && !projectName.equals(path.lastSegment()))
                        selected = path.append(projectName)
                            .toString();
                    txtLocation.setText(selected);
                }
            }
        });

        return container;
    }

    private Object getButtonLayoutData(Button button) {
        GridData gd = new GridData();
        gd.widthHint = SWTUtil.getButtonWidthHint(button);
        return gd;
    }

    private IStatus checkStatus() {
        // Check for empty project name
        if (projectName == null || projectName.length() == 0)
            return new Status(IStatus.ERROR, Plugin.PLUGIN_ID, 0, "Project name must be specified", null);

        // If external location, ensure path is specified and ends with projectname
        if (!useBndWorkspace) {
            IPath loc = getExternalLocation();
            if (loc == null || loc.isEmpty())
                return new Status(IStatus.ERROR, Plugin.PLUGIN_ID, 0, "Project location invalid or not specified.", null);

            if (loc.lastSegment() == null || !loc.lastSegment()
                .equals(projectName))
                return new Status(IStatus.ERROR, Plugin.PLUGIN_ID, 0, "Project location must end with specified project name", null);

            if (workspaceLocation != null && !workspaceLocation.isPrefixOf(loc)) {
                return new Status(IStatus.INFO, Plugin.PLUGIN_ID, 0, "Project location will NOT work with the Bnd support because it is in a directory other than the cnf directory (" + workspaceLocation + ")", null);
            }
        }

        // Check valid name
        IStatus nameStatus = JavaPlugin.getWorkspace()
            .validateName(projectName != null ? projectName : "", IResource.PROJECT);
        if (!nameStatus.isOK())
            return nameStatus;

        // Check if project name already exists
        IProject existingProject = JavaPlugin.getWorkspace()
            .getRoot()
            .getProject(projectName);
        if (existingProject.exists())
            return new Status(IStatus.ERROR, Plugin.PLUGIN_ID, 0, MessageFormat.format("Project \"{0}\" already exists.", projectName), null);

        // Find existing project at that location and check if name matches
        IPath projectLocation = ResourcesPlugin.getWorkspace()
            .getRoot()
            .getLocation()
            .append(projectName);
        if (projectLocation.toFile()
            .exists()) {
            try {
                String path = projectLocation.toFile()
                    .getCanonicalPath();
                projectLocation = new Path(path);
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            String existingName = projectLocation.lastSegment();
            if (!existingName.equals(projectName))
                return new Status(IStatus.ERROR, Plugin.PLUGIN_ID, 0, MessageFormat.format("The name of the new project must be ''{0}''", existingName), null);
        }

        return Status.OK_STATUS;
    }

    private void validate() {
        IStatus oldStatus = status;
        status = checkStatus();
        propSupport.firePropertyChange(PROP_STATUS, oldStatus, status);
    }

    public void setProjectName(String projectName) {
        this.projectName = projectName;
        IPath old = location;
        location = buildProjectLocation(projectName);

        try {
            programmaticChange = true;
            updateUI();
        } finally {
            programmaticChange = false;
        }
        propSupport.firePropertyChange(PROP_LOCATION, old, location);
        validate();
    }

    public IPath getLocation() {
        IPath path = useBndWorkspace ? location : getExternalLocation();
        return path;
    }

    private IPath getExternalLocation() {
        IPath path;
        if (externalPath != null && externalPath.length() > 0 && Path.EMPTY.isValidPath(externalPath))
            path = Path.fromOSString(externalPath);
        else
            path = null;
        return path;
    }

    public IStatus getStatus() {
        return status;
    }

    private void updateUI() {
        if (btnUseBndWorkspace != null && !btnUseBndWorkspace.isDisposed()) {
            btnUseBndWorkspace.setSelection(useBndWorkspace);
            lblOtherLocation.setEnabled(!useBndWorkspace);
            txtLocation.setEnabled(!useBndWorkspace);
            txtLocation.setText(useBndWorkspace ? location.toOSString() : externalPath == null ? "" : externalPath);
            btnBrowse.setEnabled(!useBndWorkspace);
        }
    }

    private IPath buildProjectLocation(String projectName) {
        IPath result = workspaceLocation;
        if (projectName != null && projectName.length() > 0)
            result = result.append(projectName);
        return result;
    }

    public void addPropertyChangeListener(PropertyChangeListener l) {
        propSupport.addPropertyChangeListener(l);
    }

    public void removePropertyChangeListener(PropertyChangeListener l) {
        propSupport.removePropertyChangeListener(l);
    }

}
