/*
 * 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.ep_launcher.ui;

import java.nio.charset.Charset;
import java.nio.charset.IllegalCharsetNameException;
import java.util.HashSet;
import java.util.Set;

import org.apache.uima.ep_launcher.LauncherConstants;
import org.apache.uima.ep_launcher.LauncherConstants.InputFormat;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.jdt.debug.ui.launchConfigurations.JavaLaunchTab;
import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.window.Window;
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.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.dialogs.ContainerSelectionDialog;
import org.eclipse.ui.dialogs.ElementListSelectionDialog;
import org.eclipse.ui.dialogs.ElementTreeSelectionDialog;
import org.eclipse.ui.model.WorkbenchContentProvider;
import org.eclipse.ui.model.WorkbenchLabelProvider;

/**
 * The Analysis Engine Main Tab is responsible to display/edit the UIMA related settings, such as
 * project, descriptor file, input folders, etc.
 */
// TODO: Add an icon for the main tab
public class AnalysisEngineMainTab extends JavaLaunchTab {

  private Text projectText;

  private Text descriptorText;

  private Text inputText;
  private Button recursivelyButton;

  private Button casButton;
  private Button plainTextButton;
  private Combo encodingCombo;
  private Text languageText;

  private Text outputFolderText;
  private Button clearFolderButton;

  private IWorkspaceRoot getWorkspaceRoot() {
    return ResourcesPlugin.getWorkspace().getRoot();
  }

  private IProject getSelectedProject() {
    IResource project = ResourcesPlugin.getWorkspace().getRoot().findMember(projectText.getText());
    if (project instanceof IProject) {
      return (IProject) project;
    }

    return null;
  }

  private IContainer getContainer(String path) {
    Path containerPath = new Path(path);
    IResource resource = getWorkspaceRoot().findMember(containerPath);
    if (resource instanceof IContainer)
      return (IContainer) resource;

    return null;
  }

  @Override
  public void createControl(Composite composite) {

    Composite projectComposite = new Composite(composite, SWT.NONE);
    GridLayout projectGridLayout = new GridLayout();
    projectGridLayout.numColumns = 1;
    projectGridLayout.horizontalSpacing = SWT.FILL;
    projectComposite.setLayout(projectGridLayout);

    // Project Group
    Group projectGroup = new Group(projectComposite, SWT.None);
    projectGroup.setText("Project:");

    GridData projectGroupData = new GridData();
    projectGroupData.grabExcessHorizontalSpace = true;
    projectGroupData.horizontalAlignment = SWT.FILL;
    projectGroup.setLayoutData(projectGroupData);

    GridLayout projectGroupLayout = new GridLayout(2, false);
    projectGroup.setLayout(projectGroupLayout);

    projectText = new Text(projectGroup, SWT.BORDER);
    GridDataFactory.swtDefaults().align(SWT.FILL, SWT.CENTER).grab(true, false)
            .applyTo(projectText);
    projectText.addModifyListener(new ModifyListener() {

      @Override
      public void modifyText(ModifyEvent event) {
        updateLaunchConfigurationDialog();
      }
    });

    Button browseProject = new Button(projectGroup, SWT.NONE);
    browseProject.setText("Browse ...");
    browseProject.addSelectionListener(new SelectionAdapter() {
      @Override
      public void widgetSelected(SelectionEvent e) {
        ILabelProvider labelProvider = new WorkbenchLabelProvider();

        ElementListSelectionDialog dialog = new ElementListSelectionDialog(getShell(),
                labelProvider);
        dialog.setTitle("Project Selection");
        dialog.setMessage("Select a project");
        dialog.setElements(getWorkspaceRoot().getProjects());
        IProject project = getSelectedProject();
        if (project != null) {
          dialog.setInitialSelections(new Object[] { project });
        }

        if (dialog.open() == Window.OK) {
          IProject selectedProject = (IProject) dialog.getFirstResult();
          projectText.setText(selectedProject.getName());
        }
      }
    });

    // Descriptor Group
    Group descriptorGroup = new Group(projectComposite, SWT.None);
    descriptorGroup.setText("Descriptor:");

    GridData descriptorGroupData = new GridData();
    descriptorGroupData.grabExcessHorizontalSpace = true;
    descriptorGroupData.horizontalAlignment = SWT.FILL;
    descriptorGroup.setLayoutData(projectGroupData);

    GridLayout descriptorGroupLayout = new GridLayout(2, false);
    descriptorGroup.setLayout(descriptorGroupLayout);

    descriptorText = new Text(descriptorGroup, SWT.BORDER);
    GridDataFactory.swtDefaults().align(SWT.FILL, SWT.CENTER).grab(true, false)
            .applyTo(descriptorText);
    descriptorText.addModifyListener(new ModifyListener() {

      @Override
      public void modifyText(ModifyEvent event) {
        updateLaunchConfigurationDialog();
      }
    });
    Button browseDescriptor = new Button(descriptorGroup, SWT.NONE);
    browseDescriptor.setText("Browse ...");
    browseDescriptor.addSelectionListener(new SelectionAdapter() {
      @Override
      public void widgetSelected(SelectionEvent e) {
        ElementTreeSelectionDialog dialog = new ElementTreeSelectionDialog(getShell(),
                new WorkbenchLabelProvider(), new WorkbenchContentProvider());
        dialog.setTitle("Select descriptor");
        dialog.setMessage("Select descriptor");
        dialog.setInput(getWorkspaceRoot());
        dialog.setInitialSelection(getWorkspaceRoot().findMember(descriptorText.getText()));
        if (dialog.open() == IDialogConstants.OK_ID) {
          IResource resource = (IResource) dialog.getFirstResult();
          if (resource != null) {
            String fileLoc = resource.getFullPath().toString();
            descriptorText.setText(fileLoc);
          }
        }
      }
    });

    // Input Resource Group
    Group inputResourceGroup = new Group(projectComposite, SWT.None);
    inputResourceGroup.setText("Input Resource:");

    GridDataFactory.swtDefaults().align(SWT.FILL, SWT.CENTER).grab(true, false)
            .applyTo(inputResourceGroup);

    GridLayout inputResourceGroupLayout = new GridLayout(2, false);
    inputResourceGroup.setLayout(inputResourceGroupLayout);

    inputText = new Text(inputResourceGroup, SWT.BORDER);
    GridDataFactory.swtDefaults().align(SWT.FILL, SWT.CENTER).grab(true, false).applyTo(inputText);
    inputText.addModifyListener(new ModifyListener() {

      @Override
      public void modifyText(ModifyEvent event) {
        updateLaunchConfigurationDialog();
      }
    });

    Button browseInputResource = new Button(inputResourceGroup, SWT.NONE);
    browseInputResource.setText("Browse ...");
    browseInputResource.addSelectionListener(new SelectionAdapter() {
      @Override
      public void widgetSelected(SelectionEvent e) {
        ElementTreeSelectionDialog dialog = new ElementTreeSelectionDialog(getShell(),
                new WorkbenchLabelProvider(), new WorkbenchContentProvider());
        dialog.setTitle("Select input folder or file");
        dialog.setMessage("Select input folder or file");
        dialog.setInput(getSelectedProject());
        dialog.setInitialSelection(getWorkspaceRoot().findMember(inputText.getText()));
        if (dialog.open() == IDialogConstants.OK_ID) {
          IResource resource = (IResource) dialog.getFirstResult();
          if (resource != null) {
            String fileLoc = resource.getFullPath().toString();
            inputText.setText(fileLoc);
          }
        }
      }
    });

    recursivelyButton = new Button(inputResourceGroup, SWT.CHECK);
    recursivelyButton.setText("Recursively, read all files under each directory");
    GridDataFactory.swtDefaults().align(SWT.FILL, SWT.CENTER).grab(true, false)
            .applyTo(recursivelyButton);
    recursivelyButton.addSelectionListener(new SelectionListener() {

      @Override
      public void widgetSelected(SelectionEvent event) {
        updateLaunchConfigurationDialog();
      }

      @Override
      public void widgetDefaultSelected(SelectionEvent event) {
      }
    });

    Group inputFormatGroup = new Group(projectComposite, SWT.None);
    inputFormatGroup.setText("Input Format:");

    GridDataFactory.swtDefaults().align(SWT.FILL, SWT.CENTER).grab(true, false)
            .applyTo(inputFormatGroup);

    GridLayout inputFormatGroupLayout = new GridLayout(4, false);
    inputFormatGroup.setLayout(inputFormatGroupLayout);

    casButton = new Button(inputFormatGroup, SWT.RADIO);
    casButton.setText("CASes (XMI or XCAS format)");
    GridDataFactory.swtDefaults().align(SWT.FILL, SWT.CENTER).grab(true, false).span(4, 1)
            .applyTo(casButton);
    casButton.addSelectionListener(new SelectionListener() {

      @Override
      public void widgetSelected(SelectionEvent event) {
        updateLaunchConfigurationDialog();
      }

      @Override
      public void widgetDefaultSelected(SelectionEvent event) {
      }
    });

    plainTextButton = new Button(inputFormatGroup, SWT.RADIO);
    GridDataFactory.swtDefaults().align(SWT.LEFT, SWT.CENTER).grab(false, false)
            .applyTo(plainTextButton);
    plainTextButton.addSelectionListener(new SelectionListener() {

      @Override
      public void widgetSelected(SelectionEvent event) {
        encodingCombo.setEnabled(plainTextButton.getSelection());
        languageText.setEnabled(plainTextButton.getSelection());
        updateLaunchConfigurationDialog();
      }

      @Override
      public void widgetDefaultSelected(SelectionEvent event) {
      }
    });
    plainTextButton.setText("Plain Text, encoding:");

    encodingCombo = new Combo(inputFormatGroup, SWT.NONE);
    GridDataFactory.swtDefaults().align(SWT.LEFT, SWT.CENTER).grab(false, false)
            .applyTo(encodingCombo);

    encodingCombo.addModifyListener(new ModifyListener() {

      @Override
      public void modifyText(ModifyEvent event) {
        updateLaunchConfigurationDialog();
      }
    });

    String defaultEncoding = Charset.defaultCharset().displayName();

    Set<String> charsets = new HashSet<>();
    charsets.add("US-ASCII");
    charsets.add("ISO-8859-1");
    charsets.add("UTF-8");
    charsets.add("UTF-16BE");
    charsets.add("UTF-16LE");
    charsets.add("UTF-16");
    charsets.add(defaultEncoding);

    encodingCombo.setItems(charsets.toArray(new String[charsets.size()]));
    // Will be enabled by initializeForm if format is plain text
    encodingCombo.setEnabled(false);

    // Add language label
    Label languageLabel = new Label(inputFormatGroup, SWT.NONE);
    languageLabel.setText("Language:");

    // Add language text field
    languageText = new Text(inputFormatGroup, SWT.BORDER);
    GridDataFactory.swtDefaults().hint(250, SWT.DEFAULT).align(SWT.LEFT, SWT.CENTER)
            .grab(true, false).applyTo(languageText);

    languageText.addModifyListener(new ModifyListener() {

      @Override
      public void modifyText(ModifyEvent event) {
        updateLaunchConfigurationDialog();
      }
    });

    // Output Folder
    Group outputFolderGroup = new Group(projectComposite, SWT.None);
    outputFolderGroup.setText("Output Folder:");
    GridDataFactory.swtDefaults().align(SWT.FILL, SWT.CENTER).grab(true, false)
            .applyTo(outputFolderGroup);
    GridLayout outputFolderGroupLayout = new GridLayout(2, false);
    outputFolderGroup.setLayout(outputFolderGroupLayout);
    outputFolderText = new Text(outputFolderGroup, SWT.BORDER);
    GridDataFactory.swtDefaults().align(SWT.FILL, SWT.CENTER).grab(true, false)
            .applyTo(outputFolderText);
    outputFolderText.addModifyListener(new ModifyListener() {

      @Override
      public void modifyText(ModifyEvent event) {
        updateLaunchConfigurationDialog();
      }
    });

    Button browseOutputFolderButton = new Button(outputFolderGroup, SWT.NONE);
    browseOutputFolderButton.setText("Browse ...");
    browseOutputFolderButton.addSelectionListener(new SelectionAdapter() {
      @Override
      public void widgetSelected(SelectionEvent e) {
        // TODO: Only select elements within project
        String currentContainerString = outputFolderText.getText();
        IContainer currentContainer = getContainer(currentContainerString);
        ContainerSelectionDialog dialog = new ContainerSelectionDialog(getShell(), currentContainer,
                false, "Select output folder");
        dialog.showClosedProjects(false);
        dialog.open();
        Object[] results = dialog.getResult();
        if ((results != null) && (results.length > 0) && (results[0] instanceof IPath)) {
          IPath path = (IPath) results[0];
          String containerName = path.toOSString();
          outputFolderText.setText(containerName);
        }
      }
    });

    clearFolderButton = new Button(outputFolderGroup, SWT.CHECK);
    clearFolderButton.setText("Clear the output folder");
    GridDataFactory.swtDefaults().align(SWT.FILL, SWT.CENTER).grab(true, false)
            .applyTo(clearFolderButton);
    clearFolderButton.addSelectionListener(new SelectionListener() {
      @Override
      public void widgetSelected(SelectionEvent event) {
        updateLaunchConfigurationDialog();
      }

      @Override
      public void widgetDefaultSelected(SelectionEvent event) {
      }
    });
    setControl(projectComposite);
  }

  @Override
  public String getName() {
    return "Main";
  }

  @Override
  public boolean isValid(ILaunchConfiguration launchConfig) {

    setErrorMessage(null);

    // Project must be set, check that project does exist
    String projectName = projectText.getText();

    IResource projectResource = ResourcesPlugin.getWorkspace().getRoot().findMember(projectName);
    if (!(projectResource instanceof IProject)) {
      setErrorMessage("Project does not exist!");
      return false;
    }

    // Descriptor must be set and valid file
    IResource descriptorResource = getWorkspaceRoot().findMember(descriptorText.getText());
    if (!(descriptorResource instanceof IFile)) {
      setErrorMessage("Descriptor must be an existing file!");
      return false;
    }

    // Input folder or file must be set
    IResource inputResource = getWorkspaceRoot().findMember(inputText.getText());
    if (inputResource == null) {
      setErrorMessage("Input resource must be an existing file or folder!");
      return false;
    }

    // Validate the input encoding
    if (plainTextButton.getSelection()) {
      String inptuEncoding = encodingCombo.getText();

      boolean isEncodingValid;
      try {
        isEncodingValid = Charset.isSupported(inptuEncoding);
      } catch (IllegalCharsetNameException e) {
        isEncodingValid = false;
      }

      if (!isEncodingValid) {
        setErrorMessage("Invalid input format encoding!");
        return false;
      }
    }

    // Validate output folder
    if (outputFolderText.getText().length() > 0) {
      IResource outputResource = getWorkspaceRoot().findMember(outputFolderText.getText());
      if (!(outputResource instanceof IFolder)) {
        setErrorMessage("The output folder must be a valid folder or not be set!");
        return false;
      }
    }

    return super.isValid(launchConfig);
  }

  @Override
  public void performApply(ILaunchConfigurationWorkingCopy config) {
    config.setAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME,
            (String) projectText.getText());

    config.setAttribute(LauncherConstants.ATTR_DESCRIPTOR_NAME, descriptorText.getText());

    config.setAttribute(LauncherConstants.ATTR_INPUT_NAME, inputText.getText());
    config.setAttribute(LauncherConstants.ATTR_INPUT_RECURSIVELY_NAME,
            recursivelyButton.getSelection());

    String formatName;
    if (casButton.getSelection()) {
      formatName = InputFormat.CAS.toString();
    } else if (plainTextButton.getSelection()) {
      formatName = InputFormat.PLAIN_TEXT.toString();
    } else {
      throw new IllegalStateException("One button must always be selected!");
    }

    config.setAttribute(LauncherConstants.ATTR_INPUT_FORMAT_NAME, formatName);

    config.setAttribute(LauncherConstants.ATTR_INPUT_ENCODING_NAME, encodingCombo.getText());

    config.setAttribute(LauncherConstants.ATTR_INPUT_LANGUAGE_NAME, languageText.getText());

    config.setAttribute(LauncherConstants.ATTR_OUTPUT_FOLDER_NAME, outputFolderText.getText());
    config.setAttribute(LauncherConstants.ATTR_OUTPUT_CLEAR_NAME, clearFolderButton.getSelection());
  }

  @Override
  public void setDefaults(ILaunchConfigurationWorkingCopy config) {
    config.setAttribute(LauncherConstants.ATTR_INPUT_RECURSIVELY_NAME, false);
    config.setAttribute(LauncherConstants.ATTR_INPUT_FORMAT_NAME, InputFormat.CAS.toString());
    config.setAttribute(LauncherConstants.ATTR_INPUT_LANGUAGE_NAME, "x-unspecified");
    config.setAttribute(LauncherConstants.ATTR_OUTPUT_CLEAR_NAME, false);
  }

  @Override
  public void initializeFrom(ILaunchConfiguration config) {

    // TODO: Log errors if reading fails?

    // write values to launch configuration ...
    try {
      projectText.setText(
              config.getAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, ""));
    } catch (CoreException e) {
      projectText.setText("");
    }

    try {
      descriptorText.setText(config.getAttribute(LauncherConstants.ATTR_DESCRIPTOR_NAME, ""));
    } catch (CoreException e) {
      descriptorText.setText("");
    }

    try {
      inputText.setText(config.getAttribute(LauncherConstants.ATTR_INPUT_NAME, ""));
    } catch (CoreException e) {
      inputText.setText("");
    }

    // recursive button
    try {
      recursivelyButton.setSelection(
              (Boolean) config.getAttribute(LauncherConstants.ATTR_INPUT_RECURSIVELY_NAME, false));
    } catch (CoreException e) {
      recursivelyButton.setSelection(false);
    }

    // Format buttons
    String formatName;
    try {
      formatName = config.getAttribute(LauncherConstants.ATTR_INPUT_FORMAT_NAME,
              InputFormat.CAS.toString());
    } catch (CoreException e) {
      formatName = InputFormat.CAS.toString();
    }

    if (InputFormat.CAS.toString().equals(formatName)) {
      casButton.setSelection(true);
    } else if (InputFormat.PLAIN_TEXT.toString().equals(formatName)) {
      plainTextButton.setSelection(true);
      encodingCombo.setEnabled(true);
      languageText.setEnabled(true);

      String language;
      try {
        language = config.getAttribute(LauncherConstants.ATTR_INPUT_LANGUAGE_NAME, "x-unspecified");
      } catch (CoreException e) {
        language = "x-unspecified";
      }

      languageText.setText(language);
    }

    // Always remember the input encoding, even so plain text is not selected,
    // it might be convenient for the user
    String inputEncoding = Charset.defaultCharset().displayName();
    try {
      inputEncoding = config.getAttribute(LauncherConstants.ATTR_INPUT_ENCODING_NAME,
              inputEncoding);
    } catch (CoreException e) {
    }

    encodingCombo.setText(inputEncoding);

    // output folder
    try {
      outputFolderText.setText(config.getAttribute(LauncherConstants.ATTR_OUTPUT_FOLDER_NAME, ""));
    } catch (CoreException e) {
      outputFolderText.setText("");
    }

    // clear folder button
    try {
      clearFolderButton.setSelection(
              (Boolean) config.getAttribute(LauncherConstants.ATTR_OUTPUT_CLEAR_NAME, false));
    } catch (CoreException e) {
      clearFolderButton.setSelection(false);
    }

    super.initializeFrom(config);
  }
}
