/*
* 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.motorola.studio.android.emulator.core.emulationui;

import static com.motorola.studio.android.common.log.StudioLogger.debug;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.osgi.util.NLS;
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.graphics.FontMetrics;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;

import com.motorola.studio.android.emulator.core.devfrm.DeviceFrameworkManager;
import com.motorola.studio.android.emulator.core.model.IAndroidEmulatorInstance;
import com.motorola.studio.android.emulator.i18n.EmulatorNLS;

/**
 * DESCRIPTION:
 * This is a composite that is used by several emulation UI elements to choose source and
 * destination elements 
 *
 * RESPONSIBILITY:
 * Provide means for the user to choose which emulator and phone number will be involved
 * in a emulation
 *
 * COLABORATORS:
 * None.
 *
 * USAGE:
 * Add the composite to a UI element that needs to have a emulator and a phone number
 * chosen by the user
 */
public class SrcDestComposite extends Composite
{
    /**
     * Message that will be shown near the emulator combo 
     */
    private String emulatorLabelStr;

    /**
     * Message that will be shown near the phone number text field
     */
    private String phoneNumberLabelStr;

    /**
     * Emulator currently selected
     */
    private String selectedEmulator;

    /**
     * Phone number currently selected
     */
    private String selectedPhoneNumber;

    /**
     * True if the composite is valid and can provide information to the user class
     * False if not.
     */
    private boolean isValid = false;

    /**
     * Error message to be shown to the user if the composite data is not valid 
     */
    private String errorMessage =
            NLS.bind(EmulatorNLS.ERR_SrcDestComposite_InvalidFillingBase,
                    EmulatorNLS.ERR_SrcDestComposite_InvalidFillingPhoneNumber,
                    EmulatorNLS.ERR_SrcDestComposite_InvalidFillingEmulator);

    // Widgets
    private Combo runningEmulatorsCombo;

    private Text phoneNumberText;

    // attribute for calculating label sizes (for layout purposes)
    private FontMetrics fontMetrics = null;

    /**
     * Constructor. 
     * 
     * @param parent The parent composite of this one
     * @param style Style of the composite. See constants at SWT class
     * @param showSrcControls True if this composite should show 
     *                        the emulation source controls. False otherwise
     * @param isEmulatorSrc True if this composite will have the emulator 
     *                      part as source in emulation. False if the phone number 
     *                      will be the source
     */
    public SrcDestComposite(Composite parent, int style, boolean showSrcControls,
            boolean isEmulatorSrc)
    {
        super(parent, style);

        GridLayout layout = new GridLayout(2, false);
        layout.marginHeight = 5;
        layout.marginWidth = 5;
        layout.verticalSpacing = 5;
        layout.horizontalSpacing = 2;
        this.setLayout(layout);
        GridData data = new GridData(SWT.FILL, SWT.FILL, true, true);
        this.setLayoutData(data);

        // initialize font metrics
        GC gc = new GC(this);
        gc.setFont(this.getFont());
        fontMetrics = gc.getFontMetrics();
        gc.dispose();

        if (isEmulatorSrc)
        {
            // When emulator is the source part, its UI is build prior to phone number UI, 
            // and appropriate labels are used for both
            debug("Using emulator as source");
            emulatorLabelStr = EmulatorNLS.UI_SrcDestComposite_OriginatingRunningEmulatorLabel;
            phoneNumberLabelStr = EmulatorNLS.UI_SrcDestComposite_DestinationPhoneNumberLabel;
            if (showSrcControls)
            {
                debug("Showing source controls");
                createEmulatorUI();
            }
            createPhoneNumberUI();
        }
        else
        {
            // When phone number is the source part, its UI is build prior to emulator UI, 
            // and appropriate labels are used for both
            debug("Using phone number as source");
            emulatorLabelStr = EmulatorNLS.UI_SrcDestComposite_DestinationRunningEmulatorLabel;
            phoneNumberLabelStr = EmulatorNLS.UI_SrcDestComposite_OriginatingPhoneNumberLabel;
            if (showSrcControls)
            {
                debug("Showing source controls");
                createPhoneNumberUI();
            }
            createEmulatorUI();
        }

        addListeners();

        // call the check method to refresh error message.
        checkData();

    }

    /**
     * Build the emulator part controls
     */
    private void createEmulatorUI()
    {

        Label runningEmulatorsLabel = new Label(this, SWT.NONE);
        runningEmulatorsLabel.setText(emulatorLabelStr);
        GridData data = new GridData(SWT.FILL, SWT.CENTER, false, false);
        data.widthHint = getLabelWidthHint(runningEmulatorsLabel);
        runningEmulatorsLabel.setLayoutData(data);

        this.runningEmulatorsCombo = new Combo(this, SWT.BORDER | SWT.READ_ONLY);
        data = new GridData(SWT.FILL, SWT.FILL, true, false);
        this.runningEmulatorsCombo.setLayoutData(data);
        populateEmulatorCombo();

    }

    /**
     * Build the phone number part controls
     */
    private void createPhoneNumberUI()
    {

        Label phoneNumberLabel = new Label(this, SWT.NONE);
        phoneNumberLabel.setText(phoneNumberLabelStr);
        GridData data = new GridData(SWT.FILL, SWT.CENTER, false, false);
        data.widthHint = getLabelWidthHint(phoneNumberLabel);
        phoneNumberLabel.setLayoutData(data);

        this.phoneNumberText = new Text(this, SWT.BORDER);
        data = new GridData(SWT.FILL, SWT.FILL, true, false);
        this.phoneNumberText.setLayoutData(data);
        this.phoneNumberText.setTextLimit(40);

    }

    /**
     * Add listeners to the composite controls
     */
    private void addListeners()
    {

        if (runningEmulatorsCombo != null)
        {
            runningEmulatorsCombo.addSelectionListener(new SelectionAdapter()
            {
                @Override
                public void widgetSelected(SelectionEvent e)
                {
                    selectedEmulator = getCurrentlySelectedIdentifier();
                    checkData();
                }
            });
        }

        if (phoneNumberText != null)
        {
            phoneNumberText.addModifyListener(new ModifyListener()
            {
                public void modifyText(ModifyEvent e)
                {
                    selectedPhoneNumber = phoneNumberText.getText();
                    checkData();
                }
            });
        }
    }

    /**
     * Defines the width hint to be used for the given label on a GridData object.
     * 
     * @param label the label to calculate the width hint for
     * 
     * @return the width hint
     */
    private int getLabelWidthHint(Label label)
    {
        int widthHint = Dialog.convertHorizontalDLUsToPixels(fontMetrics, label.getText().length());
        return Math.max(widthHint, label.computeSize(SWT.DEFAULT, SWT.DEFAULT, true).x);
    }

    /**
     * Populates the emulator combo box with the currently running emulators
     */
    private void populateEmulatorCombo()
    {

        // Populating emulator combo with all running emulator names
        // Besides, keeping an array of the identifiers as combo data.
        Map<String, String> identifiersAndNames = new HashMap<String, String>();
        Collection<IAndroidEmulatorInstance> startedInstances =
                DeviceFrameworkManager.getInstance().getAllStartedInstances();
        for (IAndroidEmulatorInstance instance : startedInstances)
        {
            identifiersAndNames.put(instance.getInstanceIdentifier(), instance.getName());
        }

        String[] instanceNamesArray = new String[identifiersAndNames.size()];
        String[] identifiersArray = new String[identifiersAndNames.size()];
        int i = 0;

        Set<String> identifiers = identifiersAndNames.keySet();
        for (String identifier : identifiers)
        {

            String viewerName = identifiersAndNames.get(identifier);

            // It is VERY important that the index used at the data array is equal to the
            // index used at the items array. According to the selected item in the combo, the
            // corresponding identifier is retrieved from the data array in the future
            instanceNamesArray[i] = viewerName;
            identifiersArray[i] = identifier;
            i++;
        }

        runningEmulatorsCombo.setItems(instanceNamesArray);
        runningEmulatorsCombo.setData(identifiersArray);

        // if there is just one emulator in the combo list, 
        // it will be chose by default.
        if (runningEmulatorsCombo.getItemCount() == 1)
        {
            runningEmulatorsCombo.select(0);
            selectedEmulator = getCurrentlySelectedIdentifier();
            checkData();
        }

    }

    /**
     * Retrieve the identifier of the selected instance at Android Emulator combo box
     * 
     * @return The identifier, or an empty string if no emulator is selected
     */
    private String getCurrentlySelectedIdentifier()
    {

        String currentlySelectedSerial = "";
        int index = runningEmulatorsCombo.getSelectionIndex();

        if (index >= 0)
        {
            String[] serials = (String[]) runningEmulatorsCombo.getData();
            currentlySelectedSerial = serials[index];

        }

        return currentlySelectedSerial;
    }

    /**
     * Get the emulator identifier that was selected by the user
     * 
     * @return The selected emulator identifier
     */
    public String getSelectedEmulator()
    {
        return selectedEmulator;
    }

    /**
     * Get the phone number that was typed by the user
     * 
     * @return The phone number typed by the user
     */
    public String getSelectedPhoneNumber()
    {
        return selectedPhoneNumber;
    }

    /**
     * Tests if the values chosen/typed by the user are valid
     * By invoking this method, the user class is able to know if it can proceed
     * 
     * @return True if the user has chosen valid values. False otherwise
     */
    public boolean isValid()
    {
        return isValid;
    }

    /**
     * Retrieves the error message to be shown to the user if the composite is
     * not valid
     * 
     * @return The error message if the composite is not valid, or <code>null</code> if
     * the composite is valid and no error message should be displayed.
     */
    public String getErrorMessage()
    {
        return errorMessage;
    }

    /**
     * Check if the data entered by the user is correct and set instance variables
     * to store the test results
     */
    private void checkData()
    {

        isValid = false;

        boolean isEmulatorValid = false;
        boolean isPhoneNumberValid = false;

        boolean isUsingPhoneNumber = (phoneNumberText != null);
        boolean isUsingEmulator = (runningEmulatorsCombo != null);

        // Tests if emulator selection is valid.
        //
        // If the emulator combo is null, that means that the user decided not to use it. In 
        // this case, it will always be valid. Otherwise, the combo selection needs to be
        // not null and not blank
        if ((!isUsingEmulator) || ((selectedEmulator != null) && (!selectedEmulator.equals(""))))
        {
            isEmulatorValid = true;
        }

        // Tests if phone number selection is valid.
        //
        // If the phone number text is null, that means that the user decided not to use it. In 
        // this case, it will always be valid. Otherwise, the text field selection needs to be
        // not null, not blank and can be parsed to double (that means that the contents are 
        // composed by numerals only)
        if (!isUsingPhoneNumber)
        {
            isPhoneNumberValid = true;
        }
        else if ((selectedPhoneNumber != null) && (!selectedPhoneNumber.equals("")))
        {
            Pattern p = Pattern.compile("(\\d)+");
            Matcher m = p.matcher(selectedPhoneNumber);
            isPhoneNumberValid = m.matches();
        }

        // Based on previous checks, determine if the composite state is valid
        if (isEmulatorValid && isPhoneNumberValid)
        {
            isValid = true;
            errorMessage = null;
        }
        else
        {
            // If not valid, an error message will be shown. The following calculations
            // are for determining which error has happened to build the message
            String phoneNumberError = "";
            String emulatorError = "";

            if (isUsingPhoneNumber && (!isPhoneNumberValid))
            {
                phoneNumberError = EmulatorNLS.ERR_SrcDestComposite_InvalidFillingPhoneNumber;
            }
            if (isUsingEmulator && (!isEmulatorValid))
            {
                emulatorError = EmulatorNLS.ERR_SrcDestComposite_InvalidFillingEmulator;
            }

            errorMessage =
                    NLS.bind(EmulatorNLS.ERR_SrcDestComposite_InvalidFillingBase,
                            phoneNumberError, emulatorError);
        }

    }
}
