/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * Licensed 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 com.android.sdkuilib.internal.widgets;

import com.android.annotations.Nullable;
import com.android.resources.Density;
import com.android.resources.Keyboard;
import com.android.resources.KeyboardState;
import com.android.resources.Navigation;
import com.android.resources.NavigationState;
import com.android.resources.ResourceEnum;
import com.android.resources.ScreenOrientation;
import com.android.resources.ScreenRatio;
import com.android.resources.ScreenSize;
import com.android.resources.TouchScreen;
import com.android.sdklib.devices.Abi;
import com.android.sdklib.devices.ButtonType;
import com.android.sdklib.devices.Camera;
import com.android.sdklib.devices.CameraLocation;
import com.android.sdklib.devices.Device;
import com.android.sdklib.devices.DeviceManager;
import com.android.sdklib.devices.Hardware;
import com.android.sdklib.devices.Multitouch;
import com.android.sdklib.devices.Network;
import com.android.sdklib.devices.PowerType;
import com.android.sdklib.devices.Screen;
import com.android.sdklib.devices.ScreenType;
import com.android.sdklib.devices.Sensor;
import com.android.sdklib.devices.Software;
import com.android.sdklib.devices.State;
import com.android.sdklib.devices.Storage;
import com.android.sdkuilib.internal.repository.icons.ImageFactory;
import com.android.sdkuilib.ui.GridDataBuilder;
import com.android.sdkuilib.ui.GridDialog;
import com.android.sdkuilib.ui.GridLayoutBuilder;

import org.eclipse.jface.dialogs.IDialogConstants;
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.events.SelectionListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;

import java.util.List;

public class DeviceCreationDialog extends GridDialog {

    private static final String MANUFACTURER = "User";

    private final ImageFactory mImageFactory;
    private final DeviceManager mManager;
    private List<Device> mUserDevices;

    private Device mDevice;

    private Text mDeviceName;
    private Text mDiagonalLength;
    private Text mXDimension;
    private Text mYDimension;
    private Button mKeyboard;
    private Button mDpad;
    private Button mTrackball;
    private Button mNoNav;
    private Text mRam;
    private Combo mRamCombo;
    private Combo mButtons;
    private Combo mSize;
    private Combo mDensity;
    private Combo mRatio;
    private Button mAccelerometer; // hw.accelerometer
    private Button mGyro; // hw.sensors.orientation
    private Button mGps; // hw.sensors.gps
    private Button mProximitySensor; // hw.sensors.proximity
    private Button mCameraFront;
    private Button mCameraRear;
    private Group mStateGroup;
    private Button mPortrait;
    private Label mPortraitLabel;
    private Button mPortraitNav;
    private Button mLandscape;
    private Label mLandscapeLabel;
    private Button mLandscapeNav;
    private Button mPortraitKeys;
    private Label mPortraitKeysLabel;
    private Button mPortraitKeysNav;
    private Button mLandscapeKeys;
    private Label mLandscapeKeysLabel;
    private Button mLandscapeKeysNav;

    private Button mForceCreation;
    private Label mStatusIcon;
    private Label mStatusLabel;

    private Button mOkButton;

    /** The hardware instance attached to each of the states of the created device. */
    private Hardware mHardware;
    /** The instance of the Device created by the dialog, if the user pressed {@code mOkButton}. */
    private Device mCreatedDevice;

    /**
     * This contains the Software for the device. Since it has no effect on the
     * emulator whatsoever, we just use a single instance with reasonable
     * defaults. */
    private static final Software mSoftware;

    static {
        mSoftware = new Software();
        mSoftware.setLiveWallpaperSupport(true);
        mSoftware.setGlVersion("2.0");
    }

    public DeviceCreationDialog(Shell parentShell,
            DeviceManager manager,
            ImageFactory imageFactory,
            @Nullable Device device) {
        super(parentShell, 3, false);
        mImageFactory = imageFactory;
        mDevice = device;
        mManager = manager;
        mUserDevices = mManager.getDevices(DeviceManager.USER_DEVICES);
    }

    /**
     * Returns the instance of the Device created by the dialog,
     * if the user pressed the OK|create|edit|clone button.
     * Typically only non-null if the dialog returns OK.
     */
    public Device getCreatedDevice() {
        return mCreatedDevice;
    }

    @Override
    protected Control createContents(Composite parent) {
        Control control = super.createContents(parent);

        mOkButton = getButton(IDialogConstants.OK_ID);

        if (mDevice == null) {
            getShell().setText("Create New Device");
        } else {
            if (mUserDevices.contains(mDevice)) {
                getShell().setText("Edit Device");
            } else {
                getShell().setText("Clone Device");
            }
        }

        Object ld = mOkButton.getLayoutData();
        if (ld instanceof GridData) {
            ((GridData) ld).widthHint = 100;
        }

        validatePage();

        return control;
    }

    @Override
    public void createDialogContent(Composite parent) {

        ValidationListener validator = new ValidationListener();
        SizeListener sizeListener    = new SizeListener();
        NavStateListener navListener = new NavStateListener();

        Composite column1 = new Composite(parent, SWT.NONE);
        GridDataBuilder.create(column1).hFill().vTop();
        GridLayoutBuilder.create(column1).columns(2);

        // vertical separator between column 1 and 2
        Label label = new Label(parent, SWT.SEPARATOR | SWT.VERTICAL);
        GridDataBuilder.create(label).vFill().vGrab();

        Composite column2 = new Composite(parent, SWT.NONE);
        GridDataBuilder.create(column2).hFill().vTop();
        GridLayoutBuilder.create(column2).columns(2);

        // Column 1

        String tooltip = "Name of the new device";
        generateLabel("Name:", tooltip, column1);
        mDeviceName = generateText(column1, tooltip, new CreateNameModifyListener());

        tooltip = "Diagonal length of the screen in inches";
        generateLabel("Screen Size (in):", tooltip, column1);
        mDiagonalLength = generateText(column1, tooltip, sizeListener);

        tooltip = "The resolution of the device in pixels";
        generateLabel("Resolution (px):", tooltip, column1);
        Composite dimensionGroup = new Composite(column1, SWT.NONE); // Like a Group with no border
        GridDataBuilder.create(dimensionGroup).hFill();
        GridLayoutBuilder.create(dimensionGroup).columns(3).noMargins();
        mXDimension = generateText(dimensionGroup, tooltip, sizeListener);
        new Label(dimensionGroup, SWT.NONE).setText("x");
        mYDimension = generateText(dimensionGroup, tooltip, sizeListener);

        label = new Label(column1, SWT.None);   // empty space holder
        GridDataBuilder.create(label).hFill().hGrab().hSpan(2);

        // Column 2

        tooltip = "The screen size bucket that the device falls into";
        generateLabel("Size:", tooltip, column2);
        mSize = generateCombo(column2, tooltip, ScreenSize.values(), 1, validator);

        tooltip = "The aspect ratio bucket the screen falls into. A \"long\" screen is wider.";
        generateLabel("Screen Ratio:", tooltip, column2);
        mRatio = generateCombo(column2, tooltip, ScreenRatio.values(), 1, validator);

        tooltip = "The pixel density bucket the device falls in";
        generateLabel("Density:", tooltip, column2);
        mDensity = generateCombo(column2, tooltip, Density.values(), 3, validator);

        label = new Label(column2, SWT.None);   // empty space holder
        GridDataBuilder.create(label).hFill().hGrab().hSpan(2);


        // Column 1, second row

        generateLabel("Sensors:", "The sensors available on the device", column1);
        Group sensorGroup = new Group(column1, SWT.NONE);
        sensorGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
        sensorGroup.setLayout(new GridLayout(2, false));
        mAccelerometer = generateButton(sensorGroup, "Accelerometer",
                "Presence of an accelerometer", SWT.CHECK, true, validator);
        mGyro = generateButton(sensorGroup, "Gyroscope",
                "Presence of a gyroscope", SWT.CHECK, true, validator);
        mGps = generateButton(sensorGroup, "GPS", "Presence of a GPS", SWT.CHECK, true, validator);
        mProximitySensor = generateButton(sensorGroup, "Proximity Sensor",
                "Presence of a proximity sensor", SWT.CHECK, true, validator);

        generateLabel("Cameras", "The cameras available on the device", column1);
        Group cameraGroup = new Group(column1, SWT.NONE);
        cameraGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
        cameraGroup.setLayout(new GridLayout(2, false));
        mCameraFront = generateButton(cameraGroup, "Front", "Presence of a front camera",
                SWT.CHECK, false, validator);
        mCameraRear = generateButton(cameraGroup, "Rear", "Presence of a rear camera",
                SWT.CHECK, true, validator);

        generateLabel("Input:", "The input hardware on the given device", column1);
        Group inputGroup = new Group(column1, SWT.NONE);
        inputGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
        inputGroup.setLayout(new GridLayout(3, false));
        mKeyboard = generateButton(inputGroup, "Keyboard", "Presence of a hardware keyboard",
                SWT.CHECK, false,
                new KeyboardListener());
        GridData gridData = new GridData(GridData.FILL_HORIZONTAL);
        gridData.horizontalSpan = 3;
        mKeyboard.setLayoutData(gridData);
        mNoNav = generateButton(inputGroup, "No Nav", "No hardware navigation",
                SWT.RADIO, true, navListener);
        mDpad = generateButton(inputGroup, "DPad", "The device has a DPad navigation element",
                SWT.RADIO, false, navListener);
        mTrackball = generateButton(inputGroup, "Trackball",
                "The device has a trackball navigation element", SWT.RADIO, false, navListener);

        tooltip = "The amount of RAM on the device";
        generateLabel("RAM:", tooltip, column1);
        Group ramGroup = new Group(column1, SWT.NONE);
        ramGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
        ramGroup.setLayout(new GridLayout(2, false));
        mRam = generateText(ramGroup, tooltip, validator);
        mRamCombo = new Combo(ramGroup, SWT.DROP_DOWN | SWT.READ_ONLY);
        mRamCombo.setToolTipText(tooltip);
        mRamCombo.add("MiB");
        mRamCombo.add("GiB");
        mRamCombo.select(0);
        mRamCombo.addModifyListener(validator);

        // Column 2, second row

        tooltip = "Type of buttons (Home, Menu, etc.) on the device. "
                + "This can be software buttons like on the Galaxy Nexus, or hardware buttons like "
                + "the capacitive buttons on the Nexus S.";
        generateLabel("Buttons:", tooltip, column2);
        mButtons = new Combo(column2, SWT.DROP_DOWN | SWT.READ_ONLY);
        mButtons.setToolTipText(tooltip);
        mButtons.add(ButtonType.SOFT.getDescription());
        mButtons.add(ButtonType.HARD.getDescription());
        mButtons.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
        mButtons.select(0);
        mButtons.addModifyListener(validator);

        generateLabel("Device States:", "The available states for the given device", column2);

        mStateGroup = new Group(column2, SWT.NONE);
        mStateGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
        mStateGroup.setLayout(new GridLayout(2, true));

        tooltip = "The device has a portait position with no keyboard available";
        mPortraitLabel = generateLabel("Portrait:", tooltip, mStateGroup);
        gridData = new GridData(GridData.FILL_HORIZONTAL);
        gridData.horizontalSpan = 2;
        mPortraitLabel.setLayoutData(gridData);
        mPortrait = generateButton(mStateGroup, "Enabled", tooltip, SWT.CHECK, true,
                navListener);
        mPortraitNav = generateButton(mStateGroup, "Navigation",
                "Hardware navigation is available in this state", SWT.CHECK, true, validator);
        mPortraitNav.setEnabled(false);

        tooltip = "The device has a landscape position with no keyboard available";
        mLandscapeLabel = generateLabel("Landscape:", tooltip, mStateGroup);
        gridData = new GridData(GridData.FILL_HORIZONTAL);
        gridData.horizontalSpan = 2;
        mLandscapeLabel.setLayoutData(gridData);
        mLandscape = generateButton(mStateGroup, "Enabled", tooltip, SWT.CHECK, true,
                navListener);
        mLandscapeNav = generateButton(mStateGroup, "Navigation",
                "Hardware navigation is available in this state", SWT.CHECK, true, validator);
        mLandscapeNav.setEnabled(false);

        tooltip = "The device has a portait position with a keyboard available";
        mPortraitKeysLabel = generateLabel("Portrait with keyboard:", tooltip, mStateGroup);
        gridData = new GridData(GridData.FILL_HORIZONTAL);
        gridData.horizontalSpan = 2;
        mPortraitKeysLabel.setLayoutData(gridData);
        mPortraitKeysLabel.setEnabled(false);
        mPortraitKeys = generateButton(mStateGroup, "Enabled", tooltip, SWT.CHECK, true,
                navListener);
        mPortraitKeys.setEnabled(false);
        mPortraitKeysNav = generateButton(mStateGroup, "Navigation",
                "Hardware navigation is available in this state", SWT.CHECK, true, validator);
        mPortraitKeysNav.setEnabled(false);

        tooltip = "The device has a landscape position with the keyboard open";
        mLandscapeKeysLabel = generateLabel("Landscape with keyboard:", tooltip, mStateGroup);
        gridData = new GridData(GridData.FILL_HORIZONTAL);
        gridData.horizontalSpan = 2;
        mLandscapeKeysLabel.setLayoutData(gridData);
        mLandscapeKeysLabel.setEnabled(false);
        mLandscapeKeys = generateButton(mStateGroup, "Enabled", tooltip, SWT.CHECK, true,
                navListener);
        mLandscapeKeys.setEnabled(false);
        mLandscapeKeysNav = generateButton(mStateGroup, "Navigation",
                "Hardware navigation is available in this state", SWT.CHECK, true, validator);
        mLandscapeKeysNav.setEnabled(false);


        mForceCreation = new Button(column2, SWT.CHECK);
        mForceCreation.setText("Override the existing device with the same name");
        mForceCreation.setToolTipText("There's already an AVD with the same name. Check this to delete it and replace it by the new AVD.");
        mForceCreation.setLayoutData(new GridData(GridData.BEGINNING, GridData.CENTER,
                true, false, 2, 1));
        mForceCreation.setEnabled(false);
        mForceCreation.addSelectionListener(validator);


        // -- third row

        // add a separator to separate from the ok/cancel button
        label = new Label(parent, SWT.SEPARATOR | SWT.HORIZONTAL);
        label.setLayoutData(new GridData(GridData.FILL, GridData.CENTER, true, false, 3, 1));

        // add stuff for the error display
        Composite statusComposite = new Composite(parent, SWT.NONE);
        GridLayout gl;
        statusComposite.setLayoutData(
                new GridData(GridData.FILL, GridData.CENTER, true, false, 3, 1));
        statusComposite.setLayout(gl = new GridLayout(2, false));
        gl.marginHeight = gl.marginWidth = 0;

        mStatusIcon = new Label(statusComposite, SWT.NONE);
        mStatusIcon.setLayoutData(new GridData(GridData.BEGINNING, GridData.BEGINNING,
                false, false));
        mStatusLabel = new Label(statusComposite, SWT.NONE);
        mStatusLabel.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
        mStatusLabel.setText(""); //$NON-NLS-1$

        prefillWithDevice(mDevice);

        validatePage();
    }

    private Button generateButton(Composite parent, String text, String tooltip, int type,
            boolean selected, SelectionListener listener) {
        Button b = new Button(parent, type);
        b.setText(text);
        b.setToolTipText(tooltip);
        b.setSelection(selected);
        b.addSelectionListener(listener);
        b.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
        return b;
    }

    /**
     * Generates a combo widget attached to the given parent, then sets the
     * tooltip, adds all of the {@link String}s returned by
     * {@link ResourceEnum#getResourceValue()} for each {@link ResourceEnum},
     * sets the combo to the given index and adds the given
     * {@link ModifyListener}.
     */
    private Combo generateCombo(Composite parent, String tooltip, ResourceEnum[] values,
            int selection,
            ModifyListener validator) {
        Combo c = new Combo(parent, SWT.DROP_DOWN | SWT.READ_ONLY);
        c.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
        c.setToolTipText(tooltip);
        for (ResourceEnum r : values) {
            c.add(r.getResourceValue());
        }
        c.select(selection);
        c.addModifyListener(validator);
        return c;
    }

    /** Generates a text widget with the given tooltip, parent and listener */
    private Text generateText(Composite parent, String tooltip, ModifyListener listener) {
        Text t = new Text(parent, SWT.BORDER);
        t.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
        t.setToolTipText(tooltip);
        t.addModifyListener(listener);
        return t;
    }

    /** Generates a label and attaches it to the given parent */
    private Label generateLabel(String text, String tooltip, Composite parent) {
        Label label = new Label(parent, SWT.NONE);
        label.setText(text);
        label.setToolTipText(tooltip);
        label.setLayoutData(new GridData(GridData.VERTICAL_ALIGN_CENTER));
        return label;
    }

    /**
     * Callback when the device name is changed. Enforces that device names
     * don't conflict with already existing devices unless we're editing that
     * device.
     */
    private class CreateNameModifyListener implements ModifyListener {
        @Override
        public void modifyText(ModifyEvent e) {
            String name = mDeviceName.getText();
            boolean nameCollision = false;
            for (Device d : mUserDevices) {
                if (MANUFACTURER.equals(d.getManufacturer()) && name.equals(d.getName())) {
                    nameCollision = true;
                    break;
                }
            }
            mForceCreation.setEnabled(nameCollision);
            mForceCreation.setSelection(!nameCollision);

            validatePage();
        }
    }

    /**
     * Callback attached to the diagonal length and resolution text boxes. Sets
     * the screen size and display density based on their values, then validates
     * the page.
     */
    private class SizeListener implements ModifyListener {
        @Override
        public void modifyText(ModifyEvent e) {

            if (!mDiagonalLength.getText().isEmpty()) {
                try {
                    double diagonal = Double.parseDouble(mDiagonalLength.getText());
                    double diagonalDp = 160.0 * diagonal;

                    // Set the Screen Size
                    if (diagonalDp >= 1200) {
                        mSize.select(ScreenSize.getIndex(ScreenSize.getEnum("xlarge")));
                    } else if (diagonalDp >= 800) {
                        mSize.select(ScreenSize.getIndex(ScreenSize.getEnum("large")));
                    } else if (diagonalDp >= 568) {
                        mSize.select(ScreenSize.getIndex(ScreenSize.getEnum("normal")));
                    } else {
                        mSize.select(ScreenSize.getIndex(ScreenSize.getEnum("small")));
                    }
                    if (!mXDimension.getText().isEmpty() && !mYDimension.getText().isEmpty()) {

                        // Set the density based on which bucket it's closest to
                        double x = Double.parseDouble(mXDimension.getText());
                        double y = Double.parseDouble(mYDimension.getText());
                        double dpi = Math.sqrt(x * x + y * y) / diagonal;
                        double difference = Double.MAX_VALUE;
                        Density bucket = Density.MEDIUM;
                        for (Density d : Density.values()) {
                            if (Math.abs(d.getDpiValue() - dpi) < difference) {
                                difference = Math.abs(d.getDpiValue() - dpi);
                                bucket = d;
                            }
                        }
                        mDensity.select(Density.getIndex(bucket));
                    }
                } catch (NumberFormatException ignore) {}
            }
        }
    }


    /**
     * Callback attached to the keyboard checkbox.Enables / disables device
     * states based on the keyboard presence and then validates the page.
     */
    private class KeyboardListener extends SelectionAdapter {
        @Override
        public void widgetSelected(SelectionEvent event) {
            super.widgetSelected(event);
            if (mKeyboard.getSelection()) {
                mPortraitKeys.setEnabled(true);
                mPortraitKeysLabel.setEnabled(true);
                mLandscapeKeys.setEnabled(true);
                mLandscapeKeysLabel.setEnabled(true);
            } else {
                mPortraitKeys.setEnabled(false);
                mPortraitKeysLabel.setEnabled(false);
                mLandscapeKeys.setEnabled(false);
                mLandscapeKeysLabel.setEnabled(false);
            }
            toggleNav();
            validatePage();
        }

    }

    /**
     * Listens for changes on widgets that affect nav availability and toggles
     * the nav checkboxes for device states based on them.
     */
    private class NavStateListener extends SelectionAdapter {
        @Override
        public void widgetSelected(SelectionEvent event) {
            super.widgetSelected(event);
            toggleNav();
            validatePage();
        }
    }

    /**
     * Method that inspects all of the relevant dialog state and enables or disables the nav
     * elements accordingly.
     */
    private void toggleNav() {
        mPortraitNav.setEnabled(mPortrait.getSelection() && !mNoNav.getSelection());
        mLandscapeNav.setEnabled(mLandscape.getSelection() && !mNoNav.getSelection());
        mPortraitKeysNav.setEnabled(mPortraitKeys.getSelection() && mPortraitKeys.getEnabled()
                && !mNoNav.getSelection());
        mLandscapeKeysNav.setEnabled(mLandscapeKeys.getSelection()
                && mLandscapeKeys.getEnabled() && !mNoNav.getSelection());
        validatePage();
    }

    /**
     * Callback that validates the page on modification events or widget
     * selections
     */
    private class ValidationListener extends SelectionAdapter implements ModifyListener {
        @Override
        public void modifyText(ModifyEvent e) {
            validatePage();
        }

        @Override
        public void widgetSelected(SelectionEvent e) {
            super.widgetSelected(e);
            validatePage();
        }
    }

    /**
     * Validates all of the config options to ensure a valid device can be
     * created from them.
     *
     * @return Whether the config options will result in a valid device.
     */
    private boolean validatePage() {
        boolean valid = true;
        String error = null;
        String warning = null;
        setError(null);

        String name = mDeviceName.getText();

        /* If we're editing / cloning a device, this will get called when the name gets pre-filled
         * but the ok button won't be populated yet, so we need to skip the initial setting.
         */
        if (mOkButton != null) {
            if (mDevice == null) {
                getShell().setText("Create New Device");
                mOkButton.setText("Create Device");
            } else {
                if (mDevice.getName().equals(name)){
                    if (mUserDevices.contains(mDevice)) {
                        getShell().setText("Edit Device");
                        mOkButton.setText("Edit Device");
                    } else {
                        warning = "Only user created devices are editable.\nA clone of it will be created under " +
                        "the \"User\" category.";
                        getShell().setText("Clone Device");
                        mOkButton.setText("Clone Device");
                    }
                } else {
                    warning = "The device \"" + mDevice.getName() +"\" will be duplicated into\n" +
                            "\"" + name + "\" under the \"User\" category";
                    getShell().setText("Clone Device");
                    mOkButton.setText("Clone Device");
                }
            }
        }

        if (valid && name.isEmpty()) {
            warning = "Please enter a name for the device.";
            valid = false;
        }
        if (valid && !validateFloat("Diagonal Length", mDiagonalLength.getText())) {
            warning = "Please enter a screen size.";
            valid = false;
        }
        if (valid && !validateInt("Resolution", mXDimension.getText())) {
            warning = "Please enter the screen resolution.";
            valid = false;
        }
        if (valid && !validateInt("Resolution", mYDimension.getText())) {
            warning = "Please enter the screen resolution.";
            valid = false;
        }
        if (valid && mSize.getSelectionIndex() < 0) {
            error = "A size bucket must be selected.";
            valid = false;
        }
        if (valid && mDensity.getSelectionIndex() < 0) {
            error = "A screen density bucket must be selected";
            valid = false;
        }
        if (valid && mRatio.getSelectionIndex() < 0) {
            error = "A screen ratio must be selected.";
            valid = false;
        }
        if (valid && !mNoNav.getSelection() && !mTrackball.getSelection() && !mDpad.getSelection()) {
            error = "A mode of hardware navigation, or no navigation, has to be selected.";
            valid = false;
        }
        if (valid && !validateInt("RAM", mRam.getText())) {
            warning = "Please enter a RAM amount.";
            valid = false;
        }
        if (valid && mRamCombo.getSelectionIndex() < 0) {
            error = "RAM must have a selected unit.";
            valid = false;
        }
        if (valid && mButtons.getSelectionIndex() < 0) {
            error = "A button type must be selected.";
            valid = false;
        }
        if (valid) {
            if (mKeyboard.getSelection()) {
                if (!mPortraitKeys.getSelection()
                        && !mPortrait.getSelection()
                        && !mLandscapeKeys.getSelection()
                        && !mLandscape.getSelection()) {
                    error = "At least one device state must be enabled.";
                    valid = false;
                }
            } else {
                if (!mPortrait.getSelection() && !mLandscape.getSelection()) {
                    error = "At least one device state must be enabled";
                    valid = false;
                }
            }
        }
        if (mForceCreation.isEnabled() && !mForceCreation.getSelection()) {
            error = "Name conflicts with an existing device.";
            valid = false;
        }

        if (mOkButton != null) {
            mOkButton.setEnabled(valid);
        }

        if (error != null) {
            setError(error);
        } else if (warning != null) {
            setWarning(warning);
        }

        return valid;
    }

    /**
     * Validates the string is a valid, positive float. If not, it sets the
     * error at the bottom of the dialog and returns false. Note this does
     * <b>not</b> unset the error message, it's up to the caller to unset it iff
     * it knows there are no errors on the page.
     */
    private boolean validateFloat(String box, String value) {
        if (value == null || value.isEmpty()) {
            return false;
        }
        boolean ret = true;
        try {
            double val = Double.parseDouble(value);
            if (val <= 0) {
                ret = false;
            }
        } catch (NumberFormatException e) {
            ret = false;
        }
        if (!ret) {
            setError(box + " must be a valid, positive number.");
        }
        return ret;
    }

    /**
     * Validates the string is a valid, positive integer. If not, it sets the
     * error at the bottom of the dialog and returns false. Note this does
     * <b>not</b> unset the error message, it's up to the caller to unset it iff
     * it knows there are no errors on the page.
     */
    private boolean validateInt(String box, String value) {
        if (value == null || value.isEmpty()) {
            return false;
        }
        boolean ret = true;
        try {
            int val = Integer.parseInt(value);
            if (val <= 0) {
                ret = false;
            }
        } catch (NumberFormatException e) {
            ret = false;
        }

        if (!ret) {
            setError(box + " must be a valid, positive integer.");
        }

        return ret;
    }

    /**
     * Sets the error to the given string. If null, removes the error message.
     */
    private void setError(@Nullable String error) {
        if (error == null) {
            mStatusIcon.setImage(null);
            mStatusLabel.setText("");
        } else {
            mStatusIcon.setImage(mImageFactory.getImageByName("reject_icon16.png"));
            mStatusLabel.setText(error);
        }
    }

    /**
     * Sets the warning message to the given string. If null, removes the
     * warning message.
     */
    private void setWarning(@Nullable String warning) {
        if (warning == null) {
            mStatusIcon.setImage(null);
            mStatusLabel.setText("");
        } else {
            mStatusIcon.setImage(mImageFactory.getImageByName("warning_icon16.png"));
            mStatusLabel.setText(warning);
        }
    }

    /** Sets the hardware for the new device */
    private void prefillWithDevice(@Nullable Device device) {
        if (device == null) {

            // Setup the default hardware instance with reasonable values for
            // the things which are configurable via this dialog.
            mHardware = new Hardware();

            Screen s = new Screen();
            s.setXdpi(316);
            s.setYdpi(316);
            s.setMultitouch(Multitouch.JAZZ_HANDS);
            s.setMechanism(TouchScreen.FINGER);
            s.setScreenType(ScreenType.CAPACITIVE);
            mHardware.setScreen(s);

            mHardware.addNetwork(Network.BLUETOOTH);
            mHardware.addNetwork(Network.WIFI);
            mHardware.addNetwork(Network.NFC);

            mHardware.addSensor(Sensor.BAROMETER);
            mHardware.addSensor(Sensor.COMPASS);
            mHardware.addSensor(Sensor.LIGHT_SENSOR);

            mHardware.setHasMic(true);
            mHardware.addInternalStorage(new Storage(4, Storage.Unit.GiB));
            mHardware.setCpu("Generic CPU");
            mHardware.setGpu("Generic GPU");

            mHardware.addSupportedAbi(Abi.ARMEABI);
            mHardware.addSupportedAbi(Abi.ARMEABI_V7A);
            mHardware.addSupportedAbi(Abi.MIPS);
            mHardware.addSupportedAbi(Abi.X86);

            mHardware.setChargeType(PowerType.BATTERY);
            return;
        }
        mHardware = device.getDefaultHardware().deepCopy();
        mDeviceName.setText(device.getName());
        mForceCreation.setSelection(true);
        Screen s = mHardware.getScreen();
        mDiagonalLength.setText(Double.toString(s.getDiagonalLength()));
        mXDimension.setText(Integer.toString(s.getXDimension()));
        mYDimension.setText(Integer.toString(s.getYDimension()));
        String size = s.getSize().getResourceValue();
        for (int i = 0; i < mSize.getItemCount(); i++) {
            if (size.equals(mSize.getItem(i))) {
                mSize.select(i);
                break;
            }
        }
        String ratio = s.getRatio().getResourceValue();
        for (int i = 0; i < mRatio.getItemCount(); i++) {
            if (ratio.equals(mRatio.getItem(i))) {
                mRatio.select(i);
                break;
            }
        }
        String density = s.getPixelDensity().getResourceValue();
        for (int i = 0; i < mDensity.getItemCount(); i++) {
            if (density.equals(mDensity.getItem(i))) {
                mDensity.select(i);
                break;
            }
        }
        mKeyboard.setSelection(!Keyboard.NOKEY.equals(mHardware.getKeyboard()));
        mDpad.setSelection(Navigation.DPAD.equals(mHardware.getNav()));
        mTrackball.setSelection(Navigation.TRACKBALL.equals(mHardware.getNav()));
        mNoNav.setSelection(Navigation.NONAV.equals(mHardware.getNav()));
        mAccelerometer.setSelection(mHardware.getSensors().contains(Sensor.ACCELEROMETER));
        mGyro.setSelection(mHardware.getSensors().contains(Sensor.GYROSCOPE));
        mGps.setSelection(mHardware.getSensors().contains(Sensor.GPS));
        mProximitySensor.setSelection(mHardware.getSensors().contains(Sensor.PROXIMITY_SENSOR));
        mCameraFront.setSelection(false);
        mCameraRear.setSelection(false);
        for (Camera c : mHardware.getCameras()) {
            if (CameraLocation.FRONT.equals(c.getLocation())) {
                mCameraFront.setSelection(true);
            } else if (CameraLocation.BACK.equals(c.getLocation())) {
                mCameraRear.setSelection(true);
            }
        }
        mRam.setText(Long.toString(mHardware.getRam().getSizeAsUnit(Storage.Unit.MiB)));
        mRamCombo.select(0);

        for (int i = 0; i < mButtons.getItemCount(); i++) {
            if (mButtons.getItem(i).equals(mHardware.getButtonType().getDescription())) {
                mButtons.select(i);
                break;
            }
        }

        for (State state : device.getAllStates()) {
            Button nav = null;
            if (state.getOrientation().equals(ScreenOrientation.PORTRAIT)) {
                if (state.getKeyState().equals(KeyboardState.EXPOSED)) {
                    mPortraitKeys.setSelection(true);
                    nav = mPortraitKeysNav;
                } else {
                    mPortrait.setSelection(true);
                    nav = mPortraitNav;
                }
            } else {
                if (state.getKeyState().equals(KeyboardState.EXPOSED)) {
                    mLandscapeKeys.setSelection(true);
                    nav = mLandscapeKeysNav;
                } else {
                    mLandscape.setSelection(true);
                    nav = mLandscapeNav;
                }
            }
            nav.setSelection(state.getNavState().equals(NavigationState.EXPOSED)
                    && !mHardware.getNav().equals(Navigation.NONAV));
        }
    }

    /**
     * If given a valid page, generates the corresponding device. The device is
     * then added to the user device list, replacing any previous device with
     * its given name and manufacturer, and the list is saved out to disk.
     */
    @Override
    protected void okPressed() {
        if (validatePage()) {
            Device.Builder builder = new Device.Builder();
            builder.setManufacturer("User");
            builder.setName(mDeviceName.getText());
            builder.addSoftware(mSoftware);
            Screen s = mHardware.getScreen();
            double diagonal = Double.parseDouble(mDiagonalLength.getText());
            int x = Integer.parseInt(mXDimension.getText());
            int y = Integer.parseInt(mYDimension.getText());
            s.setDiagonalLength(diagonal);
            s.setXDimension(x);
            s.setYDimension(y);
            // The diagonal DPI will be somewhere in between the X and Y dpi if
            // they differ
            double dpi = Math.sqrt(x * x + y * y) / diagonal;
            s.setXdpi(dpi);
            s.setYdpi(dpi);
            s.setPixelDensity(Density.getEnum(mDensity.getText()));
            s.setSize(ScreenSize.getEnum(mSize.getText()));
            s.setRatio(ScreenRatio.getEnum(mRatio.getText()));
            if (mAccelerometer.getSelection()) {
                mHardware.addSensor(Sensor.ACCELEROMETER);
            }
            if (mGyro.getSelection()) {
                mHardware.addSensor(Sensor.GYROSCOPE);
            }
            if (mGps.getSelection()) {
                mHardware.addSensor(Sensor.GPS);
            }
            if (mProximitySensor.getSelection()) {
                mHardware.addSensor(Sensor.PROXIMITY_SENSOR);
            }
            if (mCameraFront.getSelection()) {
                Camera c = new Camera();
                c.setAutofocus(true);
                c.setFlash(true);
                c.setLocation(CameraLocation.FRONT);
                mHardware.addCamera(c);
            }
            if (mCameraRear.getSelection()) {
                Camera c = new Camera();
                c.setAutofocus(true);
                c.setFlash(true);
                c.setLocation(CameraLocation.BACK);
                mHardware.addCamera(c);
            }
            if (mKeyboard.getSelection()) {
                mHardware.setKeyboard(Keyboard.QWERTY);
            } else {
                mHardware.setKeyboard(Keyboard.NOKEY);
            }
            if (mDpad.getSelection()) {
                mHardware.setNav(Navigation.DPAD);
            } else if (mTrackball.getSelection()) {
                mHardware.setNav(Navigation.TRACKBALL);
            } else {
                mHardware.setNav(Navigation.NONAV);
            }
            long ram = Long.parseLong(mRam.getText());
            Storage.Unit unit = Storage.Unit.getEnum(mRamCombo.getText());
            mHardware.setRam(new Storage(ram, unit));
            if (mButtons.getText().equals(ButtonType.HARD.getDescription())) {
                mHardware.setButtonType(ButtonType.HARD);
            } else {
                mHardware.setButtonType(ButtonType.SOFT);
            }

            // Set the first enabled state to the default state
            boolean defaultSelected = false;
            if (mPortrait.getSelection()) {
                State state = new State();
                state.setName("Portrait");
                state.setDescription("The device in portrait orientation");
                state.setOrientation(ScreenOrientation.PORTRAIT);
                if (mHardware.getNav().equals(Navigation.NONAV) || !mPortraitNav.getSelection()) {
                    state.setNavState(NavigationState.HIDDEN);
                } else {
                    state.setNavState(NavigationState.EXPOSED);
                }
                if (mHardware.getKeyboard().equals(Keyboard.NOKEY)) {
                    state.setKeyState(KeyboardState.SOFT);
                } else {
                    state.setKeyState(KeyboardState.HIDDEN);
                }
                state.setHardware(mHardware);
                if (!defaultSelected) {
                    state.setDefaultState(true);
                    defaultSelected = true;
                }
                builder.addState(state);
            }
            if (mLandscape.getSelection()) {
                State state = new State();
                state.setName("Landscape");
                state.setDescription("The device in landscape orientation");
                state.setOrientation(ScreenOrientation.LANDSCAPE);
                if (mHardware.getNav().equals(Navigation.NONAV) || !mLandscapeNav.getSelection()) {
                    state.setNavState(NavigationState.HIDDEN);
                } else {
                    state.setNavState(NavigationState.EXPOSED);
                }
                if (mHardware.getKeyboard().equals(Keyboard.NOKEY)) {
                    state.setKeyState(KeyboardState.SOFT);
                } else {
                    state.setKeyState(KeyboardState.HIDDEN);
                }
                state.setHardware(mHardware);
                if (!defaultSelected) {
                    state.setDefaultState(true);
                    defaultSelected = true;
                }
                builder.addState(state);
            }
            if (mKeyboard.getSelection()) {
                if (mPortraitKeys.getSelection()) {
                    State state = new State();
                    state.setName("Portrait with keyboard");
                    state.setDescription("The device in portrait orientation with a keyboard open");
                    state.setOrientation(ScreenOrientation.LANDSCAPE);
                    if (mHardware.getNav().equals(Navigation.NONAV)
                            || !mPortraitKeysNav.getSelection()) {
                        state.setNavState(NavigationState.HIDDEN);
                    } else {
                        state.setNavState(NavigationState.EXPOSED);
                    }
                    state.setKeyState(KeyboardState.EXPOSED);
                    state.setHardware(mHardware);
                    if (!defaultSelected) {
                        state.setDefaultState(true);
                        defaultSelected = true;
                    }
                    builder.addState(state);
                }
                if (mLandscapeKeys.getSelection()) {
                    State state = new State();
                    state.setName("Landscape with keyboard");
                    state.setDescription("The device in landscape orientation with a keyboard open");
                    state.setOrientation(ScreenOrientation.LANDSCAPE);
                    if (mHardware.getNav().equals(Navigation.NONAV)
                            || !mLandscapeKeysNav.getSelection()) {
                        state.setNavState(NavigationState.HIDDEN);
                    } else {
                        state.setNavState(NavigationState.EXPOSED);
                    }
                    state.setKeyState(KeyboardState.EXPOSED);
                    state.setHardware(mHardware);
                    if (!defaultSelected) {
                        state.setDefaultState(true);
                        defaultSelected = true;
                    }
                    builder.addState(state);
                }
            }
            Device d = builder.build();
            if (mForceCreation.isEnabled() && mForceCreation.getSelection()) {
                mManager.replaceUserDevice(d);
            } else {
                mManager.addUserDevice(d);
            }
            mManager.saveUserDevices();
            mCreatedDevice = d;
            super.okPressed();
        }
    }

}
