/*
 * Copyright (C) 2009 The Android Open Source Project
 *
 * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
 *
 * 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 com.android.ide.eclipse.adt.internal.editors.layout.configuration;

import com.android.ddmuilib.TableHelper;
import com.android.ide.common.resources.configuration.FolderConfiguration;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.sdk.LayoutDevice;
import com.android.ide.eclipse.adt.internal.sdk.LayoutDeviceManager;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
import com.android.ide.eclipse.adt.internal.sdk.LayoutDevice.DeviceConfig;
import com.android.sdkuilib.ui.GridDialog;

import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.TreePath;
import org.eclipse.jface.viewers.TreeSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
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.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Tree;

import java.util.List;

/**
 * Dialog to view the layout devices with action button to create/edit/delete/copy layout devices
 * and configs.
 *
 */
public class ConfigManagerDialog extends GridDialog {

    private final static String COL_NAME = AdtPlugin.PLUGIN_ID + ".configmanager.name"; //$NON-NLS-1$
    private final static String COL_CONFIG = AdtPlugin.PLUGIN_ID + ".configmanager.config"; //$NON-NLS-1$

    /**
     * enum to represent the different origin of the layout devices.
     */
    private static enum DeviceType {
        DEFAULT("Default"),
        ADDON("Add-on"),
        CUSTOM("Custom");

        private final String mDisplay;

        DeviceType(String display) {
            mDisplay = display;
        }

        String getDisplayString() {
            return mDisplay;
        }
    }

    /**
     * simple class representing the tree selection with the proper types.
     */
    private static class DeviceSelection {
        public DeviceSelection(DeviceType type, LayoutDevice device,
                DeviceConfig config) {
            this.type = type;
            this.device = device;
            this.config = config;
        }

        final DeviceType type;
        final LayoutDevice device;
        final DeviceConfig config;
    }

    private final LayoutDeviceManager mManager;

    private TreeViewer mTreeViewer;
    private Button mNewButton;
    private Button mEditButton;
    private Button mCopyButton;
    private Button mDeleteButton;

    /**
     * Content provider of the {@link TreeViewer}. The expected input is
     * {@link LayoutDeviceManager}.
     *
     */
    private final static class DeviceContentProvider implements ITreeContentProvider {
        private final static DeviceType[] sCategory = new DeviceType[] {
            DeviceType.DEFAULT, DeviceType.ADDON, DeviceType.CUSTOM
        };

        private LayoutDeviceManager mLayoutDeviceManager;

        public DeviceContentProvider() {
        }

        public Object[] getElements(Object inputElement) {
            return sCategory;
        }

        public Object[] getChildren(Object parentElement) {
            if (parentElement instanceof DeviceType) {
                if (DeviceType.DEFAULT.equals(parentElement)) {
                    return mLayoutDeviceManager.getDefaultLayoutDevices().toArray();
                } else if (DeviceType.ADDON.equals(parentElement)) {
                    return mLayoutDeviceManager.getAddOnLayoutDevice().toArray();
                } else if (DeviceType.CUSTOM.equals(parentElement)) {
                    return mLayoutDeviceManager.getUserLayoutDevices().toArray();
                }
            } else if (parentElement instanceof LayoutDevice) {
                LayoutDevice device = (LayoutDevice)parentElement;
                return device.getConfigs().toArray();
            }

            return null;
        }

        public Object getParent(Object element) {
            // parent cannot be computed. this is fine.
            return null;
        }

        public boolean hasChildren(Object element) {
            if (element instanceof DeviceType) {
                if (DeviceType.DEFAULT.equals(element)) {
                    return mLayoutDeviceManager.getDefaultLayoutDevices().size() > 0;
                } else if (DeviceType.ADDON.equals(element)) {
                    return mLayoutDeviceManager.getAddOnLayoutDevice().size() > 0;
                } else if (DeviceType.CUSTOM.equals(element)) {
                    return mLayoutDeviceManager.getUserLayoutDevices().size() > 0;
                }
            } else if (element instanceof LayoutDevice) {
                LayoutDevice device = (LayoutDevice)element;
                return device.getConfigs().size() > 0;
            }

            return false;
        }


        public void dispose() {
            // nothing to dispose
        }

        public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
            if (newInput instanceof LayoutDeviceManager) {
                mLayoutDeviceManager = (LayoutDeviceManager)newInput;
                return;
            }

            // when the dialog closes we get null input
            if (newInput != null) {
                throw new IllegalArgumentException(
                        "ConfigContentProvider requires input to be LayoutDeviceManager");
            }
        }
    }

    /**
     * Label provider for the {@link TreeViewer}.
     * Supported elements are {@link DeviceType}, {@link LayoutDevice}, and {@link DeviceConfig}.
     *
     */
    private final static class DeviceLabelProvider implements ITableLabelProvider {

        public String getColumnText(Object element, int columnIndex) {
            if (element instanceof DeviceType) {
                if (columnIndex == 0) {
                    return ((DeviceType)element).getDisplayString();
                }
            } else if (element instanceof LayoutDevice) {
                if (columnIndex == 0) {
                    return ((LayoutDevice)element).getName();
                }
            } else if (element instanceof DeviceConfig) {
                if (columnIndex == 0) {
                    return ((DeviceConfig)element).getName();
                } else {
                    return ((DeviceConfig)element).getConfig().toString();
                }
            }
            return null;
        }

        public Image getColumnImage(Object element, int columnIndex) {
            // no image
            return null;
        }

        public void addListener(ILabelProviderListener listener) {
            // no listener
        }

        public void removeListener(ILabelProviderListener listener) {
            // no listener
        }

        public void dispose() {
            // nothing to dispose
        }

        public boolean isLabelProperty(Object element, String property) {
            return false;
        }
    }

    protected ConfigManagerDialog(Shell parentShell) {
        super(parentShell, 2, false);
        mManager = Sdk.getCurrent().getLayoutDeviceManager();
    }

    @Override
    protected int getShellStyle() {
        return super.getShellStyle() | SWT.RESIZE;
    }

    @Override
    protected void configureShell(Shell newShell) {
        super.configureShell(newShell);
        newShell.setText("Device Configurations");
    }

    @Override
    public void createDialogContent(final Composite parent) {
        GridData gd;
        GridLayout gl;

        Tree tree = new Tree(parent, SWT.SINGLE | SWT.FULL_SELECTION);
        tree.setLayoutData(gd = new GridData(GridData.FILL_BOTH));
        gd.widthHint = 700;

        tree.setHeaderVisible(true);
        tree.setLinesVisible(true);
        TableHelper.createTreeColumn(tree, "Name", SWT.LEFT, 150, COL_NAME,
                AdtPlugin.getDefault().getPreferenceStore());
        TableHelper.createTreeColumn(tree, "Configuration", SWT.LEFT, 500, COL_CONFIG,
                AdtPlugin.getDefault().getPreferenceStore());

        mTreeViewer = new TreeViewer(tree);
        mTreeViewer.setContentProvider(new DeviceContentProvider());
        mTreeViewer.setLabelProvider(new DeviceLabelProvider());
        mTreeViewer.setAutoExpandLevel(TreeViewer.ALL_LEVELS);
        mTreeViewer.addSelectionChangedListener(new ISelectionChangedListener() {
            public void selectionChanged(SelectionChangedEvent event) {
                setEnabled(getSelection());
            }
        });

        Composite buttons = new Composite(parent, SWT.NONE);
        buttons.setLayoutData(new GridData(GridData.FILL_VERTICAL));
        buttons.setLayout(gl = new GridLayout());
        gl.marginHeight = gl.marginWidth = 0;

        mNewButton = new Button(buttons, SWT.PUSH | SWT.FLAT);
        mNewButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
        mNewButton.setText("New...");
        mNewButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                DeviceSelection selection = getSelection();

                ConfigEditDialog dlg = new ConfigEditDialog(parent.getShell(), null);
                if (selection.device != null) {
                    dlg.setDeviceName(selection.device.getName());
                    dlg.setXDpi(selection.device.getXDpi());
                    dlg.setYDpi(selection.device.getYDpi());
                }
                if (selection.config != null) {
                    dlg.setConfigName(selection.config.getName());
                    dlg.setConfig(selection.config.getConfig());
                }

                if (dlg.open() == Window.OK) {
                    String deviceName = dlg.getDeviceName();
                    String configName = dlg.getConfigName();
                    FolderConfiguration config = new FolderConfiguration();
                    dlg.getConfig(config);

                    // first if there was no original device, we create one.
                    // Because the new button is disabled when something else than "custom" is
                    // selected, we always add to the user devices without checking.
                    LayoutDevice d;
                    if (selection.device == null) {
                        // FIXME: this doesn't check if the device name is taken.
                        d = mManager.addUserDevice(deviceName, dlg.getXDpi(), dlg.getYDpi());
                    } else {
                        // search for it.
                        d = mManager.getUserLayoutDevice(deviceName);
                    }

                    if (d != null) {
                        // then if there was no config, we add it, otherwise we edit it
                        // (same method that adds/replace a config).
                        // FIXME this doesn't check if the name was already taken.
                        mManager.addUserConfiguration(d, configName, config);

                        mTreeViewer.refresh();
                        select(d, configName);
                    }
                }
            }
        });

        mEditButton = new Button(buttons, SWT.PUSH | SWT.FLAT);
        mEditButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
        mEditButton.setText("Edit...");
        mEditButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                DeviceSelection selection = getSelection();
                ConfigEditDialog dlg = new ConfigEditDialog(parent.getShell(), null);
                dlg.setDeviceName(selection.device.getName());
                dlg.setXDpi(selection.device.getXDpi());
                dlg.setYDpi(selection.device.getYDpi());
                dlg.setConfigName(selection.config.getName());
                dlg.setConfig(selection.config.getConfig());

                if (dlg.open() == Window.OK) {
                    String deviceName = dlg.getDeviceName();
                    String configName = dlg.getConfigName();
                    FolderConfiguration config = new FolderConfiguration();
                    dlg.getConfig(config);

                    // replace the device if needed.
                    // FIXME: this doesn't check if the replacement name doesn't exist already.
                    LayoutDevice d = mManager.replaceUserDevice(selection.device, deviceName,
                            dlg.getXDpi(), dlg.getYDpi());

                    // and add/replace the config
                    mManager.replaceUserConfiguration(d, selection.config.getName(), configName,
                            config);

                    mTreeViewer.refresh();
                    select(d, configName);
                }
            }
        });

        mCopyButton = new Button(buttons, SWT.PUSH | SWT.FLAT);
        mCopyButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
        mCopyButton.setText("Copy");
        mCopyButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                DeviceSelection selection = getSelection();

                // is the source a default/add-on device, or are we copying a full device?
                // if so the target device is a new device.
                LayoutDevice targetDevice = selection.device;
                if (selection.type == DeviceType.DEFAULT || selection.type == DeviceType.ADDON ||
                        selection.config == null) {
                    // create a new device
                    targetDevice = mManager.addUserDevice(
                            selection.device.getName() + " Copy", // new name
                            selection.device.getXDpi(),
                            selection.device.getYDpi());
                }

                String newConfigName = null; // name of the single new config. used for the select.

                // are we copying the full device?
                if (selection.config == null) {
                    // get the config from the origin device
                    List<DeviceConfig> configs = selection.device.getConfigs();

                    // and copy them in the target device
                    for (DeviceConfig config : configs) {
                        // we need to make a copy of the config object, or it could be modified
                        // in default/addon by editing the version in the new device.
                        FolderConfiguration copy = new FolderConfiguration();
                        copy.set(config.getConfig());

                        // the name can stay the same since we are copying a full device
                        // and the target device has its own new name.
                        mManager.addUserConfiguration(targetDevice, config.getName(), copy);
                    }
                } else {
                    // only copy the config. target device is not the same as the selection, don't
                    // change the config name as we already changed the name of the device.
                    newConfigName = (selection.device != targetDevice) ?
                            selection.config.getName() : selection.config.getName() + " Copy";

                    // copy of the config
                    FolderConfiguration copy = new FolderConfiguration();
                    copy.set(selection.config.getConfig());

                    // and create the config
                    mManager.addUserConfiguration(targetDevice, newConfigName, copy);
                }

                mTreeViewer.refresh();

                select(targetDevice, newConfigName);
            }
        });

        mDeleteButton = new Button(buttons, SWT.PUSH | SWT.FLAT);
        mDeleteButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
        mDeleteButton.setText("Delete");
        mDeleteButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                DeviceSelection selection = getSelection();

                if (selection.config != null) {
                    mManager.removeUserConfiguration(selection.device, selection.config.getName());
                } else if (selection.device != null) {
                    mManager.removeUserDevice(selection.device);
                }

                mTreeViewer.refresh();

                // either select the device (if we removed a entry, or the top custom node if
                // we removed a device)
                select(selection.config != null ? selection.device : null, null);
            }
        });

        Label separator = new Label(parent, SWT.SEPARATOR | SWT.HORIZONTAL);
        separator.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
        gd.horizontalSpan = 2;

        mTreeViewer.setInput(mManager);
        setEnabled(null); // no selection at the start
    }

    @Override
    protected void createButtonsForButtonBar(Composite parent) {
        // we only want an OK button.
        createButton(parent, IDialogConstants.OK_ID, IDialogConstants.OK_LABEL, true);
    }

    /**
     * Returns a {@link DeviceSelection} object representing the selected path in the
     * {@link TreeViewer}
     */
    private DeviceSelection getSelection() {
        // get the selection paths
        TreeSelection selection = (TreeSelection)mTreeViewer.getSelection();
        TreePath[] paths =selection.getPaths();

        if (paths.length == 0) {
            return null;
        }

        TreePath pathSelection = paths[0];

        DeviceType type = (DeviceType)pathSelection.getFirstSegment();
        LayoutDevice device = null;
        DeviceConfig config = null;
        switch (pathSelection.getSegmentCount()) {
            case 2: // layout device is selected
                device = (LayoutDevice)pathSelection.getLastSegment();
                break;
            case 3: // config is selected
                device = (LayoutDevice)pathSelection.getSegment(1);
                config = (DeviceConfig)pathSelection.getLastSegment();
        }

        return new DeviceSelection(type, device, config);
    }

    /**
     * Enables/disables the action button based on the {@link DeviceSelection}.
     * @param selection the selection
     */
    protected void setEnabled(DeviceSelection selection) {
        if (selection == null) {
            mNewButton.setEnabled(false);
            mEditButton.setEnabled(false);
            mCopyButton.setEnabled(false);
            mDeleteButton.setEnabled(false);
        } else {
            switch (selection.type) {
                case DEFAULT:
                case ADDON:
                    // only allow copy if device is not null
                    mNewButton.setEnabled(false);
                    mEditButton.setEnabled(false);
                    mDeleteButton.setEnabled(false);
                    mCopyButton.setEnabled(selection.device != null);
                    break;
                case CUSTOM:
                    mNewButton.setEnabled(true); // always true to create new devices.
                    mEditButton.setEnabled(selection.config != null); // only edit config for now

                    boolean enabled = selection.device != null; // need at least selected device
                    mDeleteButton.setEnabled(enabled);          // for delete and copy buttons
                    mCopyButton.setEnabled(enabled);
                    break;
            }
        }
    }

    /**
     * Selects a device and optionally a config. Because this is meant to show newly created/edited
     * device/config, it'll only do so for {@link DeviceType#CUSTOM} devices.
     * @param device the device to select
     * @param configName the config to select (optional)
     */
    private void select(LayoutDevice device, String configName) {
        Object[] path;
        if (device == null) {
            // select the "custom" node
            path = new Object[] { DeviceType.CUSTOM };
        } else if (configName == null) {
            // this is the easy case. no config to select
            path = new Object[] { DeviceType.CUSTOM, device };
        } else {
            // this is more complex. we have the configName, but the tree contains DeviceConfig
            // Look for the entry.
            DeviceConfig match = null;
            for (DeviceConfig config : device.getConfigs()) {
                if (config.getName().equals(configName)) {
                    match = config;
                    break;
                }
            }

            if (match != null) {
                path = new Object[] { DeviceType.CUSTOM, device, match };
            } else {
                path = new Object[] { DeviceType.CUSTOM, device };
            }
        }

        mTreeViewer.setSelection(new TreeSelection(new TreePath(path)), true /*reveal*/);
    }
}
