/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.apache.uima.taeconfigurator.editors.ui;

import java.util.Arrays;

import org.apache.uima.resource.metadata.ConfigurationGroup;
import org.apache.uima.resource.metadata.ConfigurationParameter;
import org.apache.uima.resource.metadata.ConfigurationParameterSettings;
import org.apache.uima.taeconfigurator.InternalErrorCDE;
import org.apache.uima.taeconfigurator.editors.MultiPageEditor;
import org.apache.uima.taeconfigurator.editors.ui.dialogs.CommonInputDialog;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CCombo;
import org.eclipse.swt.custom.StackLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.forms.IManagedForm;

/**
 * The Class ValueSection.
 */
public class ValueSection extends AbstractSectionParm {

  /** The value text stack. */
  private Composite valueTextStack;

  /** The value text stack layout. */
  private StackLayout valueTextStackLayout;

  /** The value text. */
  private Text valueText;

  /** The value text combo. */
  private CCombo valueTextCombo;

  /** The vtc 1. */
  private Composite vtc1;

  /** The vtc 2. */
  private Composite vtc2;

  /** The value table. */
  private Table valueTable;

  /** The master. */
  private ParameterSettingsSection master;

  /** The add button. */
  private Button addButton;

  /** The edit button. */
  private Button editButton;

  /** The remove button. */
  private Button removeButton;

  /** The up button. */
  private Button upButton;

  /** The down button. */
  private Button downButton;

  /** The button container. */
  private Composite buttonContainer;

  /** The selected CP. */
  private ConfigurationParameter selectedCP;

  /** The model settings. */
  private ConfigurationParameterSettings modelSettings;

  /**
   * Instantiates a new value section.
   *
   * @param editor
   *          the editor
   * @param parent
   *          the parent
   */
  public ValueSection(MultiPageEditor editor, Composite parent) {
    super(editor, parent, "Values", "Specify the value of the selected configuration parameter.");
  }

  /*
   * Called by the page constructor after all sections are created, to initialize them.
   * (non-Javadoc)
   * 
   * @see org.eclipse.ui.forms.IFormPart#initialize(org.eclipse.ui.forms.IManagedForm)
   */
  @Override
  public void initialize(IManagedForm form) {
    super.initialize(form);

    master = editor.getSettingsPage().getParameterSettingsSection();

    Composite sectionClient = new3ColumnComposite(this.getSection());
    enableBorders(sectionClient);
    toolkit.paintBordersFor(sectionClient);

    valueTextStack = newComposite(sectionClient);
    valueTextStack
            .setLayoutData(new GridData(GridData.FILL_HORIZONTAL + GridData.VERTICAL_ALIGN_FILL));
    ((GridData) valueTextStack.getLayoutData()).horizontalSpan = 2;
    valueTextStack.setLayout(valueTextStackLayout = new StackLayout());
    valueTextStackLayout.marginHeight = 5;
    valueTextStackLayout.marginWidth = 5;
    vtc1 = new2ColumnComposite(valueTextStack);
    vtc2 = new2ColumnComposite(valueTextStack);
    enableBorders(vtc1);
    enableBorders(vtc2);
    toolkit.paintBordersFor(vtc1);
    toolkit.paintBordersFor(vtc2);
    valueText = newLabeledTextField(vtc1, "Value", "Specify the value", SWT.NONE);
    valueTextCombo = newLabeledCComboWithTip(vtc2, "Value",
            "Use the combo pulldown to pick True or False");
    valueTextCombo.add("true");
    valueTextCombo.add("false");

    spacer(sectionClient);

    Label valueListLabel = toolkit.createLabel(sectionClient, "Value list:");
    valueListLabel.setLayoutData(new GridData(SWT.TOP));
    valueTable = newTable(sectionClient, SWT.MULTI, 0);

    // no column spec in table is an idiom that makes it a fancy list

    // Buttons
    buttonContainer = newButtonContainer(sectionClient);

    addButton = newPushButton(buttonContainer, S_ADD, "Click here to add a value to the list.");
    editButton = newPushButton(buttonContainer, S_EDIT, S_EDIT_TIP);
    removeButton = newPushButton(buttonContainer, S_REMOVE, S_REMOVE_TIP);
    upButton = newPushButton(buttonContainer, S_UP, S_UP_TIP);
    downButton = newPushButton(buttonContainer, S_DOWN, S_DOWN_TIP);
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.eclipse.ui.forms.IFormPart#refresh()
   */
  @Override
  public void refresh() {
    super.refresh();
    master = editor.getSettingsPage().getParameterSettingsSection();
    valueTextStackLayout.topControl = vtc1;

    selectedCP = master.getSelectedModelParameter();

    if (selectedCP == null) { // no param selected
      valueText.setText("");
      valueTable.removeAll();
    } else { // parm selected in master view
      Object modelValue;
      modelSettings = getModelSettings();
      String groupName = master.getSelectedParamGroupName();
      String parmName = selectedCP.getName();

      modelValue = (NOT_IN_ANY_GROUP.equals(groupName)) ? modelSettings.getParameterValue(parmName)
              : modelSettings.getParameterValue(groupName, parmName);

      if (selectedCP.isMultiValued()) {
        // use list, not text field
        valueText.setText("");
        valueTable.removeAll();
        if (modelValue != null && modelValue instanceof Object[]) {
          Object[] valArr = (Object[]) modelValue;
          for (int i = 0; i < valArr.length; i++) {
            TableItem item = new TableItem(valueTable, SWT.NONE);
            item.setText(valArr[i].toString());
          }
        }
      } else { // single-valued parameter - use Text field
        valueTable.removeAll();
        valueText.setText((modelValue == null) ? "" : modelValue.toString());
        if ("Boolean".equals(selectedCP.getType())) {
          valueTextCombo.setText((modelValue == null) ? "" : modelValue.toString());
          valueTextStackLayout.topControl = vtc2;
        } else {
          valueText.setText((modelValue == null) ? "" : modelValue.toString());
          valueTextStackLayout.topControl = vtc1;
        }
      }
    }
    valueTextStack.layout();
    enable();
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.apache.uima.taeconfigurator.editors.ui.AbstractSectionParm#enable()
   */
  @Override
  public void enable() {

    boolean mvValue = (null != selectedCP) && (selectedCP.isMultiValued());
    valueText.setVisible((null != selectedCP) && (!selectedCP.isMultiValued()));
    valueTextCombo.setVisible((null != selectedCP) && (!selectedCP.isMultiValued()));

    addButton.setEnabled(mvValue);
    int selected = valueTable.getSelectionIndex();
    editButton.setEnabled(mvValue && selected > -1);
    removeButton.setEnabled(mvValue && selected > -1);
    upButton.setEnabled(mvValue && selected > 0);
    downButton
            .setEnabled(mvValue && (selected > -1) && (selected < (valueTable.getItemCount() - 1)));
    valueText.getParent().redraw();
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.eclipse.swt.widgets.Listener#handleEvent(org.eclipse.swt.widgets.Event)
   */
  @Override
  public void handleEvent(Event event) {

    if (event.widget == valueText) {
      setParmValue(valueText.getText());
    }

    else if (event.widget == valueTextCombo) {
      setParmValue(valueTextCombo.getText());
    }

    else if (event.widget == addButton) {
      // open dialog to enter value
      String dataType = selectedCP.getType();
      int validationFilter = "Boolean".equals(dataType) ? CommonInputDialog.TRUE_FALSE
              : "Integer".equals(dataType) ? CommonInputDialog.INTEGER
                      : "Float".equals(dataType) ? CommonInputDialog.FLOAT
                              : CommonInputDialog.ALLOK;

      CommonInputDialog dialog = new CommonInputDialog(this, "Add value", "Enter a value",
              validationFilter);

      if (dialog.open() == Window.CANCEL) {
        return;
      }
      TableItem item = new TableItem(valueTable, SWT.NONE);
      item.setText(dialog.getValue());
      // update model
      setCurrentParameterValue(valueTable.getItems());
    }

    else if (event.widget == editButton) {
      // open dialog to enter value
      TableItem item = valueTable.getItems()[valueTable.getSelectionIndex()];
      CommonInputDialog dialog = new CommonInputDialog(this, "Add value", "Enter a value",
              CommonInputDialog.ALLOK, item.getText());

      if (dialog.open() == Window.CANCEL) {
        return;
      }

      item.setText(dialog.getValue());
      // update model
      setCurrentParameterValue(valueTable.getItems());
    }

    else if (event.widget == upButton) {
      // update both model and gui: swap nodes
      int selection = valueTable.getSelectionIndex();
      TableItem[] items = valueTable.getItems();
      String temp = items[selection - 1].getText();
      items[selection - 1].setText(items[selection].getText());
      items[selection].setText(temp);
      valueTable.setSelection(selection - 1);
      setCurrentParameterValue(valueTable.getItems());
    } else if (event.widget == downButton) {
      // update both model and gui: swap nodes
      int selection = valueTable.getSelectionIndex();
      TableItem[] items = valueTable.getItems();
      String temp = items[selection + 1].getText();
      items[selection + 1].setText(items[selection].getText());
      items[selection].setText(temp);
      valueTable.setSelection(selection + 1);
      setCurrentParameterValue(valueTable.getItems());
    }

    else if (event.widget == removeButton
            || (event.widget == valueTable && event.character == SWT.DEL)) {
      handleRemove(event);
    }

    enable();
  }

  /**
   * Sets the parm value.
   *
   * @param value
   *          the new parm value
   */
  private void setParmValue(String value) {
    if (null != value) {
      if ("".equals(value)) {
        value = null; // means clear the value
      }
      setCurrentParameterValue(value);
    }
  }

  /**
   * Handle remove.
   *
   * @param event
   *          the event
   */
  public void handleRemove(Event event) {
    valueTable.remove(valueTable.getSelectionIndices());
    // update model
    setCurrentParameterValue(valueTable.getItems());
  }

  /**
   * Gets the adds the button.
   *
   * @return the adds the button
   */
  public Button getAddButton() {
    return addButton;
  }

  /**
   * Gets the removes the button.
   *
   * @return the removes the button
   */
  public Button getRemoveButton() {
    return removeButton;
  }

  /**
   * Gets the value table.
   *
   * @return the value table
   */
  public Table getValueTable() {
    return valueTable;
  }

  /**
   * Gets the value text.
   *
   * @return the value text
   */
  public Text getValueText() {
    return valueText;
  }

  /**
   * Sets the currently selected parameter to the specified value. The string value will be
   * converted to the appropriate data type. This method works only for single-valued parameters.
   *
   * @param aValueString
   *          the new current parameter value
   */
  private void setCurrentParameterValue(String aValueString) {

    Object value = null;
    if (null != aValueString) {
      String paramType = selectedCP.getType();
      try {
        if (ConfigurationParameter.TYPE_STRING.equals(paramType)) {
          value = aValueString;
        } else if (ConfigurationParameter.TYPE_INTEGER.equals(paramType)) {
          value = Integer.valueOf(aValueString);
        } else if (ConfigurationParameter.TYPE_LONG.equals(paramType)) {
          value = Long.valueOf(aValueString);
        } else if (ConfigurationParameter.TYPE_FLOAT.equals(paramType)) {
          value = Float.valueOf(aValueString);
        } else if (ConfigurationParameter.TYPE_DOUBLE.equals(paramType)) {
          value = Double.valueOf(aValueString);
        } else if (ConfigurationParameter.TYPE_BOOLEAN.equals(paramType)) {
          value = Boolean.valueOf(aValueString);
        }
      } catch (NumberFormatException e) {
        Utility.popMessage("Invalid Number",
                "If typing a floating point exponent, please complete the exponent.\nOtherwise, please retype the proper kind of number",
                MessageDialog.ERROR);
        return;
      }
    }
    setModelValue(value);
  }

  /**
   * Sets the currently selected parameter to the specified value. This method works only for
   * multi-valued parameters. The Table Items will be converted to the appropriate data type.
   * 
   * @param aValues
   *          Table Items, one for each value of the multi-valued param
   */
  private void setCurrentParameterValue(TableItem[] aValues) {

    Object[] valueArr = null;
    String paramType = selectedCP.getType();
    try {
      if (ConfigurationParameter.TYPE_STRING.equals(paramType)) {
        valueArr = new String[aValues.length];
        for (int i = 0; i < valueArr.length; i++) {
          valueArr[i] = aValues[i].getText();
        }
      } else if (ConfigurationParameter.TYPE_INTEGER.equals(paramType)) {
        valueArr = new Integer[aValues.length];
        for (int i = 0; i < valueArr.length; i++) {
          valueArr[i] = Integer.valueOf(aValues[i].getText());
        }
      } else if (ConfigurationParameter.TYPE_LONG.equals(paramType)) {
        valueArr = new Long[aValues.length];
        for (int i = 0; i < valueArr.length; i++) {
          valueArr[i] = Long.valueOf(aValues[i].getText());
        }
      } else if (ConfigurationParameter.TYPE_FLOAT.equals(paramType)) {
        valueArr = new Float[aValues.length];
        for (int i = 0; i < valueArr.length; i++) {
          valueArr[i] = Float.valueOf(aValues[i].getText());
        }
      } else if (ConfigurationParameter.TYPE_DOUBLE.equals(paramType)) {
        valueArr = new Double[aValues.length];
        for (int i = 0; i < valueArr.length; i++) {
          valueArr[i] = Double.valueOf(aValues[i].getText());
        }
      } else if (ConfigurationParameter.TYPE_BOOLEAN.equals(paramType)) {
        valueArr = new Boolean[aValues.length];
        for (int i = 0; i < valueArr.length; i++) {
          valueArr[i] = Boolean.valueOf(aValues[i].getText());
        }
      } else {
        throw new InternalErrorCDE("invalid state");
      }
    } catch (NumberFormatException e) {
      Utility.popMessage("Invalid Number",
              "One or more values is not of the proper kind of number."
                      + " If this entry is the only one with the wrong numeric type,"
                      + " Please retype the proper kind of number. Otherwise,"
                      + " use the source page to change all the values to the proper type.",
              MessageDialog.ERROR);
      return;
    }
    setModelValue(valueArr);
  }

  /**
   * Sets the model value.
   *
   * @param value
   *          the new model value
   */
  private void setModelValue(Object value) {
    String groupName = master.getSelectedParamGroupName();
    boolean changed = false;
    if (COMMON_GROUP.equals(groupName)) {
      ConfigurationGroup[] groups = getConfigurationParameterDeclarations()
              .getConfigurationGroups();
      for (int i = 0; i < groups.length; i++) {
        String[] groupNames = groups[i].getNames();
        for (int j = 0; j < groupNames.length; j++) {
          if (isSameValue(value,
                  modelSettings.getParameterValue(groupNames[j], selectedCP.getName()))) {
            continue;
          }
          modelSettings.setParameterValue(groupNames[j], selectedCP.getName(), value);
          changed = true;
        }
      }
    } else if (NOT_IN_ANY_GROUP.equals(groupName)) {
      if (!isSameValue(value, modelSettings.getParameterValue(selectedCP.getName()))) {
        modelSettings.setParameterValue(selectedCP.getName(), value);
        changed = true;
      }
    } else {
      if (!isSameValue(value, modelSettings.getParameterValue(groupName, selectedCP.getName()))) {
        modelSettings.setParameterValue(groupName, selectedCP.getName(), value);
        changed = true;
      }
    }
    if (changed) {
      editor.setFileDirty();
    }
  }

  /**
   * Checks if is same value.
   *
   * @param v1
   *          the v 1
   * @param v2
   *          the v 2
   * @return true, if is same value
   */
  private boolean isSameValue(Object v1, Object v2) {
    if (v1 instanceof Object[]) {
      return (Arrays.equals((Object[]) v1, (Object[]) v2));
    } else {
      if (null == v1) {
        return null == v2;
      }
      return v1.equals(v2);
    }
  }

}
